Join FlipAndroid.COM Telegram Group: https://t.me/joinchat/F_aqThGkhwcLzmI49vKAiw


¿Cómo dividir los APK divididos creados mientras se usa instant-run, dentro del propio Android?

Fondo

Tengo una aplicación ( aquí ) que, entre otras características, permite compartir archivos APK.

Para ello, llega al archivo accediendo a la ruta de acceso de packageInfo.applicationInfo.sourceDir (docs link here ), y comparte el archivo (usando ContentProvider cuando sea necesario, como he usado aquí ).

El problema

Esto funciona bien en la mayoría de los casos, especialmente cuando se instalan archivos APK desde Play Store o desde un archivo APK independiente, pero cuando instalo una aplicación utilizando Android-Studio, veo varios archivos APK en esta ruta y ninguno de ellos Válidos que se pueden instalar y ejecutar sin ningún problema.

Aquí hay una captura de pantalla del contenido de esta carpeta, después de probar una muestra de "Alerter" github repo :

Introduzca aquí la descripción de la imagen

No estoy seguro de cuando este problema ha comenzado, pero sí ocurre al menos en mi Nexus 5x con Android 7.1.2. Tal vez incluso antes.

Lo que he encontrado

Esto parece ser causado sólo por el hecho de que la ejecución instantánea está habilitada en el IDE, por lo que podría ayudar a actualizar la aplicación sin la necesidad de reconstruir todos juntos:

Introduzca aquí la descripción de la imagen

Después de deshabilitarlo, puedo ver que hay un solo APK, como solía ser en el pasado:

Introduzca aquí la descripción de la imagen

Puede ver la diferencia en el tamaño del archivo entre el APK correcto y el dividido.

Además, parece que hay una API para obtener las rutas de acceso a todos los APKs splited:

Https://developer.android.com/reference/android/content/pm/ApplicationInfo.html#splitPublicSourceDirs

La pregunta

¿Cuál debe ser la forma más fácil de compartir un APK que tiene que ser dividido en múltiples?

¿Es realmente necesario para fusionarlos de alguna manera?

Parece que es posible según los documentos :

Caminos completos a cero o más APKs divididos que, cuando se combinan con el APK base definido en sourceDir, forman una aplicación completa.

¿Pero cuál es la manera correcta de hacerlo, y hay una manera rápida y eficiente de hacerlo? Tal vez sin realmente crear un archivo?

¿Existe quizá una API para obtener un APK combinado de todos los split? O tal vez un APK ya existe de alguna manera en algún otro camino, y no hay necesidad de la fusión?

