Ошибка 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 .

Solutions Collecting From Web of "Ошибка FileProvider на устройствах Huawei"

Обновление для 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) для скопированного файла (т. 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 и / или вам все равно, что файл может быть сохранен вне каталога classic /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 .