4 Pregunta: ¿Es posible fusionar / instalar archivos APK divididos (también conocido como "paquete de aplicaciones"), en el propio dispositivo Android, sin root?

pregunta creada en Sun, Mar 17, 2019 12:00 AM

Fondo

En el pasado, he preguntado acerca de compartir o hacer una copia de seguridad de los archivos apk de app-bundle /split, here .

Esto parece una tarea casi imposible, en la que solo pude averiguar cómo instalar los archivos APK divididos, y aún así solo es a través de adb:

adb install-multiple apk1 apk2 ...

El problema

Me dijeron que debería ser posible fusionar múltiples archivos APK divididos en uno que pudiera instalar ( aquí ), pero no se dio a conocer cómo hacerlo.

Esto podría ser útil para guardarlo para más tarde (copia de seguridad), y porque actualmente no hay manera de instalar archivos apk de división en el dispositivo.

De hecho, este es un problema tan importante, que no conozco ninguna aplicación de copia de seguridad que pueda manejar archivos de APK divididos (paquete de aplicaciones), y esto incluye la aplicación Titanium.

Lo que he encontrado

Tomé una aplicación de muestra que usa paquetes de aplicaciones, llamada "AirBnb".

Mirando los archivos que tiene, eso es lo que Play Store decidió descargar:

 ingrese la descripción de la imagen aquí

Así que traté de entrar cada uno. La "base" es la principal, así que la salté para mirar a los demás. A mí me parece que todos tienen estos archivos dentro de:

  • "META-INF"
  • "resources.arsc"
  • "AndroidManifest.xml"
  • en el caso del que tiene "xxxhdpi", también obtengo la carpeta "res".

Lo que pasa es que, dado que todos existen en varios lugares, no entiendo cómo podría fusionarlos.

Las preguntas

  1. ¿Cuál es la forma de combinarlas todas en un archivo APK?

  2. ¿Es posible instalar archivos APK divididos sin root y sin PC? Esto fue posible en el pasado en aplicaciones de copia de seguridad como Titanium, pero solo en archivos APK normales, y no en el paquete de aplicaciones (apk dividido).


EDITAR: He establecido una recompensa. Por favor, si conoces alguna solución, demuéstralo. Muestra algo que hayas probado para que funcione. Ya sea combinando archivos APK divididos, o instalándolos, todos sin root y directamente en el dispositivo.


EDITAR: Lamentablemente todas las soluciones aquí no funcionaron, con o sin root, y eso es a pesar de que he encontrado una aplicación que logró hacerlo (con y sin root), llamada "SAI (Split APKs Installer)" ( Creo que su repositorio es aquí , que se encuentra después de haber puesto una recompensa ).

Estoy poniendo una nueva recompensa. Por favor, quienquiera que publique una nueva respuesta, muestre que funciona con y sin root. Muestra en Github si es necesario (y aquí solo las cosas importantes). Sé que esta aplicación es de código abierto de todos modos, pero es importante para mí cómo hacerlo aquí y compartirla con los demás, ya que actualmente lo que se muestra aquí no funciona y requiere root, aunque no es realmente necesario.

