Android Xapk包安装(一)

1. xapk介绍

xapkapk类似,是android的一种打包应用程序的方法。使用这种方式打成的包以.xapk结尾.可以将.xapk后缀直接改为zip后缀,然后使用解压缩工具解压xapk包。xapk有两种组织方式:

  1. xapk=核心apk+obb文件夹
  2. xapk=核心apk+一个或多个其他apk

两种方式都包含一个核心apk,该apk的名字一般就是包名.apk,是应用必须的骨架包。在核心apk+obb文件夹的组合中,只包含一个apk,即核心apk,应用需要的音视频,图片等资源则抽离成若干个obb文件,存放在obb文件夹下。在核心apk+一个或多个其他apk的组合中,会将不同语言配置,so库等数据抽离成若干个单独的apk。在Google play中下载的应用,大部分都是这两种形式。

2. xapk包的安装

2.1 核心apk+obb文件夹的xapk安装

这种方式的xapk包安装比较简单,首先安装核心apk,然后手动将obb文件复制到/sdcard/Android/obb目录即可。

2.2 核心apk+一个或多个其他apk

这种方式的apk包,一般需要借助第三方的xapk安装工具,常见的有XapkInstaller,可以直接在Google play下载,,此外,也可以使用adb方式安装,adb安装命令如下

Bash 复制代码
adb install-multiple au.com.metrotrains.dwtd4.apk config.arm64_v8a.apk

其中adb install-multiple命令是用于安装由多个apk组成的应用,后面跟的参数就是该应用的所有apk。 注意,需要将该命令和install-multi-package命令区分开,该命令用于批量安装多个应用。

本系列以adb install-multiple命令为线索,简单分析一下xapk的安装过程,同时也学习了解Android安装应用的过程,涉及到的Android 源码为Android 10源码

3.adb install-multiple安装流程

adbAndroid系统提供的调试工具,分为Host端和daemon端,Host端即PC安装的应用程序,daemon即运行于android真机上的adb后台服务。其中Host端又分为adb client 和adb service,其中adb client负责接收adb命令或处理不需要daemon处理的命令,若adb命令需要daemon处理,则将该命令发给运行于Host端的后台服务adb service,再由adb service发给daemon,本文不区分adb clientadb service,统一由adb host代替。adb源码位于/system/core/adb目录,下面先看Host端的执行过程

3.1 adb Host端

adb命令在Host的执行入口为/system/core/adb/client/main.cpp#main()

c++ 复制代码
int main(int argc, char* argv[], char* envp[]) {
    __adb_argv = const_cast<const char**>(argv);
    __adb_envp = const_cast<const char**>(envp);
    adb_trace_init(argv);
    return adb_commandline(argc - 1, const_cast<const char**>(argv + 1));
}

在main方法中,调用了adb_commandline()方法,该方法位于 /system/core/adb/client/commandline.cpp 中,该方法用于解析各种adb命令,和install-multiple有关的代码如下

