studio - hacer copia de seguridad android en pc
¿Cómo compartir archivos APK divididos creados al usar ejecución instantánea dentro de Android? (3)
Combinar varios archivos apk divididos en una única aplicación podría ser un poco complicado.
Aquí hay una sugerencia para compartir los apk divididos directamente y dejar que el sistema maneje la fusión y la instalación.
Esta podría no ser una respuesta a la pregunta, ya que es un poco larga, la publico aquí como una "respuesta".
Framework nuevo API PackageInstaller
puede manejar monolithic apk
o split apk
.
En ambiente de desarrollo
para
monolithic apk
, usandoadb install single_apk
para
split apk
, usandoadb install-multiple a_list_of_apks
Puede ver estos dos modos anteriores desde el estudio de Android La salida de Run
depende de si su proyecto tiene habilitado o deshabilitado la Instant run
.
Para el comando adb install-multiple
, podemos ver el código fuente here , 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 el pm
realmente hace es llamar al API api PackageInstaller
, podemos ver el código fuente here
runInstallCreate
runInstallWrite
runInstallCommit
No es misterioso en absoluto, acabo de copiar algunos métodos o funciones 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 programáticamente con Runtime.exec
si su dispositivo está rooteado.
#!/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 apk divididos incluyen (obtuve la lista de la salida Run
de android studio)
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
Usando adb push
inserte todos los apks y el script arriba en un directorio de escritura pública, ej. /data/local/tmp/slices
, y ejecute el script de instalación, se instalará en su dispositivo al igual que adb install-multiple
.
El siguiente código es solo otra variante del script anterior, si su aplicación tiene firma de plataforma o dispositivo rooteado, creo que estará bien. No tenía el entorno 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 más simple, puedes probar la aplicación API. Al igual que el código de ejemplo anterior, podría funcionar si el dispositivo está rooteado o si 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 utilizar la API oculta IIntentSender
, agrego la biblioteca de jar android-hidden-api como la dependencia provided
.
Fondo
Tengo una aplicación ( here ) que, entre otras características, permite compartir archivos APK.
Para hacerlo, llega al archivo accediendo a la ruta de packageInfo.applicationInfo.sourceDir (enlace de documentos here ), y solo comparte el archivo (utilizando ContentProvider cuando sea necesario, como he usado here ).
El problema
Esto funciona bien en la mayoría de los casos, especialmente al instalar archivos APK desde Play Store o desde un archivo APK independiente, pero cuando instalo una aplicación usando Android-Studio, veo varios archivos APK en esta ruta, y ninguno de ellos es 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 :
No estoy seguro de cuándo comenzó este problema, pero ocurre al menos en mi Nexus 5x con Android 7.1.2. Tal vez incluso antes.
Lo que he encontrado
Esto parece deberse solo al 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 volver a compilarla todos juntos:
Después de deshabilitarlo, puedo ver que hay una única APK, tal como solía ser en el pasado:
Puede ver la diferencia en el tamaño del archivo entre la APK correcta y la dividida.
Además, parece que hay una API para obtener las rutas a todos los APK divididos:
La pregunta
¿Cuál debería ser la forma más fácil de compartir una APK que se dividió en varias?
¿Es realmente necesario fusionarlos de alguna manera?
Parece que es posible según los documentos :
Las rutas completas a cero o más archivos APK divididos que, cuando se combinan con la base APK definida en sourceDir, forman una aplicación completa.
Pero, ¿cuál es la forma correcta de hacerlo, y existe una forma rápida y eficiente de hacerlo? Tal vez sin realmente crear un archivo?
¿Existe tal vez una API para obtener una APK combinada de todas las divididas? ¿O quizás tal APK ya existe de todos modos en algún otro camino, y no hay necesidad de fusionarse?
EDITAR: me acabo de dar cuenta de que todas las aplicaciones de terceros que he intentado compartir el APK de una aplicación instalada no lo hacen en este caso.
Para mí, la ejecución instantánea fue una pesadilla, con tiempos de construcción de 2 a 5 minutos, y enloquecentemente, 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 a menudo toma tiempo para proyectos grandes (1-2 minutos), pero después de que se almacena en caché, las compilaciones subsiguientes suelen ser de luz nocturna (<10 segundos).
¡Obtuve este consejo de reddit user / u / QuestionsEverythang que me ha ahorrado MUCHAS molestias con la ejecución instantánea!
Soy el líder tecnológico @Google para Android Gradle Plugin, déjame intentar responder a tu pregunta asumiendo que entiendo tu caso de uso.
En primer lugar, algunos usuarios mencionaron que no deben compartir su compilación habilitada para InstantRun y son correctos. Las compilaciones de Instant Run en una aplicación están altamente personalizadas para la imagen actual del dispositivo / emulador en la que se está implementando. Básicamente, digamos que generas una compilación activada por IR de tu aplicación para un dispositivo en particular con 21, fallará miserablemente si tratas de usar esos mismos APK exactos en un dispositivo con 23. Puedo entrar en una explicación mucho más profunda si es necesario pero Basta con decir que generamos códigos de bytes personalizados en las API que se encuentran en android.jar (que, por supuesto, es específica de la versión).
Por lo tanto, no creo que compartir los APK tenga sentido, ya sea que uses una compilación IR deshabilitada o una versión de lanzamiento.
Ahora, para algunos detalles, cada porción del archivo contiene 1x archivo (s) dex, por lo que, en teoría, nada le impide descomprimir todos esos archivos APK, tomar todos los archivos dex y volver a cargarlos en el archivo base.apk / rezip / resign y debería funcionar. Sin embargo, seguirá siendo una aplicación habilitada para IR, por lo que 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.