Esta vez no otorgaré la recompensa hasta que vea algo que realmente funciona (anteriormente, no tenía mucho tiempo y le concedí la respuesta que pensé que debería funcionar).

    
7
  1. "Compartir" las aplicaciones de Play Store no suenan como algo para lo que estás autorizado , y existen soluciones a nivel de plataforma para realizar copias de seguridad.
    2019-03-17 23: 24: 08Z
  2. ¿Qué quiere decir con instalar APKs desde el dispositivo? El PackageManager seguramente lo permite.
    2019-03-18 00: 35: 28Z
  3. @ Pierre No lo hace. Ya he intentado instalar cada uno de esos archivos APK. La instalación de la base funciona, pero el resto no será permitido. Además, existe la posibilidad de que la aplicación instalada no funcione correctamente (debido a la falta de recursos)
    2019-03-18 12: 52: 35Z
  4. @ ChrisStratton ¿Por qué sería un problema para mí compartir mi copia de seguridad de la aplicación en Google Drive? Lo que escribiste no tiene sentido. He comprado /descargado la aplicación, por lo que debería poder volver a hacerlo en el futuro, con o sin conexión a Internet. Además, esta es una pregunta técnica. Muchas aplicaciones de copia de seguridad ahora no pueden hacer una copia de seguridad de los paquetes de aplicaciones debido a este problema.
    2019-03-18 12: 56: 35Z
  5. Los APK no deben instalarse a través de múltiples llamadas de instalación. Deben instalarse en una sola sesión. Mire el PackageInstaller API , incluso hay una sección en el javadoc sobre APKs divididos.
    2019-03-18 18: 41: 04Z
4 Respuestas                              4                         

No se requiere implementación de root Compruebe este enlace de git hub: https://github.com/nkalra0123/splitapkinstall

Tenemos que crear un servicio y pasar ese identificador en session.commit ()

 Intent callbackIntent = new Intent(getApplicationContext(), APKInstallService.class);
 PendingIntent pendingIntent = PendingIntent.getService(getApplicationContext(), 0, callbackIntent, 0);
 session.commit(pendingIntent.getIntentSender());

EDITAR: Ya que la solución funciona, pero no se publica realmente aquí, he decidido escribirla antes de marcarla como la solución correcta. Aquí está el código:

manifest

<manifest package="com.nitin.apkinstaller" xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools">
  <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
  <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES"/>
  <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
  <application
    android:allowBackup="true" android:icon="@mipmap/ic_launcher" android:label="@string/app_name" android:roundIcon="@mipmap/ic_launcher_round" android:supportsRtl="true"
    android:theme="@style/AppTheme" tools:ignore="AllowBackup,GoogleAppIndexingWarning">
    <activity
      android:name=".MainActivity" android:label="@string/app_name" android:theme="@style/AppTheme.NoActionBar">
      <intent-filter>
        <action android:name="android.intent.action.MAIN"/>

        <category android:name="android.intent.category.LAUNCHER"/>
      </intent-filter>
    </activity>

    <service android:name=".APKInstallService"/>
  </application>
</manifest>

APKInstallService

class APKInstallService : Service() {
    override fun onStartCommand(intent: Intent, flags: Int, startId: Int): Int {
        when (if (intent.hasExtra(PackageInstaller.EXTRA_STATUS)) null else intent.getIntExtra(PackageInstaller.EXTRA_STATUS, 0)) {
            PackageInstaller.STATUS_PENDING_USER_ACTION -> {
                Log.d("AppLog", "Requesting user confirmation for installation")
                val confirmationIntent = intent.getParcelableExtra<Intent>(Intent.EXTRA_INTENT)
                confirmationIntent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
                try {
                    startActivity(confirmationIntent)
                } catch (e: Exception) {
                }
            }
            PackageInstaller.STATUS_SUCCESS -> Log.d("AppLog", "Installation succeed")
            else -> Log.d("AppLog", "Installation failed")
        }
        stopSelf()
        return START_NOT_STICKY
    }

    override fun onBind(intent: Intent): IBinder? {
        return null
    }
}

MainActivity

