【问题标题】:Possible to use multiple authorities with FileProvider?可以对 FileProvider 使用多个权限吗?
【发布时间】:2019-08-27 21:11:36
【问题描述】:

背景

我维护一个library,其核心功能包括将通过编程捕获的屏幕截图分享给外部电子邮件应用程序。

我使用FileProvider 来完成此操作,这意味着我的库的清单包含a <provider> tag

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.bugshaker.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/filepaths" />
</provider>

filepaths.xml定义如下:

<paths>
    <files-path path="bug-reports/" name="bug-reports" />
</paths>

我的图书馆的消费者有一个应用程序,它本身使用FileProvider 来共享文件。我的期望是,如果消费应用程序使用以下清单 &lt;provider&gt; 标签,则应该可以允许两个提供者共享文件:

<provider
    android:authorities="${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true"
    android:name="android.support.v4.content.FileProvider"
    tools:replace="android:authorities">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_paths"
        tools:replace="android:resource" />
</provider>

此清单条目:

  • 指定两个Provider权限,${applicationId}.fileprovider(用于应用程序文件共享)和${applicationId}.bugshaker.fileprovider(用于库文件共享);
  • 引用更新的filepaths.xml,其中包含应用程序生成的文件和库生成的文件的单独目录定义:
<paths>
    <external-path
        name="redacted"
        path="" />
    <files-path
        name="bug-reports"
        path="bug-reports/" />
</paths>

构建应用程序后,我们确认生成的清单已将正确的节点替换为这些更新的值。

但是,当使用此配置的应用程序被组装(成功)并运行时,我们会在启动时看到崩溃:

E: FATAL EXCEPTION: main
   Process: com.stkent.bugshakertest, PID: 11636
   java.lang.RuntimeException: Unable to get provider android.support.v4.content.FileProvider: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
       at android.app.ActivityThread.installProvider(ActivityThread.java:5856)
       at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)
       at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)
       at android.app.ActivityThread.-wrap2(ActivityThread.java)
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)
       at android.os.Handler.dispatchMessage(Handler.java:102)
       at android.os.Looper.loop(Looper.java:154)
       at android.app.ActivityThread.main(ActivityThread.java:6119)
       at java.lang.reflect.Method.invoke(Native Method)
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
    Caused by: java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' on a null object reference
       at android.support.v4.content.FileProvider.parsePathStrategy(FileProvider.java:583)
       at android.support.v4.content.FileProvider.getPathStrategy(FileProvider.java:557)
       at android.support.v4.content.FileProvider.attachInfo(FileProvider.java:375)
       at android.app.ActivityThread.installProvider(ActivityThread.java:5853)
       at android.app.ActivityThread.installContentProviders(ActivityThread.java:5445) 
       at android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384) 
       at android.app.ActivityThread.-wrap2(ActivityThread.java) 
       at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545) 
       at android.os.Handler.dispatchMessage(Handler.java:102) 
       at android.os.Looper.loop(Looper.java:154) 
       at android.app.ActivityThread.main(ActivityThread.java:6119) 
       at java.lang.reflect.Method.invoke(Native Method) 
       at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886) 
       at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776) 

使用调试器,我可以看到方法FileProvider.parsePathStrategy 使用权限字符串"${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider" 调用PackageManager.resolveContentProviderresolveContentProvider 然后返回 null,导致这个 NPE。

如果我在这条指令处暂停时手动调用resolveContentProvider 并传递"${applicationId}.fileprovider" "${applicationId}.bugshaker.fileprovider"resolveContentProvider 会返回一个非空的ProviderInfo 实例(看起来是预期的结果)。

这种差异让我感到困惑,因为&lt;provider&gt; element documentation 声明支持多个权限:

标识内容提供者提供的数据的一个或多个 URI 权限的列表。通过用分号分隔它们的名称来列出多个权威。为避免冲突,权限名称应使用 Java 风格的命名约定(例如 com.example.provider.cartoonprovider)。通常,它是实现提供程序的 ContentProvider 子类的名称

没有默认值。必须至少指定一个权限。

问题

  • 是否可以让单个应用程序公开具有多个权限和文件路径的FileProvider
    • 如果是这样,我需要进行哪些更改才能使其正常工作?
    • 如果没有,是否有其他方法可以在我的库中配置文件共享以避免此类冲突?

