web-dev-qa-db-fra.com

Possible d'utiliser plusieurs autorités avec FileProvider?

Contexte

Je gère une bibliothèque dont la fonctionnalité principale consiste à partager des captures d'écran capturées par programme avec des applications de messagerie externes.

J'utilise une variable FileProvider pour accomplir cela, ce qui signifie que le manifeste de ma bibliothèque contient une balise <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 est défini comme suit:

<paths>
    <files-path path="bug-reports/" name="bug-reports" />
</paths>

Un consommateur de ma bibliothèque a une application qui utilise elle-même une FileProvider pour partager des fichiers. Je m'attendais à ce qu'il soit possible d'autoriser les deux fournisseurs à partager des fichiers si l'application consommatrice utilisait la balise <provider> du manifeste suivante:

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

Cette entrée manifeste:

  • spécifie deux autorités Provider, ${applicationId}.fileprovider (pour le partage de fichiers d'application) et ${applicationId}.bugshaker.fileprovider (pour le partage de fichiers de bibliothèque);
  • fait référence à un filepaths.xml mis à jour, qui contient des définitions de répertoire distinctes pour les fichiers générés par l'application et les fichiers générés par la bibliothèque:
<paths>
    <external-path
        name="redacted"
        path="" />
    <files-path
        name="bug-reports"
        path="bug-reports/" />
</paths>

Après la construction de l'application, nous avons confirmé que les fichiers appropriés ont été remplacés par les fichiers mis à jour par le manifeste généré.

Cependant, lorsque l'application utilisant cette configuration est assemblée (avec succès) et exécutée, un blocage se produit au lancement:

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) 

À l'aide du débogueur, je peux voir que la méthode FileProvider.parsePathStrategy appelle PackageManager.resolveContentProvider avec la chaîne d'autorité "${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider". resolveContentProvider renvoie alors null, conduisant à ce NPE.

Si j'appelle manuellement resolveContentProvider alors que je suis suspendu à cette instruction et passe soit "${applicationId}.fileprovider"ou"${applicationId}.bugshaker.fileprovider", resolveContentProvider renvoie à la place une instance non-null ProviderInfo (qui semble être le résultat attendu).

Cette différence me rend confus, car la <provider> element documentation indique que plusieurs autorités sont prises en charge:

Liste d'une ou plusieurs autorités URI identifiant les données proposées par le fournisseur de contenu. Plusieurs autorités sont répertoriées en séparant leurs noms par un point-virgule. Pour éviter les conflits, les noms d'autorité doivent utiliser une convention de dénomination de style Java (telle que com.example.provider.cartoonprovider). Il s’agit généralement du nom de la sous-classe ContentProvider qui implémente le fournisseur.

Il n'y a pas de défaut. Au moins une autorité doit être spécifiée.

Des questions

  • Est-il possible qu’une seule application expose une FileProvider avec plusieurs autorités et chemins de fichiers?
    • Si oui, que dois-je changer pour que cela fonctionne?
    • Sinon, y a-t-il d'autres moyens de configurer le partage de fichiers dans ma bibliothèque pour éviter des conflits tels que celui-ci?

Liens de soutien

27
stkent

Ma solution à ce problème a en fait consisté à éviter de compter sur un seul FileProvider pour analyser plusieurs autorités. Bien que cela ne réponde pas directement à la question énoncée, je la poste pour la postérité. 


J'ai mis à jour ma bibliothèque pour exploiter une sous-classe vide de FileProvider, de sorte que l'entrée de fournisseur de manifeste mise à jour de la bibliothèque est désormais:

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

Le manifeste fusionné d'une application qui (1) utilise une action FileProvider et (2) consomme ma bibliothèque contient désormais les deux entrées ci-dessous (pas de 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>

Je n'avais pas compris qu'il s'agissait d'une solution potentielle avant qu'un collègue ne l'ait signalé. Mon hypothèse avait déjà été (et à tort) que toutes les FileProviders du manifeste doivent définir

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

mais une vérification rapide de la documentation a révélé mon erreur:

Nom de la classe qui implémente le fournisseur de contenu, une sous-classe de ContentProvider. Ce doit être un nom de classe complet (tel que "com.example.project.TransportationProvider"). [...]

27
stkent

je suis également confronté à ce problème et utilise cette approche pour le résoudre . comme si j'avais un sélecteur d'images de bibliothèque qui utilisait un fournisseur de fichiers et que mon application utilisait un fournisseur de fichiers lorsque je construisais mon conflit d'application avec une précision.

mon dossier fournir est 

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

changez ceci en

<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