Error de FileProvider en los dispositivos Huawei
Tengo una excepción que sólo ocurre en los dispositivos Huawei en mi aplicación cuando se utiliza 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:
- Soporte de huellas dactilares Huawei P9 lite
- todos Huawei, Honor, Xiaomi en Marshmallow: Notificación icono pequeño siempre establecido en el icono de la aplicación
- La opción Android withText no funciona para los botones de barra de acción de Huawei MediaPad (T1-A21L)
- System.currentTimeMillis () devuelve una marca de tiempo incorrecta en Huawei
- Mi Huawei mata a mi aplicación wakelock android
<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é sucede sólo en los dispositivos Huawei? ¿Cómo depuraría esto, dado que no tengo un dispositivo Huawei?
ACTUALIZAR:
He añadido más registros a mi aplicación y tengo algunos resultados inconsistentes al imprimir ContextCompat.getExternalFilesDirs
y 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 inconsistente 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
utiliza ContextCompat.getExternalFilesDirs
.
- Java.lang.IllegalArgumentException: registra demasiados receptores de difusión
- Servicio de primer plano muerto en Huawei (GRA-UL00) - Aplicaciones protegidas habilitadas
- No se puede iniciar la aplicación Android debido al error LogCollectManager.getUserType ()
- OnTaskRemoved () no se llama en dispositivos HUAWEI y XIOMI
Actualización para Android N (dejando la respuesta original a continuación y han confirmado que este nuevo enfoque funciona en producción):
Como observó 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 el Context#getExternalFilesDir(String)
(es decir, la entrada predeterminada) como el primer objeto de la matriz, en su lugar devuelven el primer objeto como la ruta a la tarjeta SD externa, si está presente.
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 raíces de external-files-path
. Si bien hay una variedad de soluciones que puede perseguir para tratar de abordar este problema (por ejemplo, escribir una aplicación personalizada FileProvider
), he encontrado el método más fácil es coger este problema y:
- Pre-N: Devuelve
Uri#fromFile(File)
, que no funcionará con Android N y superior debido aFileUriExposedException
- N: Copie el archivo a su
cache-path
(nota: esto puede introducir ANRs si se hace en el UI Thread) y luego devolverFileProvider#getUriForFile(Context, String, File)
para el archivo copiado (es decir, evitar el bug por completo)
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 graciosa, pero sí nos permite usar un comportamiento formalmente documentado de Android sin hacer nada demasiado drástico (por ejemplo, escribir una implementación personalizada de FileProvider
). He probado esto en la producción, así que puedo confirmar que resuelve estos desplomes de Huawei. Para mí, este fue el mejor enfoque, ya que no quería pasar demasiado tiempo abordar lo que es, obviamente, un defecto del fabricante.
Actualizar desde antes de los dispositivos de Huawei con este error actualizado a Android N:
Esto no funcionará con Android N y superior debido a FileUriExposedException
, pero todavía tengo que encontrar un dispositivo Huawei con esta configuración errónea 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); } } }
Tuve el mismo problema y mi solución al final era siempre utilizar la llamada ContextCompat.getExternalFilesDirs
para construir el File
que se utiliza como parámetro para FileProvider
. De esta manera, no tiene que utilizar 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 pueda terminar siendo guardado fuera de la carpeta clásica /storage/emulated/0/Android/data/
(que debería ser Perfectamente bien, ya que es todo lo mismo la tarjeta SD), entonces sugiero hacer lo que he hecho.
Si no es su caso, entonces sugiero utilizar la respuesta anterior con la implementación personalizada getUriForFile
.
Mi solución a este problema en este momento, incluso si no es perfecto, 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 podría romper con una futura versión de la biblioteca de soporte v4, pero no puedo ver ninguna otra solución para servir un archivo en el almacenamiento externo secundario (a menudo la tarjeta SD) utilizando el FileProvider
existente.