c++ 复制代码
...
else if (!strcmp(argv[0], "install-multiple")) {
        if (argc < 2) error_exit("install-multiple requires an argument");
        return install_multiple_app(argc, argv);
...

在该方法中,若adb的命令是install-multiple,就调用install_multiple_app方法进行后续处理。该方法源码位于/system/core/adb/client/adb_install.cpp中,是使用adb 安装xapk的核心方法。该方法的执行主要分为如下几步

(1) 遍历需要安装的apk参数,计算出需要安装的apk文件总大小
C++ 复制代码
int first_apk = -1;
uint64_t total_size = 0;
for (int i = argc - 1; i >= 0; i--) {
     const char* file = argv[i];
     if (android::base::EndsWithIgnoreCase(argv[i], ".apex")) {
     error_exit("APEX packages are not compatible with install-multiple");
     }

     if (android::base::EndsWithIgnoreCase(file, ".apk") ||
         android::base::EndsWithIgnoreCase(file, ".dm") ||
         android::base::EndsWithIgnoreCase(file, ".fsv_sig")) {
         struct stat sb;
         if (stat(file, &sb) != -1) total_size += sb.st_size;
         first_apk = i;
     } else {
         break;
     }
}

if (first_apk == -1) error_exit("need APK file on command line");

从上述代码中可以看到,install-multiple命令不能安装以apex结尾的文件,只能安装.apk,.dmfsv_sig结尾的文件,一般使用的都是以.apk结尾的文件。对每一个apk文件,使用stat获取其文件大小,并最终计算出应用程序包总的大小。

(2)向服务端请求执行install-create命令
C++ 复制代码
    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 (int i = 1; i < first_apk; i++) {
        cmd += " " + escape_arg(argv[i]);
    }

    // Create install session
    std::string error;
    char buf[BUFSIZ];
    {
        unique_fd fd(adb_connect(cmd, &error));
        if (fd < 0) {
            fprintf(stderr, "adb: connect error for create: %s\n", error.c_str());
            return EXIT_FAILURE;
        }
        read_status_line(fd.get(), buf, sizeof(buf));
    }

    int session_id = -1;
    if (!strncmp("Success", buf, 7)) {
        char* start = strrchr(buf, '[');
        char* end = strrchr(buf, ']');
        if (start && end) {
            *end = '\0';
            session_id = strtol(start + 1, nullptr, 10);
        }
    }
    if (session_id < 0) {
        fprintf(stderr, "adb: failed to create session\n");
        fputs(buf, stderr);
        return EXIT_FAILURE;
    }

在这一步,构造一个install-create命令保存到变量install_cmd中,然后将该命令传给adb daemon端,让服务端执行该命令。use_legacy_install()用来判断是否使用传统方式安装apk,所谓传统方式,指的是将apk文件复制到特定目录下,然后通知系统进行安装的方式。在较早android版本中较常见,现在一般不会使用,所以install_cmd初始值一般是exec:cmd package。在确定了install_cmd的初始值后,使用cmd拼接install_cmd的参数,其中-S用来表示需要安装的apk的大小,后面再跟上每个apk文件的路径,经过上述操作,最终得到的install_cmd类似:

Bash 复制代码
exec:cmd package install-create -S size au.com.metrotrains.dwtd4.apk config.arm64_v8a.apk

在构造好install_cmd命令后,调用adb_connect方法将该命令发往daemon端,并等待daemon端返回结果,然后将生成的sessionId保存到变量session_id中。adb_connect方法放在下面分析,现在继续向下看install_multiple_app方法

(3)执行install-write命令写入apk

在步骤(2)生成sessionId后,就遍历apk文件,每一个apk文件执行install-write命令写入apk文件

C++ 复制代码
 int success = 1;
    for (int i = first_apk; i < argc; i++) {
        const char* file = argv[i];
        struct stat sb;
        if (stat(file, &sb) == -1) {
            fprintf(stderr, "adb: failed to stat %s: %s\n", file, strerror(errno));
            success = 0;
            goto finalize_session;
        }

        std::string cmd =
                android::base::StringPrintf("%s install-write -S %" PRIu64 " %d %s -",
                                            install_cmd.c_str(), static_cast<uint64_t>(sb.st_size),
                                            session_id, android::base::Basename(file).c_str());

        unique_fd local_fd(adb_open(file, O_RDONLY | O_CLOEXEC));
        if (local_fd < 0) {
            fprintf(stderr, "adb: failed to open %s: %s\n", file, strerror(errno));
            success = 0;
            goto finalize_session;
        }

        std::string error;
        unique_fd remote_fd(adb_connect(cmd, &error));
        if (remote_fd < 0) {
            fprintf(stderr, "adb: connect error for write: %s\n", error.c_str());
            success = 0;
            goto finalize_session;
        }

        copy_to_file(local_fd.get(), remote_fd.get());
        read_status_line(remote_fd.get(), buf, sizeof(buf));

        if (strncmp("Success", buf, 7)) {
            fprintf(stderr, "adb: failed to write %s\n", file);
            fputs(buf, stderr);
            success = 0;
            goto finalize_session;
        }
    }

在上面代码中,生成的cmd形式为:

Bash 复制代码
exec:cmd package install-write -S apkSize sessionId  au.com.metrotrains.dwtd4.apk -

在生成cmd后,先调用adb_open方法打开apk文件,然后调用adb_connect方法连接daemon端执行cmd,daemon端返回结果后读取其中的内容判断操作是否成功,无论操作成功与否,最后都会跳转到finalize_session代码块

(4) install-commit提交本次安装

在步骤(3)中,无论install-write的结果如何,最后都会跳转到finalize_session代码块:

C++ 复制代码
finalize_session:
    // Commit session if we streamed everything okay; otherwise abandon
    std::string service = android::base::StringPrintf("%s install-%s %d", install_cmd.c_str(),
                                                      success ? "commit" : "abandon", session_id);
    {
        unique_fd fd(adb_connect(service, &error));
        if (fd < 0) {
            fprintf(stderr, "adb: connect error for finalize: %s\n", error.c_str());
            return EXIT_FAILURE;
        }
        read_status_line(fd.get(), buf, sizeof(buf));
    }

    if (!strncmp("Success", buf, 7)) {
        fputs(buf, stdout);
        return 0;
    }
    fprintf(stderr, "adb: failed to finalize session\n");
    fputs(buf, stderr);
    return EXIT_FAILURE;
}

