【问题标题】:FileProvider getUriForFile() error on Huawei devices华为设备上的 FileProvider getUriForFile() 错误
【发布时间】:2016-10-06 12:07:43
【问题描述】:

在我的应用中使用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

这与声明 The first path returned is the same as getExternalFilesDir(String)ContextCompat.getExternalFilesDirs 的文档不一致

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

【问题讨论】:

  • 你从哪里/如何得到这个File/storage/&lt;card name&gt; 看起来不正确。
  • 请告知 getExternalStorageDirerctory() 在此设备上提供的内容。
  • 我正在使用Context.getExternalFileDir(null) 获取文件。根据我在这些设备上的日志,它可以返回 storage/sdcard1/、/storage/3565-3131/、/storage/73A8-8626/、/storage/864A-F3ED...

标签: android android-fileprovider huawei-mobile-services


【解决方案1】:

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 实现),但我发现最简单的方法是发现此问题并且:

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

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

由于FileUriExposedException,这不适用于Android N及更高版本,但我还没有遇到在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);
        }
    }
}

【讨论】:

  • 我们现在在搭载 Android N 的华为设备上看到了这种情况。
  • 我可以确认我在运行 Android N 的华为设备上发生了几次崩溃,原因是配置错误。
  • 我也开始看到这个问题。在上面提供了一个更新的答案,应该更普遍地解决这个错误
  • IOUtils 目前已被弃用,有什么替代方案?
  • @David input.copyTo(output)
【解决方案2】:

我遇到了同样的问题,最终我的解决方案是始终使用ContextCompat.getExternalFilesDirs 调用来构建用作FileProvider 参数的File。 这样您就不必使用上述任何解决方法。

换句话说。如果您可以控制用于调用 FileProviderFile 参数和/或您不关心文件可能最终保存在经典的 /storage/emulated/0/Android/data/ 文件夹之外(这应该很好,因为都是同一张SD卡)然后我建议做我所做的。

如果不是您的情况,那么我建议将上述答案与自定义 getUriForFile 实现一起使用。

【讨论】:

  • ContextCompat.getExternalFilesDirs 返回一个文件数组。你选哪一个?如果你还记得:)
  • @Ufkoku 应该选择第一个。但是注意处理数组为空的情况
【解决方案3】:

我现在对这个问题的解决方案,即使它不完美,也是用以下路径声明我的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 卡)中提供文件.

【讨论】:

    【解决方案4】:

    尝试手动提供 uri

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

    在 AndroidManifest.xml 中的 provider 标签中从 android:authorites 获取权限

    从 file_paths.xml 中的外部路径标记中的名称获取外部路径名称

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 2018-08-21
      • 2023-03-24
      • 2021-04-19
      • 1970-01-01
      • 1970-01-01
      • 1970-01-01
      • 2018-01-04
      • 1970-01-01
      相关资源
      最近更新 更多