文章主要介绍了 Android 的 xapk 包,包括其与 apk 的类似性、两种组织方式,重点阐述了 xapk 包的安装过程。以 adb install-multiple 命令为例,其在 Host 端通过 install_multiple_app 方法执行一系列操作,包括计算 apk 文件总大小、向服务端请求执行 install-create 命令、执行 install-write 命令写入 apk 及 install-commit 提交安装,还介绍了 adb_connect 方法及 daemon 端处理命令的相关代码,最终安装命令都走到了 PackageManagerService 中。
关联问题:XAPK安装有何限制OBB文件如何获取传统安装方式怎样1. xapk介绍
xapk
和apk
类似,是android
的一种打包应用程序的方法。使用这种方式打成的包以.xapk
结尾.可以将.xapk
后缀直接改为zip
后缀,然后使用解压缩工具解压xapk
包。xapk
有两种组织方式:
- xapk=核心apk+obb文件夹
- 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安装命令如下
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安装流程
adb
是Android
系统提供的调试工具,分为Host
端和daemon
端,Host
端即PC安装的应用程序,daemon
即运行于android
真机上的adb
后台服务。其中Hos
t端又分为adb client
和adb service
,其中adb client
负责接收adb
命令或处理不需要daemon
处理的命令,若adb
命令需要daemon
处理,则将该命令发给运行于Host
端的后台服务adb service
,再由adb service
发给daemon
,本文不区分adb client
和adb service
,统一由adb host代替。adb源码位于/system/core/adb
目录,下面先看Host端的执行过程
3.1 adb Host端
adb命令在Host的执行入口为/system/core/adb/client/main.cpp#main()
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
有关的代码如下
...
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
,.dm
和fsv_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
类似:
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
形式为:
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
代码块:
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
命令废弃本次安装,这两个命令如下
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
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
端:
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
中
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
中
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()
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
中,其核心代码如下
...
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步:
- 获取到
cmd
后面的第一个参数,本文即package
- 根据获取到参数在
SystemServer
中查找对应的service
,package
对应的就是PackageManagerService
,所以实际上就是获取PackageManagerService
- 在获取到对应的
service
后,调用IBinder::shellcommand
方法执行后续操作。
IBinder::shellcommand
方法实际上调用的是对应的service
的onShellCommand
方法在本文实际就是调用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
中的执行过程