是否可以在没有根目录的Android设备本身上合并/安装拆分的APK文件(又称为“应用程序捆绑包")? [英] Is it possible to merge/install split APK files (AKA "app bundle"), on Android device itself, without root?

查看:109
本文介绍了是否可以在没有根目录的Android设备本身上合并/安装拆分的APK文件(又称为“应用程序捆绑包")?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

背景

过去,我曾问过有关共享或备份应用程序捆绑包/拆分apk文件的问题, 此处 ),但未给出具体操作方法.

这对于将其保存以备后用(备份)很有用,因为当前无法在设备中安装split-apk文件.

实际上,这是一个重大问题,我不知道有任何备份应用程序可以处理拆分的APK文件(应用程序捆绑包),其中包括Titanium应用程序.

我发现的东西

我拍摄了一个使用应用程序捆绑包的示例应用程序,称为"AirBnb".

从Play商店决定下载的文件来看:

所以我尝试输入每个. 基础"是主要的基础,因此我略过了其他基础. 在我看来,所有这些文件都包含在以下文件中:

  • "META-INF"
  • "resources.arsc"
  • "AndroidManifest.xml"
  • 如果是带有"xxxhdpi"的文件夹,我也会得到"res"文件夹.

问题是,由于这些都存在于多个地方,所以我不知道如何合并它们.

问题

  1. 将这些全部合并为一个APK文件的方式是什么?

  2. 是否可以在没有root用户且没有PC的情况下安装拆分的APK文件?过去在备份应用程序(例如Titanium)上可以做到这一点,但只能在普通的APK文件上使用,而不能在应用程序捆绑包(拆分式APK)上使用.


我设置了赏金.请,如果您知道解决方案,请显示它.显示您经过测试可以正常工作的内容.要么合并拆分的APK文件,要么安装它们,而无需在设备上直接创建root权限.


遗憾的是,无论有无root用户,这里的所有解决方案都无法使用,即使我找到了一个成功完成该应用程序(有root用户和没有root用户)的应用程序,它也称为"SAI(Split APKs Installer)"(我认为它的存储库是 此处 ,这是在我悬赏后发现的) .

我要增加新的赏金.请发布新答案的人表明,无论有无root用户都可以使用该答案.如果需要,请在Github上显示(这里只是重要内容).我知道这个应用程序无论如何都是开源的,但是对我来说重要的是如何在这里做它,并与他人共享,因为目前这里显示的内容是行不通的,即使不是真正需要它,它也需要root用户.

这一次,我不会赏金,直到我看到确实有用的东西(以前我的时间很短,并且把它授予了我认为应该起作用的答案).

解决方案

请选中此项. 当我们发送

adb install-multiple apk1 apk2 ...

它调用此代码 多次安装

 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]);
    }

依次调用Pm.java或执行PackageManagerService代码的新方式,两者都是相似的

我试图将代码集成到我的应用程序中,我所面临的问题是apk安装无法完成,这是由于该应用程序需要的原因.

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

但是它仅提供给系统专用的应用程序.当我从adb shell执行apk时成功安装了这些步骤,并且在创建我的应用程序时,系统priv-app apk安装成功了.

代码以调用PackageManager的新api,通常是从Pm.java复制而来 安装拆分式APK的步骤

  1. 使用-S参数创建会话,返回会话ID.

    (install-create,-S,52488426) 52488426-APK的总大小.

  2. 在该会话中使用大小,名称和路径编写拆分的APK

    (install-write,-S,44334187,824704264,1_base.apk,-)

    (install-write,-S,1262034,824704264,2_split_config.en.apk,-)

    (install-write,-S,266117,824704264,3_split_config.hdpi.apk,-)

    (install-write,-S,6626088,824704264,4_split_config.x86.apk,-)

  3. 使用会话ID提交会话

    (install-commit,824704264)

我已将airbnb apk放置在我的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

并调用函数来安装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;
}

这是我们执行adb install-multiple时Pm.java中实际收到的命令的列表.

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]]

因此对于不是系统priv-app的应用程序,我不知道它们如何安装拆分式APK. 作为系统私人应用程序的Play商店可以使用这些API并安装拆分的APK,而不会出现任何问题.

Background

In the past, I've asked about sharing or backup of app-bundle / split apk files, here .

This seems like an almost impossible task, which I could only figure out how to install the split APK files, and even then it's only via adb:

adb install-multiple apk1 apk2 ...

The problem

I was told that it should be actually possible to merge multiple split APK files into one that I could install (here), but wasn't given of how to do it.

This could be useful for saving it for later (backup), and because currently there is no way to install split-apk files within the device.

In fact, this is such a major issue, that I don't know of any backup app that can handle split APK files (app bundle), and this include Titanium app.

What I've found

I took a sample app that uses app-bundles, called "AirBnb".

Looking at the files it has, those are what the Play Store decided to download:

So I tried to enter each. The "base" is the main one, so I skipped it to look at the others. To me it seems that all have these files within:

  • "META-INF"
  • "resources.arsc"
  • "AndroidManifest.xml"
  • in the case of the one with the "xxxhdpi", I also get "res" folder.

Thing is, since those all exist in multiple places, I don't get how could I merge them.

The questions

  1. What is the way to merge those all into one APK file?

  2. Is it possible to install split APK files without root and without PC ? This was possible in the past on backup apps such as Titanium, but only on normal APK files, and not app bundle (split apk).


EDIT: I've set a bounty. Please, if you know of a solution, show it. Show something that you've tested to work. Either of merging split APK files, or installing them , all without root and right on the device.


EDIT: Sadly all solutions here didn't work, with or without root, and that's even though I've found an app that succeeded doing it (with and without root), called "SAI (Split APKs Installer)" (I think its repository is here, found after I've put a bounty).

I'm putting a new bounty. Please, whoever publishes a new answer, show that it works with and without root. Show on Github if needed (and here just the important stuff). I know this app is open sourced anyway, but it's important for me how to do it here, and share with others, as currently what's shown here isn't working, and requires root, even though it's not really needed.

This time I won't grant the bounty till I see something that indeed works (previously I was short on time and granted it to the answer I thought should work).

解决方案

Please check this. when we send

adb install-multiple apk1 apk2 ...

it calls this code install-multiple

 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]);
    }

which in turn calls Pm.java or a new way of executing PackageManagerService code, both are similar

I tried to integrate that code in my app, The problem which I faced, apk installation was not able to complete, it is due to the reason that the app needs.

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

But it is only given to system-priv apps. When I executed these steps from adb shell apk installation was successful and when I created my app a system priv-app apk install was successfull.

code to call new apis of PackageManager, mostly copied from Pm.java Steps in installing split apks

  1. Create a session with argument -S , return session id.

    (install-create, -S, 52488426) 52488426 -- total size of apks.

  2. Write split apks in that session with size , name and path

    (install-write, -S, 44334187, 824704264, 1_base.apk, -)

    (install-write, -S, 1262034, 824704264, 2_split_config.en.apk, -)

    (install-write, -S, 266117, 824704264, 3_split_config.hdpi.apk, -)

    (install-write, -S, 6626088, 824704264, 4_split_config.x86.apk, -)

  3. commit the session with session id

    (install-commit, 824704264)

I have placed airbnb apk in my 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

and calling functions to install 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;
}

This is the list of commands that are actually received in Pm.java when we do 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]]

So for apps which are not system priv-app, I don't know how can they can install split apks. Play store being a system priv-app can use these apis and install split apks without any issues.

这篇关于是否可以在没有根目录的Android设备本身上合并/安装拆分的APK文件(又称为“应用程序捆绑包")?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
相关文章
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