class MainActivity : AppCompatActivity() {
    private lateinit var packageInstaller: PackageInstaller

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        val toolbar = findViewById<Toolbar>(R.id.toolbar)
        setSupportActionBar(toolbar)
        val fab = findViewById<FloatingActionButton>(R.id.fab)
        fab.setOnClickListener {
            packageInstaller = packageManager.packageInstaller
            val ret = installApk("/storage/emulated/0/Download/split/")
            Log.d("AppLog", "onClick: return value is $ret")
        }

    }

    private fun installApk(apkFolderPath: String): Int {
        val nameSizeMap = HashMap<String, Long>()
        var totalSize: Long = 0
        var sessionId = 0
        val folder = File(apkFolderPath)
        val listOfFiles = folder.listFiles()
        try {
            for (listOfFile in listOfFiles) {
                if (listOfFile.isFile) {
                    Log.d("AppLog", "installApk: " + listOfFile.name)
                    nameSizeMap[listOfFile.name] = listOfFile.length()
                    totalSize += listOfFile.length()
                }
            }
        } catch (e: Exception) {
            e.printStackTrace()
            return -1
        }
        val installParams = PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL)
        installParams.setSize(totalSize)
        try {
            sessionId = packageInstaller.createSession(installParams)
            Log.d("AppLog","Success: created install session [$sessionId]")
            for ((key, value) in nameSizeMap) {
                doWriteSession(sessionId, apkFolderPath + key, value, key)
            }
            doCommitSession(sessionId)
            Log.d("AppLog","Success")
        } catch (e: IOException) {
            e.printStackTrace()
        }
        return sessionId
    }

    private fun doWriteSession(sessionId: Int, inPath: String?, sizeBytes: Long, splitName: String): Int {
        var inPathToUse = inPath
        var sizeBytesToUse = sizeBytes
        if ("-" == inPathToUse) {
            inPathToUse = null
        } else if (inPathToUse != null) {
            val file = File(inPathToUse)
            if (file.isFile)
                sizeBytesToUse = file.length()
        }
        var session: PackageInstaller.Session? = null
        var inputStream: InputStream? = null
        var out: OutputStream? = null
        try {
            session = packageInstaller.openSession(sessionId)
            if (inPathToUse != null) {
                inputStream = FileInputStream(inPathToUse)
            }
            out = session!!.openWrite(splitName, 0, sizeBytesToUse)
            var total = 0
            val buffer = ByteArray(65536)
            var c: Int
            while (true) {
                c = inputStream!!.read(buffer)
                if (c == -1)
                    break
                total += c
                out!!.write(buffer, 0, c)
            }
            session.fsync(out!!)
            Log.d("AppLog", "Success: streamed $total bytes")
            return PackageInstaller.STATUS_SUCCESS
        } catch (e: IOException) {
            Log.e("AppLog", "Error: failed to write; " + e.message)
            return PackageInstaller.STATUS_FAILURE
        } finally {
            try {
                out?.close()
                inputStream?.close()
                session?.close()
            } catch (e: IOException) {
                e.printStackTrace()
            }
        }
    }

    private fun doCommitSession(sessionId: Int) {
        var session: PackageInstaller.Session? = null
        try {
            try {
                session = packageInstaller.openSession(sessionId)
                val callbackIntent = Intent(applicationContext, APKInstallService::class.java)
                val pendingIntent = PendingIntent.getService(applicationContext, 0, callbackIntent, 0)
                session!!.commit(pendingIntent.intentSender)
                session.close()
                Log.d("AppLog", "install request sent")
                Log.d("AppLog", "doCommitSession: " + packageInstaller.mySessions)
                Log.d("AppLog", "doCommitSession: after session commit ")
            } catch (e: IOException) {
                e.printStackTrace()
            }

        } finally {
            session!!.close()
        }
    }
}
    
