FileProvider error onhuawei devices

j'ai une exception qui se produit uniquement sur les périphériques Huawei dans mon application lorsque vous utilisez FileProvider.getUriForFile:

Exception: java.lang.IllegalArgumentException: Failed to find configured root that contains /storage/<card name>/Android/data/<app package>/files/.export/2016-10-06 13-22-33.pdf
   at android.support.v4.content.FileProvider$SimplePathStrategy.getUriForFile(SourceFile:711)
   at android.support.v4.content.FileProvider.getUriForFile(SourceFile:400)

Voici la définition de mon fournisseur de fichier dans mon manifest:

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileprovider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/file_provider_paths" />
</provider>

le fichier de ressources avec les chemins configurés:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="external_files" path="" />
</paths>

Aucune idée sur la cause de ce problème et pourquoi il ne se produit que sur Huawei appareils? Comment déboguer ce, étant donné que je n'ai pas de Huawei dispositif?

mise à jour:

j'ai ajouté plus de logs dans mon application et j'ai obtenu des résultats incohérents lors de l'impression des deux ContextCompat.getExternalFilesDirs et context.getExternalFilesDir sur ces appareils:

ContextCompat.getExternalFilesDirs:
/storage/emulated/0/Android/data/<package>/files
/storage/sdcard1/Android/data/<package>/files

context.getExternalFilesDir:
/storage/sdcard1/Android/data/<package>/files

ceci est incompatible avec la documentation de ContextCompat.getExternalFilesDirs qui indique que The first path returned is the same as getExternalFilesDir(String)

cela explique le problème puisque j'utilise context.getExternalFilesDir dans mon code et FileProviderContextCompat.getExternalFilesDirs.

22
demandé sur guillaume-tgl 2016-10-06 15:07:43

3 réponses

mise à jour pour Android N (laissant la réponse originale ci-dessous et ayant confirmé que cette nouvelle approche fonctionne en production):

comme vous l'avez noté dans votre mise à jour, de nombreux modèles de périphériques Huawei (par exemple KIW-L24, ALE-L21, ALE-L02, PLK-L01, et une variété d'autres) rompent le contrat Android pour les appels à ContextCompat#getExternalFilesDirs(String).
 Plutôt que de retourner Context#getExternalFilesDir(String) (c'est-à-dire l'entrée par défaut) comme premier objet dans le tableau, ils renvoient plutôt le premier objet comme chemin vers la carte SD externe, si l'on est présent.

en rompant ce contrat de commande, ces appareils Huawei avec des cartes SD externes vont s'écraser avec un IllegalArgumentException sur les appels à FileProvider#getUriForFile(Context, String, File)external-files-path racines. Bien qu'il existe une variété de solutions que vous pouvez poursuivre pour tenter de traiter cette question (par exemple, écrire une coutume FileProvider mise en oeuvre), j'ai trouvé que l'approche la plus facile est de saisir cette question et:

  • Pre - N: Return Uri#fromFile(File), qui ne fonctionnera pas avec Android N et au-dessus en raison de FileUriExposedException
  • n: Copiez le fichier vers votre cache-path (note: Ceci peut introduire des ANRs si c'est fait sur le Thread de L'interface utilisateur) et ensuite retourner FileProvider#getUriForFile(Context, String, File) pour le fichier copié (c'est à dire éviter le bug en tout)

Code pour accomplir ce qui peut être trouvé ci-dessous:

public class ContentUriProvider {

    private static final String HUAWEI_MANUFACTURER = "Huawei";

