2016-10-06 7 views
15

я исключение, которое происходит только на устройствах Huawei в моем приложении при использовании FileProvider.getUriForFile:FileProvider ошибок onHuawei устройства

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?

UPDATE:

Я добавил несколько записей в мое приложение, и я получил некоторые противоречивые результаты при печати как 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.

+0

Где/как вы получаете этот «файл»? '/ storage/' не выглядит корректным. – CommonsWare

+0

Укажите, что getExternalStorageDirerctory() предоставляет на этом устройстве. – greenapps

+0

Я получаю файл, используя 'Context.getExternalFileDir (null)'. Из журналов, которые у меня есть на этих устройствах, он может вернуть память/sdcard1 /,/storage/3565-3131 /,/storage/73A8-8626 /,/storage/864A-F3ED ... –

ответ

7

Обновление для 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); 
     } 
    } 
} 
+0

Мы видим, что это происходит Устройства Huawei с Android N теперь. –

+0

Я могу подтвердить, что у меня несколько сбоев с устройствами Huawei под управлением Android N, с этой неправильной конфигурацией. –

+0

Я тоже начал эту проблему. Приведенный выше ответ, который должен решить эту ошибку в целом – wrb

1

Мое решение этой проблемы прямо сейчас, даже если она не совершенна, чтобы объявить мой 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 ,

3

У меня была такая же проблема, и в конечном итоге мое решение заключалось в том, чтобы всегда использовать вызов ContextCompat.getExternalFilesDirs для построения File, который используется как параметр для FileProvider. Таким образом, вам не нужно использовать какие-либо из описанных выше обходных решений.

Иными словами. Если у вас есть контроль над параметром File, который вы используете для вызова FileProvider и/или вам все равно, что файл может быть сохранен вне классической папки /storage/emulated/0/Android/data/ (что должно быть прекрасно, так как это все тот же SD карты), то я предлагаю сделать то, что я сделал.

Если это не ваш случай, то я предлагаю использовать приведенный выше ответ с пользовательской версией getUriForFile.