0
2019-04-20 09: 49: 38Z
  1. ¿Dónde está el código relevante aquí? Todo lo que veo es una Intención ... ¿Dónde se manejan los archivos? ¿Y cómo puede hacerse usando root (la pregunta fue con y sin root)?
    2019-04-14 06: 46: 54Z
  2. Abra el enlace github, ya que tiene todo el código relevante para el enfoque sin root. Ya te he dado el código para el enfoque de raíz. Funcionó para mí.
    2019-04-14 11: 10: 05Z
  3. ¿Por qué publicaste como una nueva respuesta? De todos modos, publique el que tiene root.
    2019-04-14 14: 19: 52Z
  4. OK He actualizado su respuesta con el código, ya que funciona bien, pero el método rooteado todavía no funciona para mí. ¿Puedes publicarlo también en Github? Le he otorgado la recompensa por ambas soluciones, pero las soluciones enraizadas no funcionan ...
    2019-04-20 09: 50: 34Z
  5. Gracias, la solución Rooted funcionó para mí, lo probé en un emulador rooteado, lo probaré en un teléfono rooteado. Le actualizaré, si encuentro algo, ¿Ha intentado depurar, en qué etapa está fallando? Hay tres etapas primero: crear una sesión "instalar-crear" y luego, escribir apks divididos a sesión con "instalar-escribir" y luego confirmando la sesión con "install-commit". Para verificar el primer paso, verifique si obtiene un ID de sesión válido o ejecutando "adb shell dumpsys package" y busque "Sesiones de instalación histórica:" o "Sesiones de instalación activa:"
    2019-04-20 15: 42: 42Z

Por favor, compruebe esto. cuando enviamos

adb install-multiple apk1 apk2 ...

llama a este código instalación múltiple

 std::string install_cmd;
    if (_use_legacy_install()) {
        install_cmd = "exec:pm";
    } else {
        install_cmd = "exec:cmd package";
    }

    std::string cmd = android::base::StringPrintf("%s install-create -S %" PRIu64, install_cmd.c_str(), total_size);
    for (i = 1; i < first_apk; i++) {
        cmd += " " + escape_arg(argv[i]);
    }

que a su vez llama a Pm.java o una nueva forma de ejecutar el código de servicio PackageManager, ambos son similares

Intenté integrar ese código en mi aplicación. El problema al que me enfrenté, la instalación de apk no se pudo completar, se debe a la razón por la que la aplicación necesita.

<uses-permission android:name="android.permission.INSTALL_PACKAGES"/>

Pero solo se da a las aplicaciones de sistema priv. Cuando ejecuté estos pasos desde adb shell, la instalación de apk fue exitosa y cuando creé mi aplicación, la instalación de la aplicación priv-app de sistema fue exitosa.

Código

para llamar a nuevas API de PackageManager, en su mayoría copiadas de Pm.java Pasos para instalar apks divididos

  1. Cree una sesión con el argumento -S, devuelva el ID de sesión.

    (instalar-crear, -S, 52488426) 52488426 - tamaño total de los apks.

  2. Escriba apks divididos en esa sesión con tamaño, nombre y ruta

    (instalación-escritura, -S, 44334187, 824704264, 1_base.apk, -)

    (instalación-escritura, -S, 1262034, 824704264, 2_split_config.en.apk, -)

    (instalación-escritura, -S, 266117, 824704264, 3_split_config.hdpi.apk, -)

    (instalación-escritura, -S, 6626088, 824704264, 4_split_config.x86.apk, -)

  3. cometer la sesión con el ID de sesión

    (install-commit, 824704264)

He colocado el apk airbnb en mi sdcard.

OnePlus5:/sdcard/com.airbnb.android-1 $ ll
total 51264
-rw-rw---- 1 root sdcard_rw 44334187 2019-04-01 14:20 base.apk
-rw-rw---- 1 root sdcard_rw  1262034 2019-04-01 14:20 split_config.en.apk
-rw-rw---- 1 root sdcard_rw   266117 2019-04-01 14:20 split_config.hdpi.apk
-rw-rw---- 1 root sdcard_rw  6626088 2019-04-01 14:20 split_config.x86.apk

y llamando a las funciones para instalar apk.

