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:
Provider
, ${applicationId}.fileprovider
(pour le partage de fichiers d'application) et ${applicationId}.bugshaker.fileprovider
(pour le partage de fichiers de bibliothèque);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.
FileProvider
avec plusieurs autorités et chemins de fichiers? 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 FileProvider
s 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"). [...]
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>