Ошибка FileProvider на устройствах Huawei
У меня есть исключение, которое происходит только на устройствах Huawei в моем приложении при использовании FileProvider.getUriForFile
:
Exception: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/<card name>/Android/data/<app package>/files/.export/2016-10-06 13-22-33.pdf
at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(SourceFile:711)
at android.support.v4.content.FileProvider.getUriForFile(SourceFile:400)
Вот определение моего файлового провайдера в моем манифесте:
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="${applicationId}.fileprovider"
android:exported="false"
android:grantUriPermissions="true">
<meta-data
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/file_provider_paths" />
</provider>
Файл ресурса с настроенными путями:
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-files-path name="external_files" path="" />
</paths>
Любая идея о причине этой проблемы и почему это происходит только на устройствах Huawei? Как бы я отладил это, учитывая, что у меня нет устройства Huawei?
ОБНОВИТЬ:
Я добавил больше журналов в свое приложение и получил противоречивые результаты при печати обоих ContextCompat.getExternalFilesDirs
а также context.getExternalFilesDir
на этих устройствах:
ContextCompat.getExternalFilesDirs:
/storage/emulated/0/Android/data/<package>/files
/storage/sdcard1/Android/data/<package>/files
context.getExternalFilesDir:
/storage/sdcard1/Android/data/<package>/files
Это не согласуется с документацией ContextCompat.getExternalFilesDirs
в котором говорится, что The first path returned is the same as getExternalFilesDir(String)
Это объясняет проблему, так как я использую context.getExternalFilesDir
в моем коде и FileProvider
использования ContextCompat.getExternalFilesDirs
,
3 ответа
Обновление для 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, если это сделано в потоке пользовательского интерфейса), а затем вернуть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);
}
}
}
У меня была та же проблема, и мое решение, в конце концов, было всегда использовать ContextCompat.getExternalFilesDirs
позвонить, чтобы построить File
который используется в качестве параметра для FileProvider
, Таким образом, вам не нужно использовать ни один из описанных выше обходных путей.
Другими словами. Если у вас есть контроль над File
параметр, который вы используете для вызова FileProvider
и / или вас не волнует, что файл может быть сохранен за пределами классического /storage/emulated/0/Android/data/
папку (что должно быть совершенно нормально, так как это все та же SD-карта), то я предлагаю сделать то, что я сделал.
Если это не ваш случай, то я предлагаю использовать приведенный выше ответ с пользовательским getUriForFile
реализация.
Мое решение этой проблемы прямо сейчас, даже если оно не идеально, это объявить FileProvider
по следующему пути (чтобы можно было обслуживать все файлы на устройстве):
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<root-path name="root" path="" />
</paths>
Это официально не задокументировано и может нарушить будущую версию библиотеки поддержки v4, но я не вижу другого решения для обслуживания файла во вторичном внешнем хранилище (часто на SD-карте) с использованием существующей FileProvider
,
Попробуйте вручную указать uri
var fileUri:Uri
try{
fileUri = FileProvider.getUriForFile(
this,
"com.example.android.fileprovider",
it
)
} catch (e:Exception){
Log.w("fileProvider Exception","$e")
fileUri=Uri.parse("content://${authority}/${external-path name}/${file name}")
}
получить полномочия от android:authorites в теге провайдера внутри AndroidManifest.xml
получить имя внешнего пути из имени в теге внешнего пути внутри file_paths.xml