53 votes

Est-il possible d'utiliser plusieurs autorités avec FileProvider ?

Contexte

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

J'utilise un FileProvider pour accomplir cela, ce qui signifie que le manifeste de ma bibliothèque contient a <provider> étiquette :

<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 possède une application qui utilise elle-même un fichier 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 le manifeste suivant <provider> étiquette :

<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 Provider autorités, ${applicationId}.fileprovider (pour le partage de fichiers d'application) et ${applicationId}.bugshaker.fileprovider (pour le partage des fichiers de la bibliothèque) ;
  • fait référence à une mise à jour filepaths.xml 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 avoir construit l'application, nous avons confirmé que les nœuds corrects du manifeste généré ont été remplacés par ces valeurs actualisées.

Cependant, lorsque l'application utilisant cette configuration est assemblée (avec succès) et exécutée, nous constatons un crash 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) 

En utilisant le débogueur, je suis capable de voir que la méthode FileProvider.parsePathStrategy invoque PackageManager.resolveContentProvider avec la chaîne d'autorité "${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider" . resolveContentProvider retourne alors null, ce qui conduit à ce NPE.

Si j'appelle manuellement resolveContentProvider tout en faisant une pause à cette instruction et passer soit "${applicationId}.fileprovider" o "${applicationId}.bugshaker.fileprovider" , resolveContentProvider renvoie plutôt un ProviderInfo (ce qui semble être le résultat attendu).

Cette différence me perturbe car le <provider> documentation des éléments indique que les autorités multiples sont supportées :

Une liste d'une ou plusieurs autorités URI qui identifient les données offertes par le fournisseur de contenu. Les autorités multiples sont listées en séparant leurs noms par un point-virgule. Pour éviter les conflits, les noms des autorités doivent utiliser une convention d'appellation de style Java (comme com.example.provider.cartoonprovider). En général, il s'agit du nom de la sous-classe ContentProvider qui met en œuvre le fournisseur.

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

Questions

  • Est-il possible d'avoir une application unique qui expose une FileProvider avec plusieurs autorités et chemins de fichiers ?
    • Si oui, que dois-je changer pour que cela fonctionne ?
    • Si ce n'est pas le cas, existe-t-il d'autres moyens de configurer le partage de fichiers au sein de ma bibliothèque qui permettent d'éviter des conflits tels que celui-ci ?

0 votes

"Je suis en mesure de voir que la méthode PackageItemInfo.loadXmlMetaData est invoquée avec la chaîne d'autorité "${applicationId}.fileprovider;${applicationId}.bugshaker.fileprovider" -- vous ne fournissez pas de chaîne d'autorité pour loadXmlMetaData() et je ne vois pas ça dans le FileProvider le code source. Une autorité est fournie pour resolveContentProvider() sur la ligne précédente. C'est ce que vous voulez dire ? Si c'est le cas, ProviderInfo fournit la liste délimitée par des points-virgules, et FileProvider ne semble pas gérer ça.

0 votes

Au-delà de ça, en regardant le code dans FileProvider Il semblerait qu'ils ne gèrent pas le scénario des autorités multiples. Ils ont des crochets pour avoir plusieurs stratégies de chemin par autorité, mais ils ne semblent jamais analyser la liste délimitée par des points-virgules. Probablement non testé. J'ai du code dans mon StreamProvider qui analyse la liste, mais je ne l'ai pas testé non plus :-(

0 votes

Ok, mise à jour. Il semble étrange que parsePathStrategy récupère un nouveau ProviderInfo du tout lorsqu'une instance est fournie à l attachInfo qui l'appelle. Je vois que ContentProvider ne divise les autorités en attachInfo et que FileProvider appelle super, mais le champ "Autorités multiples" dans le champ ContentProvider ne semble pas du tout être accessible aux sous-classes.

52voto

stkent Points 13089

La solution que j'ai trouvée à ce problème a été d'éviter de dépendre d'une seule et même personne. FileProvider l'analyse des autorités multiples. Bien que cela ne réponde pas directement à la question posée, je le publie pour la postérité.


J'ai mis à jour ma bibliothèque pour exploiter une sous-classe vide de FileProvider afin que l'entrée du fournisseur du manifeste de la bibliothèque soit maintenant mise à jour :

<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 un stock FileProvider et (2) consomme ma bibliothèque contiendra maintenant les deux entrées montré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'ai pas réalisé qu'il s'agissait d'une solution potentielle jusqu'à ce qu'un collègue de travail me le fasse remarquer. Auparavant, j'avais supposé (à tort) que tous les systèmes de gestion de l'information de l'entreprise étaient des systèmes de gestion de l'information. FileProvider dans le 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 :

Le nom de la classe qui implémente le fournisseur de contenu, une sous-classe de ContentProvider. Il doit s'agir d'un nom de classe entièrement qualifié (par exemple, "com.example.project.TransportationProvider"). [...]

3 votes

Comment utilisez-vous getUriForFile(Context, "com.my.authority.fileprovider", file); ? L'utilisez-vous dans votre bibliothèque ? Dans ce cas, comment générez-vous dynamiquement la chaîne d'autorité à partir du second paramètre de la fonction .getUriForFile() ? J'utilise le FileProvider uniquement dans mon projet de bibliothèque afin que la chaîne d'autorité soit définie de manière statique. getUriForFile(Context, "com.my.authority.fileprovider", file); mais il semble que je doive le générer dynamiquement.

1 votes

Si cela peut vous aider, voici le dépôt en question : github.com/stkent/bugshaker-Android . bugshaker module = bibliothèque, example module = application. Le fournisseur de ma bibliothèque n'est utilisé qu'à l'intérieur de celle-ci, et non par les applications qui la consomment. Il semble que ce soit différent de votre configuration ?

2 votes

1voto

Adnan Naeem Points 6

Je suis également confronté à ce problème et j'utilise cette approche pour le résoudre. Par exemple, j'ai une bibliothèque de sélection d'images qui utilise un fournisseur de fichiers et mon application utilise également un fournisseur de fichiers lorsque je crée mon conflit d'applications avec précision.

mon fichier fourni 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>

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

1 votes

Vous remplacez les autres fournisseurs de fichiers par le vôtre. Cela ne fonctionne correctement que si les autres ont enregistré leurs fichiers dans le même répertoire que le vôtre, sinon les autres librairies n'accèdent pas au fichier là où elles l'ont enregistré.

0 votes

Quelle est la meilleure façon d'implémenter ceci, je veux dire si la bibliothèque a un fournisseur de fichiers et que l'application a un fournisseur de fichiers, comment le gérer ?

Prograide.com

Prograide est une communauté de développeurs qui cherche à élargir la connaissance de la programmation au-delà de l'anglais.
Pour cela nous avons les plus grands doutes résolus en français et vous pouvez aussi poser vos propres questions ou résoudre celles des autres.

Powered by:

X