Cómo abrir un archivo APK para todas las versiones de Android

Fondo

Hasta ahora, había una manera fácil de instalar un archivo APK, usando esta intención:

final Intent intent=new Intent(Intent.ACTION_VIEW) .setDataAndType(Uri.fromFile(apkFile), "application/vnd.android.package-archive"); 

Sin embargo, si su aplicación se orienta a Android API 24 y superior (Nougat – 7.0) y ejecuta este código en él o más reciente, obtendrá una excepción, como se muestra aquí , por ejemplo:

 android.os.FileUriExposedException: file:///storage/emulated/0/sample.apk exposed beyond app through Intent.getData() 

El problema

Así que hice lo que me dijeron: usar la clase de FileProvider de la biblioteca de soporte, como tal:

  final Intent intent = new Intent(Intent.ACTION_VIEW)// .setDataAndType(android.support.v4.content.FileProvider.getUriForFile(context, context.getPackageName() + ".provider", apkFile), "application/vnd.android.package-archive").addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 

Manifiesto

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

Res / xml / provider_paths.xml :

 <?xml version="1.0" encoding="utf-8"?> <paths> <!--<external-path name="external_files" path="."/>--> <external-path name="files_root" path="Android/data/${applicationId}"/> <external-path name="external_storage_root" path="."/> </paths> 

Pero, ahora funciona sólo en Android Nougat. En Android 5.0, lanza una excepción: ActivityNotFoundException.

Lo que he intentado

Sólo puedo añadir un cheque para la versión de Android OS, y utilizar cualquiera de los métodos, pero como he leído, debe haber un único método para usar: FileProvider.

Por lo tanto, lo que he intentado es utilizar mi propio ContentProvider que actúa como FileProvider, pero tengo la misma excepción que de la biblioteca de soporte FileProvider.

Aquí está mi código para ello:

  final Intent intent = new Intent(Intent.ACTION_VIEW) .setDataAndType(OpenFileProvider.prepareSingleFileProviderFile(apkFilePath), "application/vnd.android.package-archive") .addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION); 

