可以通过 FileProvider 使用多个权限吗?

Possible to use multiple authorities with FileProvider?

背景

我维护着一个 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 来共享文件。我的期望是,如果消费应用程序使用以下清单 <provider> 标签,应该可以允许两个提供者共享文件:

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

此清单条目:

<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 然后 returns 为空,导致此 NPE。

如果我在此指令处暂停时手动调用 resolveContentProvider 并传递 "${applicationId}.fileprovider" "${applicationId}.bugshaker.fileprovider"resolveContentProvider returns 一个非空 ProviderInfo 实例(这似乎是预期的结果)。

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

A list of one or more URI authorities that identify data offered by the content provider. Multiple authorities are listed by separating their names with a semicolon. To avoid conflicts, authority names should use a Java-style naming convention (such as com.example.provider.cartoonprovider). Typically, it's the name of the ContentProvider subclass that implements the provider

There is no default. At least one authority must be specified.

问题

我对这个问题的解决方案实际上是避免依赖单个 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>

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

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

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

The name of the class that implements the content provider, a subclass of ContentProvider. This should be a fully qualified class name (such as, "com.example.project.TransportationProvider"). [...]

我也遇到这个问题,用这个方法解决了。 就像我有一个使用文件提供程序的库图像选择器,而且我的应用程序在构建我的应用程序冲突时也使用文件提供程序。

我提供的文件是

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