问题描述
背景
我维护一个库,其核心功能包括将通过编程捕获的屏幕截图分享给外部电子邮件应用程序.
我使用 FileProvider
来完成此操作,这意味着我的库的清单包含 一个标签:
<元数据android:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/filepaths"/></提供者>
filepaths.xml
定义如下:
<files-path path="bug-reports/" name="bug-reports"/></路径>
我的库的使用者有一个应用程序,它本身使用 FileProvider
来共享文件.我的期望是,如果消费应用程序使用以下清单 <provider>
标记,则应该可以允许两个提供者共享文件:
<元数据android:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/file_paths"工具:替换=机器人:资源"/></提供者>
此清单条目:
- 指定两个
Provider
权限,${applicationId}.fileprovider
(用于应用程序文件共享)和${applicationId}.bugshaker.fileprovider
(用于库文件共享); - 引用更新的
filepaths.xml
,其中包含应用程序生成的文件和库生成的文件的单独目录定义:
<外部路径名称=已编辑"路径=""/><文件路径名称=错误报告"路径=错误报告/"/></路径>
构建应用程序后,我们已确认生成的清单已将正确的节点替换为这些更新的值.
但是,当使用此配置的应用程序组装(成功)并运行时,我们会看到启动时崩溃:
E: FATAL EXCEPTION: main进程:com.stkent.bugshakertest,PID:11636java.lang.RuntimeException:无法获取提供程序 android.support.v4.content.FileProvider:java.lang.NullPointerException:尝试调用虚拟方法 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)' 在一个空对象引用上在 android.app.ActivityThread.installProvider(ActivityThread.java:5856)在 android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)在 android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)在 android.app.ActivityThread.-wrap2(ActivityThread.java)在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)在 android.os.Handler.dispatchMessage(Handler.java:102)在 android.os.Looper.loop(Looper.java:154)在 android.app.ActivityThread.main(ActivityThread.java:6119)在 java.lang.reflect.Method.invoke(Native Method)在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)引起:java.lang.NullPointerException: Attempt to invoke virtual method 'android.content.res.XmlResourceParser android.content.pm.PackageItemInfo.loadXmlMetaData(android.content.pm.PackageManager, java.lang.String)'对象引用在 android.support.v4.content.FileProvider.parsePathStrategy(FileProvider.java:583)在 android.support.v4.content.FileProvider.getPathStrategy(FileProvider.java:557)在 android.support.v4.content.FileProvider.attachInfo(FileProvider.java:375)在 android.app.ActivityThread.installProvider(ActivityThread.java:5853)在 android.app.ActivityThread.installContentProviders(ActivityThread.java:5445)在 android.app.ActivityThread.handleBindApplication(ActivityThread.java:5384)在 android.app.ActivityThread.-wrap2(ActivityThread.java)在 android.app.ActivityThread$H.handleMessage(ActivityThread.java:1545)在 android.os.Handler.dispatchMessage(Handler.java:102)在 android.os.Looper.loop(Looper.java:154)在 android.app.ActivityThread.main(ActivityThread.java:6119)在 java.lang.reflect.Method.invoke(Native Method)在 com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:886)在 com.android.internal.os.ZygoteInit.main(ZygoteInit.java:776)
使用调试器,我可以看到方法 FileProvider.parsePathStrategy
调用了 PackageManager.resolveContentProvider
带有权限字符串 "${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"
.resolveContentProvider
然后返回 null,导致这个 NPE.
如果我在此指令暂停时手动调用 resolveContentProvider
并传递 "${applicationId}.fileprovider"
或 "${applicationId}.bugshaker.fileprovider"
, resolveContentProvider
而是返回一个非空的 ProviderInfo
实例(这似乎是预期的结果).>
这种差异让我感到困惑,因为 元素文档 声明支持多个权限:
一个或多个 URI 授权列表,用于标识内容提供商提供的数据.多个权威通过用分号分隔它们的名称来列出.为避免冲突,权限名称应使用 Java 风格的命名约定(例如 com.example.provider.cartoonprovider).通常,它是实现提供者的 ContentProvider 子类的名称
没有默认值.必须至少指定一个权限.
问题
- 是否可以让单个应用程序公开具有多个权限和文件路径的
FileProvider
?- 如果是这样,我需要改变什么才能使其发挥作用?
- 如果没有,是否还有其他方法可以在我的库中配置文件共享来避免此类冲突?
我对这个问题的解决方案实际上是避免依赖单个 FileProvider
解析多个权限.虽然这并没有直接解决所述问题,但我将其发布以供后代使用.
我更新了我的库以利用 FileProvider
的一个空子类,以便库的更新清单提供程序条目现在是:
<元数据android:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/library_file_paths"/></提供者>
(1) 使用库存 FileProvider
和 (2) 使用我的库的应用程序的合并清单现在将包含下面显示的两个条目(无冲突!):
</提供者><提供者android:name="com.github.stkent.bugshaker.flow.email.screenshot.BugShakerFileProvider"android:authorities="com.consumer.application.bugshaker.fileprovider"机器人:出口=假"android:grantUriPermissions="true" ><元数据android:name="android.support.FILE_PROVIDER_PATHS"android:resource="@xml/library_file_paths"/></提供者>
直到一位同事指出,我才意识到这是一个潜在的解决方案.我之前的假设(并且错误地)是清单中的所有 FileProvider
都必须设置
android:name="android.support.v4.content.FileProvider"
但是快速检查文档发现了我的错误:
实现内容提供者的类的名称,是内容提供者的子类.这应该是一个完全限定的类名(例如,com.example.project.TransportationProvider").[...]
Background
I maintain a library whose core functionality involves sharing programmatically-captured screenshots to external email applications.
I use a FileProvider
to accomplish this, which means my library's manifest contains 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
is defined as follows:
<paths>
<files-path path="bug-reports/" name="bug-reports" />
</paths>
A consumer of my library has an application which itself uses a FileProvider
to share files. My expectation was that it should be possible to allow both providers to share files if the consuming application used the following manifest <provider>
tag:
<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>
This manifest entry:
- specifies two
Provider
authorities,${applicationId}.fileprovider
(for application file sharing) and${applicationId}.bugshaker.fileprovider
(for library file sharing); - references an updated
filepaths.xml
, which contains separate directory definitions for application-generated files and library-generated files:
<paths>
<external-path
name="redacted"
path="" />
<files-path
name="bug-reports"
path="bug-reports/" />
</paths>
After building the application, we have confirmed that the generated manifest has had the correct nodes replaced with these updated values.
However, when the application using this configuration is assembled (successfully) and run, we see a crash on launch:
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)
Using the debugger, I am able to see that the method FileProvider.parsePathStrategy
invokes PackageManager.resolveContentProvider
with the authority string "${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"
. resolveContentProvider
then returns null, leading to this NPE.
If I manually call resolveContentProvider
while paused at this instruction and pass either "${applicationId}.fileprovider"
or "${applicationId}.bugshaker.fileprovider"
, resolveContentProvider
instead returns a non-null ProviderInfo
instance (which would seem to be the expected result).
This difference confuses me because the <provider>
element documentation states that multiple authorities are supported:
Questions
- Is it possible to have a single application expose a
FileProvider
with multiple authorities and file paths?- If so, what do I need to change to make that work?
- If not, are there other ways to configure file sharing within my library that avoid conflicts such as this one?
My solution to this problem has actually been to avoid relying on a single FileProvider
parsing multiple authorities. While this doesn't directly address the question as stated, I'm posting it for posterity.
I updated my library to leverage an empty subclass of FileProvider
, so that the library's updated manifest provider entry is now:
<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>
The merged manifest of an application that (1) uses a stock FileProvider
and (2) consumes my library will now contain both of the entries shown below (no collision!):
<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>
I didn't realize that this was a potential solution until a coworker pointed it out. My assumption had previously (and incorrectly) been that all FileProvider
s in the manifest must set
android:name="android.support.v4.content.FileProvider"
but a quick check of the documentation revealed my error:
这篇关于可以对 FileProvider 使用多个权限吗?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持!