finalize_session代码块中,如果install-write操作写入成功,就执行install-commit操作,commit本次安装,若写入失败,就执行install abandon命令废弃本次安装,这两个命令如下

Bash 复制代码
exec:cmd package install-commit sessionId
exec:cmd package intall-abandon sessionId

本系统只考虑commit的情形。从代码可以看到,也是通过adb_connect方法将该命令发送给daemon端,由服务端处理,daemon处理完成后,将结果打印到终端上,结束本次安装操作。

总结:通过分析install_multiple_app方法可知,adb install-multiple命令实际上是通过adb_connect方法向daemon端发送cmd:package install-create,cmd:packgae:install-write,cmd:packgae:install-commit三个命令完成安装操作。在分析adb daemon端相关代码前,先看看adb_connect的代码

3.2 adb_connect

adb_connect方法是adb Host端和daemon端通信的核心方法,源码路径为/system/core/adb/client/adb_client.cpp

C++ 复制代码
int adb_connect(std::string_view service, std::string* error) {
    return adb_connect(nullptr, service, error);
}

int adb_connect(TransportId* transport, std::string_view service, std::string* error) {
    LOG(DEBUG) << "adb_connect: service: " << service;

    // Query the adb server's version.
    if (!adb_check_server_version(error)) {
        return -1;
    }

    // if the command is start-server, we are done.
    if (service == "host:start-server") {
        return 0;
    }

    unique_fd fd(_adb_connect(service, transport, error));
    if (fd == -1) {
        D("_adb_connect error: %s", error->c_str());
    } else if(fd == -2) {
        fprintf(stderr, "* daemon still not running\n");
    }
    D("adb_connect: return fd %d", fd.get());

    return fd.release();
}

adb_connect方法中,首先调用adb_check_server_version方法检查adb server的版本,在该方法中也会调用_adb_connect方法。检查完版本后,调用_adb_connect方法将命令发给adb daemon端:

C++ 复制代码
static int _adb_connect(std::string_view service, TransportId* transport, std::string* error) {
    LOG(DEBUG) << "_adb_connect: " << service;
    if (service.empty() || service.size() > MAX_PAYLOAD) {
        *error = android::base::StringPrintf("bad service name length (%zd)", service.size());
        return -1;
    }

    std::string reason;
    unique_fd fd;
    if (!socket_spec_connect(&fd, __adb_server_socket_spec, nullptr, nullptr, &reason)) {
        *error = android::base::StringPrintf("cannot connect to daemon at %s: %s",
                                             __adb_server_socket_spec, reason.c_str());
        return -2;
    }

    if (!service.starts_with("host")) {
        std::optional<TransportId> transport_result = switch_socket_transport(fd.get(), error);
        if (!transport_result) {
            return -1;
        }

        if (transport) {
            *transport = *transport_result;
        }
    }

    if (!SendProtocolString(fd.get(), service)) {
        *error = perror_str("write failure during connection");
        return -1;
    }

    if (!adb_status(fd.get(), error)) {
        return -1;
    }

    D("_adb_connect: return fd %d", fd.get());
    return fd.release();
}

_adb_connect方法中,先建立和daemon端的tcp连接,然后通过SendProtocolString方法将数据传输给daemon

3.3 adb daemon端

daemon端处理Host端发送过来的adb 命令的入口函数为service_to_fd方法,位于/system/core/adb/service.cpp

C++ 复制代码
unique_fd service_to_fd(std::string_view name, atransport* transport) {
    unique_fd ret;

    if (is_socket_spec(name)) {
        std::string error;
        if (!socket_spec_connect(&ret, name, nullptr, nullptr, &error)) {
            LOG(ERROR) << "failed to connect to socket '" << name << "': " << error;
        }
    } else {
#if !ADB_HOST
        ret = daemon_service_to_fd(name, transport);
#endif
    }

    if (ret >= 0) {
        close_on_exec(ret.get());
    }
    return ret;
}

其中ADB_HOST宏用于标识是否是HOST端,在adb daemon端,该值为0,HOST端该值为1,所以在daemon端是调用daemon_service_to_fd方法处理adb命令,该方法位于/system/core/adb/daemon/service.cpp

