可以在FileProvider中使用多个权限吗?

stk*_*ent 34 android android-manifest android-contentprovider manifest-merging

背景

我维护一个库,其核心功能包括以编程方式捕获的屏幕截图与外部电子邮件应用程序共享.

我用a 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>
Run Code Online (Sandbox Code Playgroud)

filepaths.xml 定义如下:

<paths>
    <files-path path="bug-reports/" name="bug-reports" />
</paths>
Run Code Online (Sandbox Code Playgroud)

我的库的消费者有一个应用程序,它本身使用a 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>
Run Code Online (Sandbox Code Playgroud)

这个清单条目:

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

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

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

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) 
Run Code Online (Sandbox Code Playgroud)

使用调试器,我能够看到该方法使用权限字符串进行FileProvider.parsePathStrategy调用.然后返回null,导致这个NPE.PackageManager.resolveContentProvider"${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider"resolveContentProvider

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

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

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

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

问题

  • 是否可以让单个应用程序公开FileProvider具有多个权限和文件路径?
    • 如果是这样,我需要做些什么来改变它?
    • 如果没有,是否有其他方法可以在我的库中配置文件共享以避免这样的冲突?

支持链接

stk*_*ent 34

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

(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>
Run Code Online (Sandbox Code Playgroud)

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

android:name="android.support.v4.content.FileProvider"
Run Code Online (Sandbox Code Playgroud)

但快速检查文档显示我的错误:

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

  • 您如何使用`getUriForFile(Context,“ com.my.authority.fileprovider”,file);`?您是否在库中使用它,如果是的话,如何从.getUriForFile()中的第二个参数动态生成授权字符串?我仅在我的图书馆项目中使用`FileProvider`,所以我拥有静态定义的授权字符串`getUriForFile(Context,“ com.my.authority.fileprovider”,file);`,但似乎我可能需要生成它动态地。 (2认同)
  • 让我们[在聊天中继续讨论](http://chat.stackoverflow.com/rooms/150244/discussion-between-stkent-and-sakiboy). (2认同)
  • 如果有人没有参加聊天,您可以通过为库版本设置唯一后缀来避免权限路径冲突。并调用`getUriForFile(context, applicationContext.getPackageName() + LIB_SUFFIX, file);` (2认同)