在我的应用程序中,当使用
时,我有一个异常,
只
发生
在华为设备
上。
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契约。它们不是将
(即默认条目)作为数组中的第一个对象返回,而是将第一个对象作为外部SD卡的路径返回,如果有的话。
ContextCompat#getExternalFilesDirs(String)
Context#getExternalFilesDir(String)
通过破坏这个订购契约,这些带有外部SD卡的华为设备在调用
的
根时,会出现
而崩溃。虽然有多种解决方案可以尝试处理这个问题(例如编写一个自定义的
实现),但我发现最简单的方法是抓住这个问题并。
FileProvider#getUriForFile(Context, String, File)
external-files-path
IllegalArgumentException
FileProvider
Uri#fromFile(File)
FileUriExposedException
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