【问题讨论】:

  • "我可以看到 PackageItemInfo.loadXmlMetaData 方法正在使用权限字符串“${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider”被调用——你没有给loadXmlMetaData()提供一个权限字符串,我在FileProvider源代码中没有看到。上一行给resolveContentProvider()提供了一个权限。是这个意思吗?如果是,ProviderInfo是提供分号分隔的列表,FileProvider 似乎无法处理。
  • 除此之外,查看FileProvider 中的代码,似乎他们没有处理多权限方案。它们具有按权限制定多种路径策略的钩子,但它们似乎从未解析分号分隔的列表。可能未经测试。我在my StreamProvider 中有可以解析列表的代码,但我也没有对其进行测试。 :-(
  • 好的,更新了。当 attachInfo 调用它的方法提供一个实例时,parsePathStrategy 检索一个新的 ProviderInfo 实例似乎很奇怪。我看到ContentProvider does split the authorities in attachInfoFileProvider 调用了super,但是ContentProvider 中的multi-authorities 字段似乎根本无法被子类访问。
  • 可能是一个基本问题,但是需要不同类型内容提供者的应用程序通常在清单中做什么?他们是否需要创建一个“基础”内容提供者并适当地拥有该委托?
  • 密切关注this issue

标签: android android-manifest android-contentprovider manifest-merging


【解决方案1】:

我对这个问题的解决方案实际上是避免依赖单个FileProvider 解析多个权限。虽然这并没有直接解决上述问题,但我将其发布以供后代使用。


我更新了我的库以利用 FileProvider 的空子类,因此库的更新清单提供程序条目现在是:

<provider
    android:name=".flow.email.screenshot.BugShakerFileProvider"
    android:authorities="${applicationId}.bugshaker.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/library_file_paths" />
</provider>

应用程序的合并清单 (1) 使用股票 FileProvider 和 (2) 使用我的库现在将包含下面显示的两个条目(没有冲突!):

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="com.consuming.application.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/application_file_paths" />
</provider>

<provider
    android:name="com.github.stkent.bugshaker.flow.email.screenshot.BugShakerFileProvider"
    android:authorities="com.consuming.application.bugshaker.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true" >
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/library_file_paths" />
</provider>

直到一位同事指出,我才意识到这是一个潜在的解决方案。我之前的假设(并且错误地)是清单中的所有FileProviders 都必须设置

android:name="android.support.v4.content.FileProvider"

但对the documentation 的快速检查发现了我的错误:

实现内容提供者的类的名称,ContentProvider 的子类。这应该是一个完全限定的类名(例如,“com.example.project.TransportationProvider”)。 [...]

【讨论】:

  • 您如何使用getUriForFile(Context, "com.my.authority.fileprovider", file);?您是否在您的库中使用它,如果是,您如何从.getUriForFile() 中的第二个参数动态生成权限字符串?我只在我的库项目中使用FileProvider,所以我有静态定义的权限字符串getUriForFile(Context, "com.my.authority.fileprovider", file);,但似乎我可能需要动态生成它。
  • 如果有帮助,这是有问题的回购:github.com/stkent/bugshaker-androidbugshaker 模块 = 库,example 模块 = 应用程序。我的库提供程序仅在库内部使用,而不是由消费应用程序使用 - 听起来这与您的设置不同?
  • 在我的情况下,将 ContentProvider 子类化并没有帮助,所以我将 FileProvider 子类化,它就像魅力一样工作
  • 如果有人没有加入聊天,您可以通过为库版本设置唯一的后缀来避免权限路径冲突。并致电getUriForFile(context, applicationContext.getPackageName() + LIB_SUFFIX, file);
【解决方案2】:

我也面临这个问题并使用这种方法来解决它。 就像我有一个使用文件提供程序的库图像选择器,当我构建我的应用程序冲突时,我的应用程序也使用文件提供程序。

我提供的文件是

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="org.contentarcadeapps.photoeditor.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"/>
        </provider>

改成

<provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="org.contentarcadeapps.photoeditor.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true"
            tools:replace="android:authorities">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths"
                tools:replace="android:resource"/>
        </provider>

【讨论】:

  • 您正在用您的替换其他文件提供程序。只有当其他人将他们的文件保存到与您的文件相同的目录中时,它才能正常工作,否则其他库没有访问他们保存它的文件。
  • 实现这个的更好方法是什么,我的意思是如果库有一个文件提供程序,而应用程序有一个文件提供程序如何处理它
猜你喜欢
  • 1970-01-01
  • 1970-01-01
  • 2011-03-29
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2015-01-01
  • 1970-01-01
  • 2021-07-18
相关资源
最近更新 更多