Обновление для 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: Возвращение
Uri#fromFile(File)
, которая не будет работать с Android N и выше из-за FileUriExposedException
- N: Скопируйте файл на
cache-path
(примечание: это может ввести ошибки ANR, если сделать на UI Thread), а затем вернуться 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)
Честно говоря, Я не думаю, что это особенно грациозное решение, но это позволяет нам использовать Mally документированное поведение 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);
}
}
}
Где/как вы получаете этот «файл»? '/ storage/' не выглядит корректным. –
CommonsWare
Укажите, что getExternalStorageDirerctory() предоставляет на этом устройстве. – greenapps
Я получаю файл, используя 'Context.getExternalFileDir (null)'. Из журналов, которые у меня есть на этих устройствах, он может вернуть память/sdcard1 /,/storage/3565-3131 /,/storage/73A8-8626 /,/storage/864A-F3ED ... –