final InstallParams installParams = makeInstallParams(52488426l);

            try {
                int sessionId = runInstallCreate(installParams);

                runInstallWrite(44334187,sessionId, "1_base.apk", "/sdcard/com.airbnb.android-1/base.apk");

                runInstallWrite(1262034,sessionId, "2_split_config.en.apk", "/sdcard/com.airbnb.android-1/split_config.en.apk");

                runInstallWrite(266117,sessionId, "3_split_config.hdpi.apk", "/sdcard/com.airbnb.android-1/split_config.hdpi.apk");

                runInstallWrite(6626088,sessionId, "4_split_config.x86.apk", "/sdcard/com.airbnb.android-1/split_config.x86.apk");


                if (doCommitSession(sessionId, false )
                        != PackageInstaller.STATUS_SUCCESS) {
                }
                System.out.println("Success");

            } catch (RemoteException e) {
                e.printStackTrace();
            }

private int runInstallCreate(InstallParams installParams) throws RemoteException {
    final int sessionId = doCreateSession(installParams.sessionParams);
    System.out.println("Success: created install session [" + sessionId + "]");
    return sessionId;
}

private int doCreateSession(PackageInstaller.SessionParams params)
        throws RemoteException {

    int sessionId = 0 ;
    try {
        sessionId = packageInstaller.createSession(params);
    } catch (IOException e) {
        e.printStackTrace();
    }
    return sessionId;
}

private int runInstallWrite(long size, int sessionId , String splitName ,String path ) throws RemoteException {
    long sizeBytes = -1;

    String opt;
    sizeBytes = size;
    return doWriteSession(sessionId, path, sizeBytes, splitName, true /*logSuccess*/);
}


private int doWriteSession(int sessionId, String inPath, long sizeBytes, String splitName,
                           boolean logSuccess) throws RemoteException {
    if ("-".equals(inPath)) {
        inPath = null;
    } else if (inPath != null) {
        final File file = new File(inPath);
        if (file.isFile()) {
            sizeBytes = file.length();
        }
    }

    final PackageInstaller.SessionInfo info = packageInstaller.getSessionInfo(sessionId);

    PackageInstaller.Session session = null;
    InputStream in = null;
    OutputStream out = null;
    try {
        session = packageInstaller.openSession(sessionId);

        if (inPath != null) {
            in = new FileInputStream(inPath);
        }

        out = session.openWrite(splitName, 0, sizeBytes);

        int total = 0;
        byte[] buffer = new byte[65536];
        int c;
        while ((c = in.read(buffer)) != -1) {
            total += c;
            out.write(buffer, 0, c);
        }
        session.fsync(out);

        if (logSuccess) {
            System.out.println("Success: streamed " + total + " bytes");
        }
        return PackageInstaller.STATUS_SUCCESS;
    } catch (IOException e) {
        System.err.println("Error: failed to write; " + e.getMessage());
        return PackageInstaller.STATUS_FAILURE;
    } finally {
        try {
            out.close();
            in.close();
            session.close();
        } catch (IOException e) {
            e.printStackTrace();
        }

    }
}


private int doCommitSession(int sessionId, boolean logSuccess) throws RemoteException {
    PackageInstaller.Session session = null;
    try {
        try {
            session = packageInstaller.openSession(sessionId);
        } catch (IOException e) {
            e.printStackTrace();
        }
        session.commit(PendingIntent.getBroadcast(getApplicationContext(), sessionId,
                new Intent("android.intent.action.MAIN"), 0).getIntentSender());
        System.out.println("install request sent");

        Log.d(TAG, "doCommitSession: " + packageInstaller.getMySessions());

        Log.d(TAG, "doCommitSession: after session commit ");

        return 1;
    } finally {
        session.close();
    }
}



private static class InstallParams {
    PackageInstaller.SessionParams sessionParams;
}

private InstallParams makeInstallParams(long totalSize ) {
    final PackageInstaller.SessionParams sessionParams = new PackageInstaller.SessionParams(PackageInstaller.SessionParams.MODE_FULL_INSTALL);
    final InstallParams params = new InstallParams();
    params.sessionParams = sessionParams;
    String opt;
    sessionParams.setSize(totalSize);
    return params;
}

Esta es la lista de comandos que realmente se reciben en Pm.java cuando hacemos adb install-multiple

