一、公司架构:项目经理与快递小哥
想象 Android 系统是一家 "应用安装公司":
-
PKMS(PackageManagerService) 是穿着西装的项目经理,坐在办公室里(system 权限)负责接收用户安装请求、规划流程
-
installd 是穿着工作服的快递小哥,真正跑腿干活(拥有 root 权限),负责执行安装的实际操作
项目经理不会亲自搬箱子,而是通过一个 "内部电话系统"(socket 套接字)给快递小哥派任务。这个电话系统的号码是 "installd",专门用于两者沟通。
二、快递小哥的上班流程
2.1 入职报到:installd 的启动
清晨,公司老板(init 进程,pid=1)翻开员工花名册(init.rc 文件),看到这行记录:
plaintext
service installd /system/bin/installd
class main
socket installd stream 600 system system
老板下令:"installd,开工!" 于是快递小哥正式入职,并且老板给了他一部专用电话(创建 installd socket),号码是 "installd",权限设置为只能内部人员(system 用户)拨打。
快递小哥的工作入口在 installd.cpp 的 main 函数,他上班后的第一件事就是:
- 整理办公桌(初始化全局目录)
- 打扫仓库(初始化数据目录)
- 拿起电话听筒(监听 socket),开始等待任务
2.2 整理办公桌:初始化目录
快递小哥需要知道各种包裹该放哪里,所以上班第一件事就是记住这些地址:
-
应用仓库:/data/app/
-
私密应用仓库:/data/priv-app/
-
应用配件仓库:/data/app-lib/
-
临时仓库:/mnt/asec
-
系统应用仓库:/system/app/、/vendor/app/ 等
这些地址记录在 initialize_globals 函数里,就像快递小哥的地址簿:
c
运行
int initialize_globals() {
// 从环境变量获取数据根目录
if (get_path_from_env(&android_data_dir, "ANDROID_DATA") < 0) return -1;
// 拼接出各个子目录路径
if (copy_and_append(&android_app_dir, &android_data_dir, "app") < 0) return -1;
if (copy_and_append(&android_app_private_dir, &android_data_dir, "priv-app") < 0) return -1;
// ...更多目录初始化
}
2.3 打扫仓库:初始化目录结构
接下来快递小哥需要确保仓库结构正确,比如:
-
创建用户包裹区:/data/user/
-
创建主用户包裹区:/data/user/0/
-
将 /data/user/0 链接到 /data/data,方便旧系统兼容
这部分工作在 initialize_directories 函数中,就像快递小哥按区域划分仓库:
c
运行
int initialize_directories() {
// 读取当前仓库版本号
char version_path[PATH_MAX];
snprintf(version_path, PATH_MAX, "%s.layout_version", android_data_dir.path);
// 创建用户数据目录
char *user_data_dir = build_string2(android_data_dir.path, "user/");
char *primary_data_dir = build_string3(android_data_dir.path, "user/", "0");
// 建立目录链接
if (access(primary_data_dir, R_OK) < 0) {
symlink("/data/data", primary_data_dir); // 将user/0链接到data
}
// ...更多目录处理
}
2.4 接听电话:处理任务
电话铃声响起(socket 收到消息),快递小哥开始处理任务。他的工作流程是:
-
接听电话(accept socket 连接)
-
听清楚任务长度(读取命令长度)
-
记录任务内容(读取命令内容)
-
查看任务清单(命令表),找到对应的处理方法
-
执行任务,返回结果
核心代码就像这样:
c
运行
int main() {
// 初始化...
int lsocket = android_get_control_socket("installd");
listen(lsocket, 5); // 开始接听电话
for (;;) {
int s = accept(lsocket, ...); // 接听来电
for (;;) {
unsigned short count;
readx(s, &count, sizeof(count)); // 听任务长度
char buf[1024];
readx(s, buf, count); // 听任务内容
buf[count] = 0;
execute(s, buf); // 执行任务
}
close(s); // 挂电话
}
}
static int execute(int s, char cmd[1024]) {
char arg[100+1];
int n = 0;
arg[0] = cmd;
// 解析命令参数...
// 查看任务清单
for (int i=0; i<命令表长度; i++) {
if (strcmp(cmds[i].name, arg[0]) == 0) {
if (参数数量匹配) {
cmds[i].func(参数, 回复); // 执行对应任务函数
}
break;
}
}
// 返回结果...
}
2.5 任务清单:快递小哥的工作内容
快递小哥能处理 25 种任务,记录在命令表里:
c
运行
struct cmdinfo cmds[] = {
{"ping", 0, do_ping}, // 测试连通性("你好,在吗?")
{"install", 5, do_install}, // 安装应用(核心任务)
{"dexopt", 9, do_dexopt}, // 优化应用代码
{"remove", 3, do_remove}, // 卸载应用
{"getsize", 8, do_get_size}, // 获取应用大小
// ...更多任务
};
比如当收到 "install" 命令时,就会调用 do_install 函数,完成复制 APK、优化 DEX、设置权限等实际安装工作。
三、项目经理如何派任务
3.1 项目经理的秘书:Installer 类
项目经理(PKMS)不会直接打电话,而是让秘书(Installer 类)处理沟通:
java
// SystemServer.java中启动秘书服务
private void startBootstrapServices() {
Installer installer = mSystemServiceManager.startService(Installer.class);
}
// Installer.java初始化
public Installer(Context context) {
mInstaller = new InstallerConnection(); // 创建通信员
}
public void onStart() {
mInstaller.waitForConnection(); // 等待快递小哥就绪
}
3.2 打电话的流程:socket 通信
秘书打电话的流程就像这样:
-
拨号连接(connect):
java
private boolean connect() {
mSocket = new LocalSocket();
LocalSocketAddress address = new LocalSocketAddress("installd",
LocalSocketAddress.Namespace.RESERVED);
mSocket.connect(address); // 拨打"installd"号码
mIn = mSocket.getInputStream(); // 电话听筒
mOut = mSocket.getOutputStream(); // 电话话筒
}
-
发送任务(writeCommand):
java
private boolean writeCommand(String cmdString) {
byte[] cmd = cmdString.getBytes();
int len = cmd.length;
// 先告诉对方任务长度(2字节)
buf[0] = (byte)(len & 0xff);
buf[1] = (byte)((len >> 8) & 0xff);
mOut.write(buf, 0, 2); // 说"我有X字节的任务"
mOut.write(cmd, 0, len); // 说具体任务内容
}
-
等待回复(readReply):
java
private int readReply() {
readFully(buf, 2); // 先听回复长度
int len = (buf[0] & 0xff) | ((buf[1] & 0xff) << 8);
readFully(buf, len); // 再听具体回复内容
return len;
}
3.3 第一次通话:确认快递小哥在岗
秘书上班后做的第一件事就是确认快递小哥是否在岗:
java
public void waitForConnection() {
for (;;) {
if (execute("ping") >= 0) { // 发送"ping"命令
return; // 收到回复,确认在岗
}
SystemClock.sleep(1000); // 没回应就等1秒再打
}
}
四、总结:应用安装的完整流程
-
用户点击安装 APK,PKMS(项目经理)收到请求
-
PKMS 将安装任务打包成命令(如 "install"),交给 Installer 秘书
-
秘书通过 socket 电话("installd" 号码)联系 installd 快递小哥
-
快递小哥解析命令,从命令表中找到 do_install 函数
-
执行实际安装工作:复制 APK 到 /data/app、优化 DEX、设置文件权限等
-
完成后将结果通过 socket 返回给秘书
-
秘书将结果汇报给 PKMS,PKMS 通知用户安装完成
installd 就像默默工作的快递小哥,虽然不直接面对用户,却是 Android 应用安装流程中最核心的执行者,所有文件操作、权限设置等实际工作都由它完成。通过 socket 通信,它与上层服务解耦,保证了系统的稳定性和安全性。