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
后台服务。其中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文件总大小
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);
}