04-01 16:04:40.626  4886  4886 D Pm      : run() called with: args = [[install-create, -S, 52488426]]
04-01 16:04:41.862  4897  4897 D Pm      : run() called with: args = [[install-write, -S, 44334187, 824704264, 1_base.apk, -]]
04-01 16:04:56.036  4912  4912 D Pm      : run() called with: args = [[install-write, -S, 1262034, 824704264, 2_split_config.en.apk, -]]
04-01 16:04:57.584  4924  4924 D Pm      : run() called with: args = [[install-write, -S, 266117, 824704264, 3_split_config.hdpi.apk, -]]
04-01 16:04:58.842  4936  4936 D Pm      : run() called with: args = [[install-write, -S, 6626088, 824704264, 4_split_config.x86.apk, -]]
04-01 16:05:01.304  4948  4948 D Pm      : run() called with: args = [[install-commit, 824704264]]

Entonces, para las aplicaciones que no son del sistema priv-app, no sé cómo pueden instalar apks divididos. Play Store es una aplicación privada del sistema que puede utilizar estas API e instalar apks divididos sin ningún problema.

    
1
2019-04-01 13: 36: 29Z
  1. ¿Entonces no hay una forma oficial de instalar apks divididos sin ser una aplicación del sistema, pero está bien instalar una normal? ¿Realmente funcionó para ti solo al ser una aplicación de sistema? ¿Cómo convertiste la aplicación en una aplicación de sistema y volviste a ser una aplicación de usuario? ¿Qué pasa si tengo root? ¿Eso ayudaría a instalar los split-apks?
    2019-04-01 13: 54: 07Z
  2. no hay una forma oficial de instalar apks divididos sin ser una aplicación del sistema --- No estoy seguro, se actualizará si encuentro una manera. ¿Realmente funcionó para ti solo al ser una aplicación de sistema? --- Sí, después de instalar como /system /priv-app. ¿Cómo convirtió la aplicación en una aplicación de sistema? --- Coloque la aplicación en /system /priv-app e instálela desde esta ubicación ¿Qué sucede si tengo root? ¿Eso ayudaría a instalar los split-apks? - Sí, básicamente la aplicación necesita android.permission.INSTALL_PACKAGES, si eres root puedes otorgar este permiso, o puedes ejecutar comandos pm desde Runtime.exec con usuario root
    2019-04-02 06: 50: 02Z
  3. ¿Puede mostrar cómo convertir una aplicación (incluida la aplicación actualmente instalada) en una aplicación del sistema y volver a la aplicación de usuario (usando la raíz)? Si tengo root, ¿no necesito tener la aplicación para ser una aplicación del sistema? ¿Puedes mostrar qué ejecutar en caso de que tenga root? Por ahora, porque estás mostrando esfuerzo, marco esto como la respuesta correcta. Otorgará la recompensa cuando todas estas soluciones sean claras y comprensibles.
    2019-04-02 12: 14: 13Z

Desde un paquete de aplicaciones de Android, puede generar un "APK universal" utilizando bundletool comando build-apks con el indicador --mode=universal. Esto generará un único APK "gordo" que es compatible con todos los dispositivos (que tu aplicación admite).

Sé que esto no es responder estrictamente a tu pregunta, pero intentar fusionar los APK no solo es una tarea compleja, sino que resultará en muchos casos en algo incorrecto.

    
0
2019-03-18 00: 33: 05Z
  1. ¿Puede mostrarnos cómo hacerlo dentro del propio dispositivo (es decir, sin PC)? ¿Cuál es el código al que debo llamar? Además, ¿tiene alguna otra solución posible para la copia de seguridad de los archivos APK divididos que le permitirá restaurarlos también, sin root y sin PC? Es solo que fue posible con un solo APK en el pasado, pero ahora no, y muchas aplicaciones de copia de seguridad (si no todas) ahora fallan en la copia de seguridad de estas aplicaciones debido a esto (incluida la aplicación Titanium Backup).
    2019-03-18 12: 54: 56Z
  2. Ya es posible. Ver mi otro comentario sobre la API PackageInstaller.
    2019-03-18 18: 41: 23Z
  3. Si es posible, muestre cómo hacerlo, con un ejemplo práctico, y lo aceptaré. Si la fusión no es posible, esto es mejor que nada.
    2019-03-19 08: 05: 36Z