OpenFileProvider.java

 public class OpenFileProvider extends ContentProvider { private static final String FILE_PROVIDER_AUTHORITY = "open_file_provider"; private static final String[] DEFAULT_PROJECTION = new String[]{MediaColumns.DATA, MediaColumns.DISPLAY_NAME, MediaColumns.SIZE}; public static Uri prepareSingleFileProviderFile(String filePath) { final String encodedFilePath = new String(Base64.encode(filePath.getBytes(), Base64.URL_SAFE)); final Uri uri = Uri.parse("content://" + FILE_PROVIDER_AUTHORITY + "/" + encodedFilePath); return uri; } @Override public boolean onCreate() { return true; } @Override public String getType(@NonNull Uri uri) { String fileName = getFileName(uri); if (fileName == null) return null; return MimeTypeMap.getSingleton().getMimeTypeFromExtension(fileName); } @Override public ParcelFileDescriptor openFile(@NonNull Uri uri, @NonNull String mode) throws FileNotFoundException { final String fileName = getFileName(uri); if (fileName == null) return null; final File file = new File(fileName); return ParcelFileDescriptor.open(file, ParcelFileDescriptor.MODE_READ_ONLY); } @Override public Cursor query(@NonNull Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) { final String filePath = getFileName(uri); if (filePath == null) return null; final String[] columnNames = (projection == null) ? DEFAULT_PROJECTION : projection; final MatrixCursor ret = new MatrixCursor(columnNames); final Object[] values = new Object[columnNames.length]; for (int i = 0, count = columnNames.length; i < count; ++i) { String column = columnNames[i]; switch (column) { case MediaColumns.DATA: values[i] = uri.toString(); break; case MediaColumns.DISPLAY_NAME: values[i] = extractFileName(uri); break; case MediaColumns.SIZE: File file = new File(filePath); values[i] = file.length(); break; } } ret.addRow(values); return ret; } private static String getFileName(Uri uri) { String path = uri.getLastPathSegment(); return path != null ? new String(Base64.decode(path, Base64.URL_SAFE)) : null; } private static String extractFileName(Uri uri) { String path = getFileName(uri); return path; } @Override public int update(@NonNull Uri uri, ContentValues values, String selection, String[] selectionArgs) { return 0; // not supported } @Override public int delete(@NonNull Uri uri, String arg1, String[] arg2) { return 0; // not supported } @Override public Uri insert(@NonNull Uri uri, ContentValues values) { return null; // not supported } } 

manifiesto

  <provider android:name=".utils.apps_utils.OpenFileProvider" android:authorities="open_file_provider" android:exported="true" android:grantUriPermissions="true" android:multiprocess="true"/> 

Las preguntas

  1. ¿Por qué ocurre?

  2. ¿Hay algún problema con el proveedor personalizado que he creado? ¿Es necesaria la bandera? ¿Está bien la creación de URI? ¿Debería añadir el nombre del paquete de la aplicación actual?

  3. ¿Debería agregar un cheque si es Android API 24 y superior, y si es así, usa el proveedor, y si no, usa una llamada Uri.fromFile normal? Si uso esto, la biblioteca de soporte pierde realmente su propósito, porque se utilizará para versiones más recientes de Android …

  4. ¿La biblioteca de soporte FileProvider será suficiente para todos los casos de uso (dado que tengo permiso de almacenamiento externo, por supuesto)?

Sólo puedo añadir un cheque para la versión de Android OS, y utilizar cualquiera de los métodos, pero como he leído, debe haber un único método para usar: FileProvider.

Bueno, como dice el refrán, "se necesitan dos para bailar".

Para utilizar cualquier esquema en particular ( file , content , http , etc.), no sólo tiene que proporcionar los datos en ese esquema, sino que el destinatario debe ser capaz de admitir la aceptación de los datos en ese esquema.

En el caso del instalador de paquetes, el soporte para el content como un esquema sólo se agregó en Android 7.0 (y, a continuación, tal vez sólo porque me señaló el problema ).

¿Por qué ocurre?

Porque Google (ver esto y esto ).

¿Hay algún problema con el proveedor personalizado que he creado?

Probablemente no.

¿Debería agregar un cheque si es Android API 24 y superior, y si es así, usa el proveedor, y si no, usa una llamada Uri.fromFile normal?

Sí. O, si lo prefieres, captura la ActivityNotFoundException y reacciona a eso, o usa PackageManager y resolveActivity() para ver antes de tiempo si una Intent dada (por ejemplo, una con un Uri content ) funcionará correctamente.

Si uso esto, la biblioteca de soporte pierde realmente su propósito, porque se utilizará para versiones más recientes de Android

La "biblioteca de soporte" tiene poco que ver con las versiones más recientes de Android. Sólo un pequeño porcentaje de las clases en los distintos artefactos de soporte de Android son backports o shims de compatibilidad. Vastas cantidades de ella – FileProvider , ViewPager , ConstraintLayout , etc. – son simplemente clases que Google quería proporcionar y dar soporte, pero quería hacerlas disponibles fuera del firmware.

La biblioteca de soporte FileProvider será suficiente para todos los casos de uso

Sólo en Android 7.0+. Una vez más, el instalador de paquetes Android no admite esquemas de content anteriores a Android 7.0.

  • Donde el archivo .apk se coloca en el teléfono después de la instalación
  • Su APK no parece estar diseñado para tablets
  • Cómo modificar / editar el archivo de manifiesto o el icono de la aplicación después de la instalación utilizando otra aplicación o tiempo de ejecución obteniendo respuesta de api
  • Diferencia entre app-debug.apk y app-debug-unaligned.apk
  • Xamarin implementación de Android no utiliza el código más reciente?
  • La actualización de la aplicación pierde todos los datos como ruta Código / Resouce para los cambios de pkg
  • Cómo firmar un APK ya compilado
  • Android compara la firma del paquete actual con debug.keystore
  • No puede firmar el APK sin firmar
  • ¿Qué intenciones tiene la aplicación de Facebook para Android?
  • Android Studio: Error en la instalación de la aplicación
  • FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.