EDIT: acaba de notar que todas las aplicaciones de terceros que he probado se supone que compartir el APK de una aplicación instalada no lo hacen en este caso.

  • ¿La instalación de Eclipse falló debido a un archivo APK no válido?
  • ¿Cuál es el tamaño óptimo para una aplicación para Android?
  • ¿Dónde está la ubicación .apk para las aplicaciones que se instalan en sdcard?
  • ¿Cómo pasar de APK agrietado a código java? Herramienta de 1 clic rompió mi aplicación
  • Número de línea de devolución de Android ProGuard
  • ¿Cómo firmar APK en Android Studio incluso con cadenas no traducidas?
  • Pruebe la compra inapp con la clave de depuración
  • ¿Por qué adb instala <same-packagename-always-release> falla?
  • 3 Solutions collect form web for “¿Cómo dividir los APK divididos creados mientras se usa instant-run, dentro del propio Android?”

    Soy el líder de tecnología @Google para el complemento de Android Gradle, déjame tratar de responder a su pregunta suponiendo que entiendo su caso de uso.

    En primer lugar, algunos usuarios mencionaron que no debes compartir tu compilación habilitada con InstantRun y ​​que son correctas. Las creaciones de Instant Run en una aplicación están altamente personalizadas para la imagen actual de dispositivo / emulador que está implementando. Por lo tanto, básicamente, digamos que generan una compilación habilitada por infrarrojos de su aplicación para un dispositivo en particular que ejecuta 21, que fallará miserablemente si intenta utilizar esos mismos APK exactos, por ejemplo, un dispositivo de ejecución 23. Puedo entrar en una explicación mucho más profunda si es necesario, Basta con decir que generamos códigos de bytes personalizados en las APIs encontradas en android.jar (que es por supuesto versión específica).

    Así que no creo que compartir esos APK tenga sentido, deberías usar una versión deshabilitada por IR o una versión de liberación.

    Ahora, para algunos detalles, cada tramo de APK contiene 1 + dex archivo (s), por lo que en teoría, nada le impide descomprimir todos los APKs rebanada, tomar todos los archivos dex y rellenarlos en el archivo base.apk / rezip / renunciar y Debería funcionar. Sin embargo, seguirá siendo una aplicación habilitada para IR por lo que se iniciará el pequeño servidor para escuchar las solicitudes IDE, etc, etc … No puedo imaginar una buena razón para hacer esto.

    Espero que esto ayude.

    Para combinar varios apks divididos con un solo apk puede ser un poco complicado.

    He aquí una sugerencia para compartir directamente los split apks y dejar que el sistema maneje la fusión y la instalación.

    Esto podría no ser una respuesta a la pregunta, ya que es un poco largo, que publicar aquí como una "respuesta".

    Marco nuevo API PackageInstaller puede manejar monolithic apk o split apk .

    En entorno de desarrollo

    • Para monolithic apk , usando adb install single_apk

    • Para split apk , usando adb install-multiple a_list_of_apks

    Usted puede ver estos dos modos anteriores de android studio Run salida depende de su proyecto tiene habilitado o desactivado Instant run .

    Para el comando adb install-multiple , podemos ver el código fuente aquí , llamará a la función install_multiple_app .

    Y luego realice los siguientes procedimientos

     pm install-create # create a install session pm install-write # write a list of apk to session pm install-commit # perform the merge and install 

    Lo que realmente hacen los pm es llamar al framework api PackageInstaller , podemos ver el código fuente aquí

     runInstallCreate runInstallWrite runInstallCommit 

    No es misterioso en absoluto, solo copié algunos métodos o funciono aquí.

    La siguiente secuencia de comandos se puede invocar desde el entorno de adb shell para instalar todos los split apks en el dispositivo, como adb install-multiple . Creo que podría funcionar de forma programática con Runtime.exec si el dispositivo está enraizado.

     #!/system/bin/sh # get the total size in byte total=0 for apk in *.apk do o=( $(ls -l $apk) ) let total=$total+${o[3]} done echo "pm install-create total size $total" create=$(pm install-create -S $total) sid=$(echo $create |grep -E -o '[0-9]+') echo "pm install-create session id $sid" for apk in *.apk do _ls_out=( $(ls -l $apk) ) echo "write $apk to $sid" cat $apk | pm install-write -S ${_ls_out[3]} $sid $apk - done pm install-commit $sid 

    Yo mi ejemplo, los apks split incluyen (tengo la lista de la salida de Run estudio de Android)

     app/build/output/app-debug.apk app/build/intermediates/split-apk/debug/dependencies.apk and all apks under app/build/intermediates/split-apk/debug/slices/slice[0-9].apk 

    Utilizando adb push todos los apks y el script anterior a un directorio público de escritura, eg /data/local/tmp/slices , y ejecute el script de instalación, se instalará en su dispositivo igual que adb install-multiple .

    El código a continuación es sólo otra variante de la secuencia de comandos anterior, si su aplicación tiene la firma de la plataforma o el dispositivo está enraizado, creo que va a estar bien. No tenía el ambiente para probar.

     private static void installMultipleCmd() { File[] apks = new File("/data/local/tmp/slices/slices").listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getAbsolutePath().endsWith(".apk"); } }); long total = 0; for (File apk : apks) { total += apk.length(); } Log.d(TAG, "installMultipleCmd: total apk size " + total); long sessionID = 0; try { Process pmInstallCreateProcess = Runtime.getRuntime().exec("/system/bin/sh\n"); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(pmInstallCreateProcess.getOutputStream())); writer.write("pm install-create\n"); writer.flush(); writer.close(); int ret = pmInstallCreateProcess.waitFor(); Log.d(TAG, "installMultipleCmd: pm install-create return " + ret); BufferedReader pmCreateReader = new BufferedReader(new InputStreamReader(pmInstallCreateProcess.getInputStream())); String l; Pattern sessionIDPattern = Pattern.compile(".*(\\[\\d+\\])"); while ((l = pmCreateReader.readLine()) != null) { Matcher matcher = sessionIDPattern.matcher(l); if (matcher.matches()) { sessionID = Long.parseLong(matcher.group(1)); } } Log.d(TAG, "installMultipleCmd: pm install-create sessionID " + sessionID); } catch (IOException | InterruptedException e) { e.printStackTrace(); } StringBuilder pmInstallWriteBuilder = new StringBuilder(); for (File apk : apks) { pmInstallWriteBuilder.append("cat " + apk.getAbsolutePath() + " | " + "pm install-write -S " + apk.length() + " " + sessionID + " " + apk.getName() + " -"); pmInstallWriteBuilder.append("\n"); } Log.d(TAG, "installMultipleCmd: will perform pm install write \n" + pmInstallWriteBuilder.toString()); try { Process pmInstallWriteProcess = Runtime.getRuntime().exec("/system/bin/sh\n"); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(pmInstallWriteProcess.getOutputStream())); // writer.write("pm\n"); writer.write(pmInstallWriteBuilder.toString()); writer.flush(); writer.close(); int ret = pmInstallWriteProcess.waitFor(); Log.d(TAG, "installMultipleCmd: pm install-write return " + ret); checkShouldShowError(ret, pmInstallWriteProcess); } catch (IOException | InterruptedException e) { e.printStackTrace(); } try { Process pmInstallCommitProcess = Runtime.getRuntime().exec("/system/bin/sh\n"); BufferedWriter writer = new BufferedWriter(new OutputStreamWriter(pmInstallCommitProcess.getOutputStream())); writer.write("pm install-commit " + sessionID); writer.flush(); writer.close(); int ret = pmInstallCommitProcess.waitFor(); Log.d(TAG, "installMultipleCmd: pm install-commit return " + ret); checkShouldShowError(ret, pmInstallCommitProcess); } catch (IOException | InterruptedException e) { e.printStackTrace(); } } private static void checkShouldShowError(int ret, Process process) { if (process != null && ret != 0) { BufferedReader reader = null; try { reader = new BufferedReader(new InputStreamReader(process.getErrorStream())); String l; while ((l = reader.readLine()) != null) { Log.d(TAG, "checkShouldShowError: " + l); } } catch (IOException e) { e.printStackTrace(); } finally { if (reader != null) { try { reader.close(); } catch (IOException e) { e.printStackTrace(); } } } } } 

    Mientras tanto, de la manera sencilla, puede probar el api marco. Al igual que el código de ejemplo anterior, podría funcionar si el dispositivo está enraizado o su aplicación tiene firma de plataforma, pero no obtuve un entorno viable para probarlo.

     private static void installMultiple(Context context) { if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.LOLLIPOP) { PackageInstaller packageInstaller = context.getPackageManager().getPackageInstaller(); PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL); try { final int sessionId = packageInstaller.createSession(sessionParams); Log.d(TAG, "installMultiple: sessionId " + sessionId); PackageInstaller.Session session = packageInstaller.openSession(sessionId); File[] apks = new File("/data/local/tmp/slices/slices").listFiles(new FileFilter() { @Override public boolean accept(File pathname) { return pathname.getAbsolutePath().endsWith(".apk"); } }); for (File apk : apks) { InputStream inputStream = new FileInputStream(apk); OutputStream outputStream = session.openWrite(apk.getName(), 0, apk.length()); byte[] buffer = new byte[65536]; int count; while ((count = inputStream.read(buffer)) != -1) { outputStream.write(buffer, 0, count); } session.fsync(outputStream); outputStream.close(); inputStream.close(); Log.d(TAG, "installMultiple: write file to session " + sessionId + " " + apk.length()); } try { IIntentSender target = new IIntentSender.Stub() { @Override public int send(int i, Intent intent, String s, IIntentReceiver iIntentReceiver, String s1) throws RemoteException { int status = intent.getIntExtra(PackageInstaller.EXTRA_STATUS, PackageInstaller.STATUS_FAILURE); Log.d(TAG, "send: status " + status); return 0; } }; session.commit(IntentSender.class.getConstructor(IIntentSender.class).newInstance(target)); } catch (InstantiationException | IllegalAccessException | NoSuchMethodException | InvocationTargetException e) { e.printStackTrace(); } session.close(); } catch (IOException e) { e.printStackTrace(); } } } 

    Para usar el api IIntentSender , agrego la biblioteca jar android-hidden-api como la dependencia provided .

    Para mí la carrera instantánea fue una pesadilla, 2-5 minutos de tiempo de construcción, y enloquecedor a menudo, los cambios recientes no se incluyeron en las compilaciones. Recomiendo deshabilitar la ejecución instantánea y agregar esta línea a gradle.properties:

     android.enableBuildCache=true 

    La primera compilación suele tardar algún tiempo en proyectos grandes (1-2 minutos), pero después de las caché, las compilaciones posteriores suelen ser ligeras (<10 seg).

    Tengo este consejo de usuario reddit / u / QuestionsEverythang que me ha salvado tanto hassling alrededor con ejecución instantánea!

    FlipAndroid es un fan de Google para Android, Todo sobre Android Phones, Android Wear, Android Dev y Aplicaciones para Android Aplicaciones.