    public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
        if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER)) {
            Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device Increased likelihood of failure...");
            try {
                return FileProvider.getUriForFile(context, authority, file);
            } catch (IllegalArgumentException e) {
                if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
                    Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug for pre-N devices", e);
                    return Uri.fromFile(file);
                } else {
                    Log.w(ContentUriProvider.class.getSimpleName(), "ANR Risk -- Copying the file the location cache to avoid Huawei 'external-files-path' bug for N+ devices", e);
                    // Note: Periodically clear this cache
                    final File cacheFolder = new File(context.getCacheDir(), HUAWEI_MANUFACTURER);
                    final File cacheLocation = new File(cacheFolder, file.getName());
                    InputStream in = null;
                    OutputStream out = null;
                    try {
                        in = new FileInputStream(file);
                        out = new FileOutputStream(cacheLocation); // appending output stream
                        IOUtils.copy(in, out);
                        Log.i(ContentUriProvider.class.getSimpleName(), "Completed Android N+ Huawei file copy. Attempting to return the cached file");
                        return FileProvider.getUriForFile(context, authority, cacheLocation);
                    } catch (IOException e1) {
                        Log.e(ContentUriProvider.class.getSimpleName(), "Failed to copy the Huawei file. Re-throwing exception", e1);
                        throw new IllegalArgumentException("Huawei devices are unsupported for Android N", e1);
                    } finally {
                        IOUtils.closeQuietly(in);
                        IOUtils.closeQuietly(out);
                    }
                }
            }
        } else {
            return FileProvider.getUriForFile(context, authority, file);
        }
    }

}

et file_provider_paths.xml:

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-files-path name="public-files-path" path="." />
    <cache-path name="private-cache-path" path="." />
</paths>

une Fois que vous avez créé une classe comme cela, remplacez vos appels à:

FileProvider.getUriForFile(Context, String, File)

avec:

ContentUriProvider.getUriForFile(Context, String, File)

franchement, Je ne pensez que c'est une solution particulièrement gracieuse, mais il ne nous permet d'utiliser formellement le comportement documenté Android sans rien faire de trop drastique (par exemple écrire un personnalisé FileProvider mise en œuvre). Je l'ai testé en production, donc je peux confirmer qu'il résout ces accidents Huawei. Pour moi, c'était la meilleure approche, puisque je ne voulais pas passer trop de temps à aborder ce qui est de toute évidence un défaut du fabricant.

mise à jour d'avant les appareils Huawei avec ceci bug mis à jour pour Android N:

cela ne fonctionnera pas avec Android N et au-dessus en raison de FileUriExposedException, mais je n'ai pas encore rencontré D'appareil Huawei avec cette configuration erronée sur Android N.

public class ContentUriProvider {

    private static final String HUAWEI_MANUFACTURER = "Huawei";

    public static Uri getUriForFile(@NonNull Context context, @NonNull String authority, @NonNull File file) {
        if (HUAWEI_MANUFACTURER.equalsIgnoreCase(Build.MANUFACTURER) && Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
            Log.w(ContentUriProvider.class.getSimpleName(), "Using a Huawei device on pre-N. Increased likelihood of failure...");
            try {
                return FileProvider.getUriForFile(context, authority, file);
            } catch (IllegalArgumentException e) {
                Log.w(ContentUriProvider.class.getSimpleName(), "Returning Uri.fromFile to avoid Huawei 'external-files-path' bug", e);
                return Uri.fromFile(file);
            }
        } else {
            return FileProvider.getUriForFile(context, authority, file);
        }
    }
}
8
répondu wrb 2017-03-17 15:17:10

j'ai eu le même problème et ma solution est de toujours utiliser le ContextCompat.getExternalFilesDirs appel à construire l' File qui est utilisé comme paramètre pour FileProvider. De cette façon, vous n'avez pas à utiliser l'une des solutions ci-dessus.

En d'autres termes. Si vous avez le contrôle sur le File paramètre que vous utilisez pour appeler FileProvider et / ou vous ne vous souciez pas que le fichier pourrait finir par être sauvé en dehors de la classique /storage/emulated/0/Android/data/ dossier (qui devrait être parfaitement bien, car tout est juste la même carte SD) alors je suggère de faire ce que j'ai fait.

Si ce n'est pas votre cas, alors je vous suggérons d'utiliser la réponse ci-dessus avec custom getUriForFile mise en œuvre.

3
répondu simekadam 2017-04-19 13:55:37

Ma solution à ce problème dès maintenant, même si elle n'est pas parfaite, est de déclarer mes FileProvider avec le chemin suivant (pour pouvoir servir tous les fichiers sur le périphérique):

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <root-path name="root" path="" />
</paths>

ce n'est pas officiellement documenté et pourrait rompre avec une version future de la bibliothèque de Support v4 mais je ne vois pas d'autre solution pour servir un fichier dans le stockage externe secondaire (souvent la carte SD) en utilisant le .

2
répondu guillaume-tgl 2017-03-17 14:42:32