Ответ 1
Обновление для Android N (оставив исходный ответ ниже и подтвердив, что этот новый подход работает в процессе производства):
Как вы отметили в своем обновлении, многие модели устройств Huawei (например, KIW-L24, ALE-L21, ALE-L02, PLK-L01 и многие другие) нарушают контракт Android для звонков на ContextCompat#getExternalFilesDirs(String)
. Вместо того, чтобы возвращать Context#getExternalFilesDir(String)
(т.е. запись по умолчанию) в качестве первого объекта в массиве, они вместо этого возвращают первый объект в качестве пути к внешней SD-карте, если таковой присутствует.
Разрушая этот контракт на заказ, эти устройства Huawei с внешними SD-картами будут разбиваться с помощью IllegalArgumentException
при вызовах FileProvider#getUriForFile(Context, String, File)
для корней external-files-path
. Хотя существует множество решений, которые вы можете предпринять, чтобы попытаться решить эту проблему (например, написать пользовательскую реализацию FileProvider
), я нашел, что самый простой подход - уловить эту проблему и:
- Pre-N: Return
Uri#fromFile(File)
, который не будет работать с Android N и выше из-заFileUriExposedException
- N: Скопируйте файл на ваш
cache-path
(обратите внимание: это может ввести ANR, если это делается в потоке пользовательского интерфейса), а затем вернутьFileProvider#getUriForFile(Context, String, File)
для скопированного файла (т.е. вообще избежать ошибки)
Код для этого можно найти ниже:
public class ContentUriProvider {
private static final String HUAWEI_MANUFACTURER = "Huawei";
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) {
Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device Increased likelihood of failure...");
try {
return FileProvider.getUriForFile(context, authority, file);
} catch (IllegalArgumentException e) {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e);
return Uri.fromFile(file);
} else {
Log.w(ContentUriProvider.class.getSimpleName(), "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e);
// Note: Periodically clear this cache
final File cacheFolder = new File(context.getCacheDir(), HUAWEI_MANUFACTURER);
final File cacheLocation = new File(cacheFolder, file.getName());
InputStream in = null;
OutputStream out = null;
try {
in = new FileInputStream(file);
out = new FileOutputStream(cacheLocation); // appending output stream
IOUtils.copy(in, out);
Log.i(ContentUriProvider.class.getSimpleName(), "Completed Android N+ Huawei file copy. Attempting to return the cached file");
return FileProvider.getUriForFile(context, authority, cacheLocation);
} catch (IOException e1) {
Log.e(ContentUriProvider.class.getSimpleName(), "Failed to copy the Huawei file. Re-throwing exception", e1);
throw new IllegalArgumentException("Huawei devices are unsupported for Android N", e1);
} finally {
IOUtils.closeQuietly(in);
IOUtils.closeQuietly(out);
}
}
}
} else {
return FileProvider.getUriForFile(context, authority, file);
}
}
}
Наряду с file_provider_paths.xml
:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="public-files-path" path="." />
<cache-path name="private-cache-path" path="." />
</paths>
Как только вы создали такой класс, замените свои вызовы на:
FileProvider.getUriForFile(Context, String, File)
с:
ContentUriProvider.getUriForFile(Context, String, File)
Откровенно говоря, я не думаю, что это особенно грациозное решение, но это позволяет нам использовать официально зарегистрированное поведение Android, не делая ничего слишком резкого (например, написав пользовательскую реализацию FileProvider
). Я тестировал это на производстве, поэтому могу подтвердить, что он устраняет эти сбои Huawei. Для меня это был лучший подход, поскольку я не хотел тратить слишком много времени на то, что явно является дефектом производителя.
Обновление с устройств Huawei с этой ошибкой, обновленной до Android N:
Это не будет работать с Android N и выше из-за FileUriExposedException
, но я еще не столкнулся с устройством Huawei с этой неправильной конфигурацией на Android N.
public class ContentUriProvider {
private static final String HUAWEI_MANUFACTURER = "Huawei";
public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER) && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device on pre-N. Increased likelihood of failure...");
try {
return FileProvider.getUriForFile(context, authority, file);
} catch (IllegalArgumentException e) {
Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug", e);
return Uri.fromFile(file);
}
} else {
return FileProvider.getUriForFile(context, authority, file);
}
}
}