xapk
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后台服务。其中Host端又分为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文件总大小
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命令
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文件
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);
}