C++ 复制代码
unique_fd daemon_service_to_fd(std::string_view name, atransport* transport) {
#if defined(__ANDROID__) && !defined(__ANDROID_RECOVERY__)
    if (name.starts_with("abb:") || name.starts_with("abb_exec:")) {
        return execute_abb_command(name);
    }
#endif

#if defined(__ANDROID__)
    if (name.starts_with("framebuffer:")) {
        return create_service_thread("fb", framebuffer_service);
    } else if (ConsumePrefix(&name, "remount:")) {
        std::string arg(name);
        return create_service_thread("remount",
                                     std::bind(remount_service, std::placeholders::_1, arg));
    } else if (ConsumePrefix(&name, "reboot:")) {
        std::string arg(name);
        return create_service_thread("reboot",
                                     std::bind(reboot_service, std::placeholders::_1, arg));
    } else if (name.starts_with("root:")) {
        return create_service_thread("root", restart_root_service);
    } else if (name.starts_with("unroot:")) {
        return create_service_thread("unroot", restart_unroot_service);
    } else if (ConsumePrefix(&name, "backup:")) {
        std::string cmd = "/system/bin/bu backup ";
        cmd += name;
        return StartSubprocess(cmd, nullptr, SubprocessType::kRaw, SubprocessProtocol::kNone);
    } else if (name.starts_with("restore:")) {
        return StartSubprocess("/system/bin/bu restore", nullptr, SubprocessType::kRaw,
                               SubprocessProtocol::kNone);
    } else if (name.starts_with("disable-verity:")) {
        return create_service_thread("verity-on", std::bind(set_verity_enabled_state_service,
                                                            std::placeholders::_1, false));
    } else if (name.starts_with("enable-verity:")) {
        return create_service_thread("verity-off", std::bind(set_verity_enabled_state_service,
                                                             std::placeholders::_1, true));
    } else if (ConsumePrefix(&name, "tcpip:")) {
        std::string str(name);

        int port;
        if (sscanf(str.c_str(), "%d", &port) != 1) {
            return unique_fd{};
        }
        return create_service_thread("tcp",
                                     std::bind(restart_tcp_service, std::placeholders::_1, port));
    } else if (name.starts_with("usb:")) {
        return create_service_thread("usb", restart_usb_service);
    }
#endif

    if (ConsumePrefix(&name, "dev:")) {
        return unique_fd{unix_open(name, O_RDWR | O_CLOEXEC)};
    } else if (ConsumePrefix(&name, "jdwp:")) {
        pid_t pid;
        if (!ParseUint(&pid, name)) {
            return unique_fd{};
        }
        return create_jdwp_connection_fd(pid);
    } else if (ConsumePrefix(&name, "shell")) {
        return ShellService(name, transport);
    } else if (ConsumePrefix(&name, "exec:")) {
        return StartSubprocess(std::string(name), nullptr, SubprocessType::kRaw,
                               SubprocessProtocol::kNone);
    } else if (name.starts_with("sync:")) {
        return create_service_thread("sync", file_sync_service);
    } else if (ConsumePrefix(&name, "reverse:")) {
        return reverse_service(name, transport);
    } else if (name == "reconnect") {
        return create_service_thread(
                "reconnect", std::bind(reconnect_service, std::placeholders::_1, transport));
    } else if (name == "spin") {
        return create_service_thread("spin", spin_service);
    }

    return unique_fd{};
}

在该方法中,主要就是解析各种adb命令,然后使用对应的方法去处理,在本文中,传到daemon端的命令前缀是exec:,从代码61行可以看到,对于exec:命令,是创建一个子线程去执行对应的命令,本文接收到的命令以exec:cmd开头,对应的就是创建一个子线程去执行cmd命令。cmd命令的代码源码位于/frameworks/native/cmds/cmd目录,其入口为main.cpp#main()

C++ 复制代码
int main(int argc, char* const argv[]) {
    signal(SIGPIPE, SIG_IGN);

    std::vector<std::string_view> arguments;
    arguments.reserve(argc - 1);
    // 0th argument is a program name, skipping.
    for (int i = 1; i < argc; ++i) {
        arguments.emplace_back(argv[i]);
    }

    return cmdMain(arguments, android::aout, android::aerr, STDIN_FILENO, STDOUT_FILENO,
                   STDERR_FILENO, RunMode::kStandalone);
}

