添加链接
link之家
链接快照平台
  • 输入网页链接,自动生成快照
  • 标签化管理网页链接

华为设备上的FileProvider getUriForFile()错误

42 人关注

在我的应用程序中,当使用 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.getExternalFilesDirscontext.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 个评论
你在哪里/如何得到这个 File /storage/<card name> 看起来并不正确。
请告诉我getExternalStorageDirerctory()在这个设备上提供什么。
我正在使用 Context.getExternalFileDir(null) 获取文件。从我在这些设备上的日志来看,它可以返回 storage/sdcard1/, /storage/3565-3131/, /storage/73A8-8626/, /storage/864A-F3ED...
android
android-fileprovider
huawei-mobile-services
guillaume-tgl
guillaume-tgl
发布于 2016-10-06
4 个回答
wrb
wrb
发布于 2019-12-27
0 人赞同

针对Android N的更新(留下下面的原始答案,并已确认这种新方法在生产中发挥作用)。

正如你在更新中指出的,许多华为设备型号(如KIW-L24、ALE-L21、ALE-L02、PLK-L01和其他各种型号)在调用 ContextCompat#getExternalFilesDirs(String) 时违反了Android契约。它们不是将 Context#getExternalFilesDir(String) (即默认条目)作为数组中的第一个对象返回,而是将第一个对象作为外部SD卡的路径返回,如果有的话。

通过破坏这个订购契约,这些带有外部SD卡的华为设备在调用 FileProvider#getUriForFile(Context, String, File) external-files-path 根时,会出现 IllegalArgumentException 而崩溃。虽然有多种解决方案可以尝试处理这个问题(例如编写一个自定义的 FileProvider 实现),但我发现最简单的方法是抓住这个问题并。

  • 前N:返回 Uri#fromFile(File) ),由于 FileUriExposedException ,这在Android N及以上版本中无法使用。
  • N:将文件复制到你的 cache-path (注意:如果在UI线程上完成,这可能会引入ANRs),然后为复制的文件返回 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实现)。我已经在生产中测试了这一点,所以我可以确认它解决了这些华为的崩溃。对我来说,这是最好的方法,因为我不希望花太多时间来解决很明显是制造商的缺陷。

    从之前的华为设备更新到安卓N系统,有这个bug。

    由于FileUriExposedException,这在安卓N及以上系统中是行不通的,但我还没有在安卓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);
    
    我们现在看到这种情况发生在装有Android N的华为设备上。
    我可以确认,在运行安卓N的华为设备上,在这种错误配置下,我有几次崩溃。
    wrb
    我也开始看到这个问题。在上面提供了一个更新的答案,它应该能更普遍地解决这个错误。
    IOUtils目前已被弃用,那么替代方案是什么?
    @David input.copyTo(output) )
    simekadam
    simekadam
    发布于 2019-12-27
    0 人赞同

    我也有同样的问题,最后我的解决方案是始终使用 ContextCompat.getExternalFilesDirs 调用来建立作为 FileProvider 参数的 File 。 这样,你就不必使用上述任何变通方法。

    换句话说。如果你能控制用于调用 FileProvider File 参数,并且/或者你不在乎文件最终可能被保存在经典的 /storage/emulated/0/Android/data/ 文件夹之外(这应该完全没问题,因为都是同一张SD卡),那么我建议做我所做的。

    如果这不是你的情况,那么我建议使用上面的答案和自定义 getUriForFile 实现。

    ContextCompat.getExternalFilesDirs 返回一个文件数组。你选哪一个?如果你记得的话 :)
    art
    @Ufkoku 第一个应该被选中。但要注意处理数组为空的情况
    guillaume-tgl
    guillaume-tgl
    发布于 2019-12-27
    0 人赞同

    我现在对这个问题的解决方案,即使不完美,也是用以下路径声明我的 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支持库版本中被破坏,但我看不到任何其他的解决方案,可以使用现有的FileProvider为二级外部存储(通常是SD卡)中的文件服务。

    guest5618
    guest5618
    发布于 2019-12-27
    0 人赞同

    尝试手动提供URI

    var fileUri:Uri
    try{
       fileUri = FileProvider.getUriForFile(
                                this,
                                "com.example.android.fileprovider",
                        } catch (e:Exception){
                            Log.w("fileProvider Exception","$e")
     fileUri=Uri.parse("content://${authority}/${external-path name}/${file name}")