华为设备上的FileProvider错误 [英] FileProvider error onHuawei devices

查看:400
本文介绍了华为设备上的FileProvider错误的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

使用FileProvider.getUriForFile时,我的应用中仅在华为设备上发生例外:

I have an exception that happens only on Huawei devices in my app when using 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)

这是清单中我的文件提供者的定义:

Here is the definition of my file provider in my manifest:

<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>

具有配置路径的资源文件:

The resource file with configured paths:

<?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>

关于此问题的起因的任何想法,以及为什么仅在华为设备上发生?如果我没有华为设备,该如何调试?

Any idea on the cause of this issue and why it happens only on Huawei devices? How would I debug this, given that I don't have a Huawei device?

更新:

我在应用程序中添加了更多日志,在这些设备上同时打印ContextCompat.getExternalFilesDirscontext.getExternalFilesDir时,结果不一致:

I've added more logs into my app and I got some inconsistent results when printing both ContextCompat.getExternalFilesDirs and context.getExternalFilesDir on these devices:

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)

This is inconsistent with the documentation of ContextCompat.getExternalFilesDirs that states that The first path returned is the same as getExternalFilesDir(String)

这解释了这个问题,因为我在代码中使用了context.getExternalFilesDirFileProvider使用了ContextCompat.getExternalFilesDirs.

That explains the issue since I use context.getExternalFilesDir in my code and FileProvider uses ContextCompat.getExternalFilesDirs.

推荐答案

Android N的更新(下面保留了原始答案,并确认了这种新方法可以在生产环境中使用):

正如您在更新中指出的那样,许多华为设备型号(例如KIW-L24,ALE-L21,ALE-L02,PLK-L01和其他各种设备)都违反了Android呼叫ContextCompat#getExternalFilesDirs(String)的合同.如果存在数组,它们将返回第一个对象作为到外部SD卡的路径,而不是将Context#getExternalFilesDir(String)(即默认条目)作为数组中的第一个对象返回.

As you noted in your update, many Huawei device models (e.g. KIW-L24, ALE-L21, ALE-L02, PLK-L01, and a variety of others) break the Android contract for calls to ContextCompat#getExternalFilesDirs(String).
 Rather than returning Context#getExternalFilesDir(String) (ie the default entry) as the first object in the
 array, they instead return the first object as the path to the external SD card, if one is present.

通过违反此订购合同,这些带有外部SD卡的华为设备将在呼叫FileProvider#getUriForFile(Context, String, File)external-files-path根目录时以IllegalArgumentException崩溃.您可以尝试使用多种解决方案来尝试解决此问题(例如,编写自定义FileProvider实现),但我发现最简单的方法是捕获此问题,并:

By breaking this ordering contract, these Huawei devices with external SD cards will crash with an IllegalArgumentException on calls to FileProvider#getUriForFile(Context, String, File) for external-files-path roots. While there are a variety of solutions that you can pursue to attempt to deal with this issue (e.g. writing a custom FileProvider implementation), I've found the easiest approach is to catch this issue and:

  • Pre-N:返回Uri#fromFile(File),由于FileUriExposedException
  • ,它不适用于Android N及更高版本
  • N:将文件复制到您的cache-path(注意:如果在UI线程上执行此操作,可能会引入ANR),然后为复制的文件返回FileProvider#getUriForFile(Context, String, File)(即,完全避免了该错误)
  • Pre-N: Return Uri#fromFile(File), which won't work with Android N and above due to FileUriExposedException
  • N: Copy the file to your cache-path (note: this can introduce ANRs if done on the UI Thread) and then return FileProvider#getUriForFile(Context, String, File) for the copied file (ie avoiding the bug altogether)

完成此操作的代码如下:

Code to accomplish this can be found below:

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>

创建了这样的类后,将您的调用替换为:

Once you've created a class like this, replace your calls to:

FileProvider.getUriForFile(Context, String, File)

具有:

ContentUriProvider.getUriForFile(Context, String, File)

坦率地说,我认为这不是一个特别优雅的解决方案,但是它确实允许我们使用正式记录的Android行为,而无需做任何过于激烈的事情(例如,编写自定义的FileProvider实现).我已经在生产环境中进行了测试,因此可以确认它可以解决这些华为故障.对我来说,这是最好的方法,因为我不想花太多时间解决明显是制造商的缺陷.

Frankly, I don't think this is an especially graceful solution, but it does allow us to use formally documented Android behavior without doing anything too drastic (e.g. writing a custom FileProvider implementation). I have tested this in production, so I can confirm that it resolves these Huawei crashes. For me, this was the best approach, since I didn't wish to spend too much time addressing what is quite obviously a manufacturer's defect.

从此问题之前的华为设备更新到Android N:

由于FileUriExposedException,这不适用于Android N及更高版本,但是我还没有遇到华为设备在Android N上的这种错误配置.

This won't work with Android N and above due to FileUriExposedException, but I have yet to encounter a Huawei device with this mis-configuration on 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);
        }
    }
}

这篇关于华为设备上的FileProvider错误的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