main方法中,先获取到cmd的参数,对于exec:cmd package install-create -S size au.com.metrotrains.dwtd4.apk config.arm64_v8a.apk就是cmd后面的一系列参数。在获取到这些参数后,调用cmdMain方法进行后续处理,该方法位于/frameworks/native/cmds/cmd/cmd.cpp中,其核心代码如下

C++ 复制代码
...
  const auto cmd = argv[0];

    Vector<String16> args;
    String16 serviceName = String16(cmd.data(), cmd.size());
    for (int i = 1; i < argc; i++) {
        args.add(String16(argv[i].data(), argv[i].size()));
    }
    sp<IBinder> service = sm->checkService(serviceName);
    if (service == nullptr) {
        if (runMode == RunMode::kStandalone) {
            ALOGW("Can't find service %.*s", static_cast<int>(cmd.size()), cmd.data());
        }
        errorLog << "cmd: Can't find service: " << cmd << endl;
        return 20;
    }

    sp<MyShellCallback> cb = new MyShellCallback(errorLog);
    sp<MyResultReceiver> result = new MyResultReceiver();

#if DEBUG
    ALOGD("cmd: Invoking %s in=%d, out=%d, err=%d", cmd, in, out, err);
#endif

    // TODO: block until a result is returned to MyResultReceiver.
    status_t error = IBinder::shellCommand(service, in, out, err, args, cb, result);
    if (error < 0) {
        const char* errstr;
        switch (error) {
            case BAD_TYPE: errstr = "Bad type"; break;
            case FAILED_TRANSACTION: errstr = "Failed transaction"; break;
            case FDS_NOT_ALLOWED: errstr = "File descriptors not allowed"; break;
            case UNEXPECTED_NULL: errstr = "Unexpected null"; break;
            default: errstr = strerror(-error); break;
        }
        if (runMode == RunMode::kStandalone) {
            ALOGW("Failure calling service %.*s: %s (%d)", static_cast<int>(cmd.size()), cmd.data(),
                  errstr, -error);
        }
        outputLog << "cmd: Failure calling service " << cmd << ": " << errstr << " (" << (-error)
                  << ")" << endl;
        return error;
    }

    cb->mActive = false;
    status_t res = result->waitForResult();
#if DEBUG
    ALOGD("result=%d", (int)res);
#endif
    return res;

在该方法中,主要执行的步骤有3步:

  1. 获取到cmd后面的第一个参数,本文即package
  2. 根据获取到参数在SystemServer中查找对应的service,package对应的就是PackageManagerService,所以实际上就是获取PackageManagerService
  3. 在获取到对应的service后,调用IBinder::shellcommand方法执行后续操作。

IBinder::shellcommand方法实际上调用的是对应的serviceonShellCommand方法在本文实际就是调用PackageManagerService#onShellCommand方法,该方法源码位于/frameworks/base/services/core/java/com/android/server/pm/PackageManagerService.java。到这里,实际上后续的操作就是在PackageManagerService中完成了。关于在PackageManagerService中的操作,后文继续分析

4 总结

本文首先简单介绍了xapk包的组成,然后以adb install-multiple为线索,简单分析了安装xapk的过程,总体来说,就是执行install-create,install-write,install-commit三个命令完成xapk的安装,这三个命令最终都走到了PackageManagerService中。后续的文章中将继续分析PackageManagerService中的执行过程

相关推荐
试行36 分钟前
Android实现自定义下拉列表绑定数据
android·java
Dingdangr5 小时前
Android中的Intent的作用
android
技术无疆5 小时前
快速开发与维护:探索 AndroidAnnotations
android·java·android studio·android-studio·androidx·代码注入
GEEKVIP6 小时前
Android 恢复挑战和解决方案:如何从 Android 设备恢复删除的文件
android·笔记·安全·macos·智能手机·电脑·笔记本电脑
Jouzzy12 小时前
【Android安全】Ubuntu 16.04安装GDB和GEF
android·ubuntu·gdb
极客先躯13 小时前
java和kotlin 可以同时运行吗
android·java·开发语言·kotlin·同时运行
Good_tea_h15 小时前
Android中的单例模式
android·单例模式
计算机源码社20 小时前
分享一个基于微信小程序的居家养老服务小程序 养老服务预约安卓app uniapp(源码、调试、LW、开题、PPT)
android·微信小程序·uni-app·毕业设计项目·毕业设计源码·计算机课程设计·计算机毕业设计开题
丶白泽21 小时前
重修设计模式-结构型-门面模式
android
晨春计1 天前
【git】
android·linux·git