Si tienes root, puedes usar este código.

Obtenga el permiso de lectura /escritura sdcard (a través de los permisos de tiempo de ejecución o el permiso otorgado por la aplicación de configuración) antes de ejecutar este código. El apk de airbnb se instaló correctamente después de ejecutar este código.

Al llamar a esta función con args "/split-apks /", he colocado los apks divididos de airbnb en un directorio en /sdcard /split-apks /.

installApk("/split-apks/");


 public void installApk(String apkFolderPath)
{
    PackageInstaller packageInstaller =  getPackageManager().getPackageInstaller();
    HashMap<String, Long> nameSizeMap = new HashMap<>();
    long totalSize = 0;

    File folder = new File(Environment.getExternalStorageDirectory().getPath()+ apkFolderPath);
    File[] listOfFiles = folder.listFiles();
    for (int i = 0; i < listOfFiles.length; i++) {
        if (listOfFiles[i].isFile()) {
            System.out.println("File " + listOfFiles[i].getName());
            nameSizeMap.put(listOfFiles[i].getName(),listOfFiles[i].length());
            totalSize += listOfFiles[i].length();
        }
    }

    String su = "/system/xbin/su";


    final String[] pm_install_create = new String[]{su, "-c", "pm" ,"install-create", "-S", Long.toString(totalSize) };
    execute(null, pm_install_create);

    List<PackageInstaller.SessionInfo> sessions = packageInstaller.getAllSessions();

    int sessId = sessions.get(0).getSessionId();

    String sessionId = Integer.toString(sessId);


    for(Map.Entry<String,Long> entry : nameSizeMap.entrySet())
    {
        String[] pm_install_write = new String[]{su, "-c", "pm" ,"install-write", "-S", Long.toString(entry.getValue()),sessionId, entry.getKey(), Environment.getExternalStorageDirectory().getPath()+apkFolderPath+ entry.getKey()};

        execute(null,pm_install_write);

    }

    String[] pm_install_commit  = new String[]{su, "-c", "pm" ,"install-commit", sessionId};


    execute(null, pm_install_commit);

}
public String execute(Map<String, String> environvenmentVars, String[] cmd) {

    boolean DEBUG = true;
    if (DEBUG)
        Log.d("log","command is " + Arrays.toString(cmd));

    try {
        Process process = Runtime.getRuntime().exec(cmd);
        if (DEBUG)
            Log.d("log", "process is " + process);

        BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()));
        if (DEBUG)
            Log.d("log", "bufferreader is " + reader);

        if (DEBUG)
            Log.d("log", "readline " + reader.readLine());
        StringBuffer output = new StringBuffer();

        char[] buffer = new char[4096];
        int read;

        while ((read = reader.read(buffer)) > 0) {
            output.append(buffer, 0, read);
        }

        reader.close();

        process.waitFor();
        if (DEBUG)
            Log.d("log", output.toString());

        return output.toString();

    }

    catch (Exception e)
    {
        e.printStackTrace();
    }

    return null;

}

EDITAR: el mismo código, pero en Kotlin, ya que es más corto:

uso de muestra:

Foo.installApk(context,fullPathToSplitApksFolder)

Ejemplo:

        AsyncTask.execute {
            Foo.installApk(this@MainActivity,"/storage/emulated/0/Download/split")
        }

Código:

