FileProvider是否支持使用多个授权?

52

背景

我维护一个,其核心功能是将程序捕获的截图共享到外部电子邮件应用程序。

为此,我使用了FileProvider,这意味着我的库的清单包含<provider>标记

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

该清单条目:

  • 指定了两个 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.resolveContentProvider。然而,resolveContentProvider返回null,导致了空指针异常。
如果我在此处暂停并手动调用resolveContentProvider,并传递"${applicationId}.fileprovider""${applicationId}.bugshaker.fileprovider"之一,则resolveContentProvider返回非空的ProviderInfo实例(这似乎是预期的结果)。
这种差异让我感到困惑,因为<provider>元素文档指出支持多个权限:
“一个或多个URI身份证明,它们标识由内容提供者提供的数据。使用分号将其名称分隔开以列出多个身份证明。为避免冲突,身份证明名称应使用Java样式命名约定(例如com.example.provider.cartoonprovider)。通常,它是实现提供程序的ContentProvider子类的名称。”
“没有默认值。必须指定至少一个身份证明。”
问题:
1. 是否可能一个应用程序使用单个FileProvider公开多个权限和文件路径?
- 如果可以,我需要改变什么才能使它工作? - 如果不行,有没有其他的方式来配置我的库中的文件共享,以避免这种冲突?

我注意到方法PackageItemInfo.loadXmlMetaData被调用,传入的authority字符串为"${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"。在loadXmlMetaData()方法中,您没有提供authority字符串,在FileProvider源代码中也没有看到这一点。前一行提供了一个authority给resolveContentProvider()方法。您是指这个吗?如果是的话,ProviderInfo提供了分号分隔的列表,FileProvider似乎无法处理它。 - CommonsWare
除此之外,在查看FileProvider中的代码时,似乎他们没有处理多权限场景。他们有钩子来处理按权限分组的多路径策略,但似乎从未解析过分号分隔的列表。可能是未经测试的。我在我的StreamProvider中编写了解析列表的代码,但我也没有测试它。 :-( - CommonsWare
今晚的最后一条评论;我将在本周早些时候测试我的示例应用程序中的“StreamProvider”,并回报结果... - stkent
通常情况下,需要不同类型内容提供程序的应用程序在清单文件中会有完全不同的类。很少有库附带 ContentProvider,更少的是可能由2个或更多库或1个其他库和应用程序本身以具体形式(即以注册形式)需要它。因此,我认为不存在 "通常情况"。在这种情况下,FileProvider(和StreamProvider)依赖于静态缓存数据来管理多个权限,因此仅使用FileProvider / StreamProvider的简单子类是不够的。 - CommonsWare
1
请关注此问题:https://github.com/commonsguy/cwac-provider/issues/30。 - CommonsWare
显示剩余3条评论
2个回答

51

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

一个使用通用的FileProvider并且引用了我的库的应用合并后的清单文件,将会包含下面两个条目(不会发生冲突!):

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

但是,快速查看文档后发现我的错误:

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


3
你是如何使用 getUriForFile(Context, "com.my.authority.fileprovider", file); 的?如果是在你的库中使用它,那么你是如何从.getUriForFile()的第二个参数动态生成权限字符串的?我仅在我的库项目中使用 FileProvider,因此我已经静态定义了权限字符串 getUriForFile(Context, "com.my.authority.fileprovider", file);,但似乎我需要动态生成它。 - Sakiboy
1
如果有帮助的话,这是相关存储库:https://github.com/stkent/bugshaker-android。`bugshaker`模块=库,`example`模块=应用程序。我的库提供者仅在库内部使用,而不是由使用应用程序使用——这似乎与您的设置不同? - stkent
2
让我们在聊天中继续这个讨论。点击此处进入聊天室 - stkent
1
在我的情况下,子类化ContentProvider没有帮助,所以我子类化了FileProvider,它像魔法一样奏效。 - Ashish Kanswal
2
如果有人无法进入聊天,您可以通过为库版本添加唯一后缀来避免权限路径冲突。并调用 getUriForFile(context, applicationContext.getPackageName() + LIB_SUFFIX, file); - Sameer J
显示剩余2条评论

1

我也遇到了这个问题,并采用了以下方法来解决它。 就像我有一个使用文件提供程序的图像选择器库,而且当我构建我的应用程序时,我的应用程序也使用了一个文件提供程序,导致冲突发生。

我的文件提供程序是

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

1
你正在用你的文件提供程序替换其他的。只有当其他人将他们的文件保存到与你的相同的目录中时,它才能正常工作,否则其他库无法访问他们保存的文件。 - hjchin
有什么更好的方法来实现这个,我的意思是如果库有一个文件提供者,而应用程序也有一个文件提供者,如何处理它? - sri

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接