web-dev-qa-db-ja.com

FileProviderで複数の権限を使用できますか?

バックグラウンド

library を維持します。その中核機能は、プログラムでキャプチャされたスクリーンショットを外部の電子メールアプリケーションと共有することです。

これを実現するためにFileProviderを使用します。つまり、ライブラリのマニフェストには a <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>

このマニフェストエントリ:

  • 2つの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が権限文字列PackageManager.resolveContentProvider"${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider" を呼び出すことがわかります。 resolveContentProviderはnullを返し、このNPEにつながります。

この命令で一時停止中に手動でresolveContentProviderを呼び出し、"${applicationId}.fileprovider"or"${applicationId}.bugshaker.fileprovider"resolveContentProviderは代わりに、null以外のProviderInfoインスタンスを返します(これは予想される結果のようです)。

<provider>要素ドキュメント は複数の権限がサポートされていることを示しているため、この違いは私を混乱させます:

コンテンツプロバイダーが提供するデータを識別する1つ以上のURI機関のリスト。複数の機関は、名前をセミコロンで区切ってリストされます。競合を避けるために、機関名はJavaスタイルの命名規則(com.example.provider.cartoonproviderなど)を使用する必要があります。通常、プロバイダーを実装するContentProviderサブクラスの名前です

デフォルトはありません。少なくとも1つの権限を指定する必要があります。

ご質問

  • 単一のアプリケーションで複数の権限とファイルパスを持つFileProviderを公開することは可能ですか?
    • もしそうなら、それを機能させるために何を変更する必要がありますか?
    • そうでない場合、このような競合を回避するために、ライブラリ内でファイル共有を構成する他の方法はありますか?
32
stkent

この問題に対する私の解決策は、実際には、複数の権限を解析する単一の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"

しかし、 ドキュメント の簡単なチェックは私のエラーを明らかにしました:

ContentProviderのサブクラスであるコンテンツプロバイダーを実装するクラスの名前。これは完全修飾クラス名(「com.example.project.TransportationProvider」など)である必要があります。 [...]

33
stkent

私もこの問題に直面し、このアプローチを使用して解決します。ファイルプロバイダーを使用するライブラリイメージピッカーがあり、アプリの競合が発生したときにアプリがファイルプロバイダーを使用するようにします。

私のファイルは

<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>
0
Adnan Naeem