object Foo {
    @WorkerThread
    @JvmStatic
    fun installApk(context: Context, apkFolderPath: String) {
        val packageInstaller = context.packageManager.packageInstaller
        val nameSizeMap = HashMap<File, Long>()
        var totalSize: Long = 0
        val folder = File(apkFolderPath)
        val listOfFiles = folder.listFiles().filter { it.isFile && it.name.endsWith(".apk") }
        for (file in listOfFiles) {
            Log.d("AppLog", "File " + file.name)
            nameSizeMap[file] = file.length()
            totalSize += file.length()
        }
        val su = "su"
        val pmInstallCreate = arrayOf(su, "-c", "pm", "install-create", "-S", totalSize.toString())
        execute(pmInstallCreate)
        val sessions = packageInstaller.allSessions
        val sessionId = Integer.toString(sessions[0].sessionId)
        for ((file, value) in nameSizeMap) {
            val pmInstallWrite = arrayOf(su, "-c", "pm", "install-write", "-S", value.toString(), sessionId, file.name, file.absolutePath)
            execute(pmInstallWrite)
        }
        val pmInstallCommit = arrayOf(su, "-c", "pm", "install-commit", sessionId)
        execute(pmInstallCommit)
    }

    @WorkerThread
    @JvmStatic
    private fun execute(cmd: Array<String>): String? {
        Log.d("AppLog", "command is " + Arrays.toString(cmd))
        try {
            val process = Runtime.getRuntime().exec(cmd)
            Log.d("AppLog", "process is $process")
            val reader = BufferedReader(InputStreamReader(process.inputStream))
            Log.d("AppLog", "bufferreader is $reader")
            Log.d("AppLog", "readline " + reader.readLine())
            val output = StringBuilder()
            val buffer = CharArray(4096)
            var read: Int
            while (true) {
                read = reader.read(buffer)
                if (read <= 0)
                    break
                output.append(buffer, 0, read)
            }
            reader.close()
            process.waitFor()
            Log.d("AppLog", output.toString())
            return output.toString()
        } catch (e: Exception) {
            e.printStackTrace()
        }
        return null
    }
}
    
0
2019-04-13 20: 37: 05Z
  1. ¿Cuáles son los distintos números? ¿Cómo supiste qué poner para ellos? Estoy hablando de: "52488426", "44334187", "1262034", "266117", "6626088". Y, ¿cuáles son las cadenas son "sessiongId"? ¿Cómo pensaste en qué poner para ellos? Estoy hablando de "1_base.apk", "2_split_config.en.apk", "3_split_config.hdpi.apk", "4_split_config.x86.apk".
    2019-04-03 06: 39: 51Z
  2. He descargado la versión x86 de la aplicación airbnb, tiene 4 partes, 1. base.apk de tamaño 44334187 bytes, 2. split_config.en.apk de tamaño 1262034 bytes 3. split_config.hdpi.apk de tamaño 266117 bytes, 4. split_config.x86.apk de tamaño 6626088 bytes y 52488426 es la suma de todos estos (tamaño total) Así que tienes que encontrar los tamaños de todas las divisiones y agregar el comando Básicamente, le está diciendo al sistema que desea crear una sesión en la que necesita < suma del total de todas las divisiones &gt ;, luego con instalación-escritura para escribir < bytes de < ruta (/sdcard /.../base.apk) > a < nombre (1_base.apk) >
    2019-04-03 07: 35: 53Z
  3. Necesitas el ID de sesión que creaste con "pm", "install-create", "-S", "52488426" String sessiongId = Integer.toString (sessionId) ; es solo la representación en cadena de sessionId que acabamos de crear, se requiere que se pase en los siguientes comandos de instalación-escritura y instalación-confirmación
    2019-04-03 07: 36: 51Z
  4. @ androiddeveloper, Por favor, compruebe ahora, he eliminado toda la codificación de tamaños y nombres ahora. Simplemente pase el nombre de directorio correcto a la función installApk
    2019-04-03 14: 04: 07Z
  5. Ok, lo haré el fin de semana.
    2019-04-04 08: 11: 50Z
fuente colocada aquí