unresolved tutorial taking support studio intent fileuriexposedexception fileprovider contentprovider content android android-fileprovider huawei

android - tutorial - Error FileProvider en dispositivos Huawei



photo android studio (3)

Tengo una excepción que ocurre solo en dispositivos Huawei en mi aplicación cuando uso 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)

Aquí está la definición de mi proveedor de archivos en mi manifiesto:

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

El archivo de recursos con rutas configuradas:

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

¿Alguna idea sobre la causa de este problema y por qué ocurre solo en los dispositivos Huawei? ¿Cómo puedo depurar esto, dado que no tengo un dispositivo Huawei?

ACTUALIZAR:

He agregado más registros a mi aplicación y obtuve algunos resultados inconsistentes al imprimir tanto ContextCompat.getExternalFilesDirs como context.getExternalFilesDir en estos dispositivos:

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

Esto es incoherente con la documentación de ContextCompat.getExternalFilesDirs que indica que The first path returned is the same as getExternalFilesDir(String)

Eso explica el problema ya que uso context.getExternalFilesDir en mi código y FileProvider usa ContextCompat.getExternalFilesDirs .


Mi solución a este problema ahora mismo, incluso si no es perfecta, es declarar mi FileProvider con la siguiente ruta (para poder servir todos los archivos en el dispositivo):

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

Esto no está documentado oficialmente y puede fallar con una versión futura de la biblioteca de soporte de v4, pero no puedo ver ninguna otra solución para servir un archivo en el almacenamiento externo secundario (a menudo la tarjeta SD) usando el FileProvider existente.


Tuve el mismo problema y mi solución al final fue usar siempre la llamada ContextCompat.getExternalFilesDirs para compilar el File que se usa como parámetro para FileProvider . De esa manera, no tiene que usar ninguna de las soluciones anteriores.

En otras palabras. Si tiene control sobre el parámetro File que utiliza para llamar a FileProvider y / o no le importa que el archivo termine FileProvider fuera de la carpeta classic /storage/emulated/0/Android/data/ (que debería estar guardada). perfectamente bien, ya que es la misma tarjeta SD), entonces sugiero hacer lo que he hecho.

Si no es su caso, sugiero utilizar la respuesta anterior con la implementación personalizada de getUriForFile .


Actualización para Android N (dejando la respuesta original a continuación y he confirmado que este nuevo enfoque funciona en producción):

Como notó en su actualización, muchos modelos de dispositivos Huawei (por ejemplo, KIW-L24, ALE-L21, ALE-L02, PLK-L01 y una variedad de otros) rompen el contrato de Android para llamadas a ContextCompat#getExternalFilesDirs(String) . En lugar de devolver Context#getExternalFilesDir(String) (es decir, la entrada predeterminada) como el primer objeto de la matriz, devuelven el primer objeto como la ruta a la tarjeta SD externa, si hay uno.

Al romper este contrato de pedido, estos dispositivos Huawei con tarjetas SD externas se bloquearán con una IllegalArgumentException en las llamadas a FileProvider#getUriForFile(Context, String, File) para external-files-path raíces de external-files-path . Si bien hay una variedad de soluciones que puede buscar para tratar este problema (p. Ej., Escribir una implementación personalizada de FileProvider ), he encontrado que el enfoque más sencillo es detectar este problema y:

  • Pre-N: devuelve Uri#fromFile(File) , que no funcionará con Android N y superior debido a FileUriExposedException
  • N: Copie el archivo a su cache-path (nota: esto puede introducir ANR si se realiza en el subproceso de la interfaz de usuario) y luego devuelva el FileProvider#getUriForFile(Context, String, File) para el archivo copiado (es decir, evitando el error por completo)

El código para lograr esto se puede encontrar a continuació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)) { 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); } } }

Junto con el 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>

Una vez que haya creado una clase como esta, reemplace sus llamadas a:

FileProvider.getUriForFile(Context, String, File)

con:

ContentUriProvider.getUriForFile(Context, String, File)

Francamente, no creo que esta sea una solución especialmente elegante, pero nos permite usar el comportamiento de Android documentado formalmente sin hacer nada demasiado drástico (por ejemplo, escribir una implementación personalizada de FileProvider ). He probado esto en producción, así que puedo confirmar que resuelve estos bloqueos de Huawei. Para mí, este fue el mejor enfoque, ya que no deseaba dedicar demasiado tiempo a abordar lo que obviamente es un defecto del fabricante.

Actualice desde dispositivos anteriores de Huawei con este error actualizado a Android N:

Esto no funcionará con Android N y superior debido a FileUriExposedException , pero aún no he encontrado un dispositivo Huawei con esta configuración FileUriExposedException en 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); } } }