在我的应用程序中,当使用
时,我有一个异常,
只
发生
在华为设备
上。
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>
对这个问题的原因有什么想法,为什么它只发生在华为设备上?鉴于我没有华为的设备,我如何调试这个问题?
我在我的应用程序中添加了更多的日志,在这些设备上打印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。
针对Android N的更新(留下下面的原始答案,并已确认这种新方法在生产中发挥作用)。
正如你在更新中指出的,许多华为设备型号(如KIW-L24、ALE-L21、ALE-L02、PLK-L01和其他各种型号)在调用
时违反了Android契约。它们不是将ContextCompat#getExternalFilesDirs(String)(即默认条目)作为数组中的第一个对象返回,而是将第一个对象作为外部SD卡的路径返回,如果有的话。Context#getExternalFilesDir(String)通过破坏这个订购契约,这些带有外部SD卡的华为设备在调用
的FileProvider#getUriForFile(Context, String, File)根时,会出现external-files-path而崩溃。虽然有多种解决方案可以尝试处理这个问题(例如编写一个自定义的IllegalArgumentException实现),但我发现最简单的方法是抓住这个问题并。FileProvider),由于Uri#fromFile(File),这在Android N及以上版本中无法使用。FileUriExposedException(注意:如果在UI线程上完成,这可能会引入ANRs),然后为复制的文件返回cache-path即完全避免该错误)。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