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:
- ¿Qué es el error INSTALL_PARSE_FAILED_NO_CERTIFICATES?
- ¿Cómo solucionarlo? El código de la versión de su APK debe ser superior a 2. "en la Consola para desarrolladores de Google Play.
- Lea el nombre del paquete de un APK Android
- Diferencia entre depug y release apks
- Cómo zipalign el archivo apk en Windows
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
-
¿Por qué ocurre?
-
¿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?
-
¿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 …
-
¿La biblioteca de soporte FileProvider será suficiente para todos los casos de uso (dado que tengo permiso de almacenamiento externo, por supuesto)?
- Archivo de expansión de Android
- ¿Cuál es la diferencia entre los archivos apklib y jar?
- Android instala apk sin pedir permiso de usuario
- Phonegap modo no debug y firma con clave de licencia
- Cómo convertir el archivo jar en apk?
- Cómo firmar mi archivo de aplicación / .apk de android ya hecho
- ¿Qué causa este error de carga de APK de Android: "APK no actualizable"
- Desinstalación de APK: una última acción antes de desinstalar
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.
- ¿Cómo puedo ocultar mi clave de API de Google Maps al compartir mi código en github?
- ¿Reacciona imágenes remotas de caché nativo?