前言
Android系统中最先被我们感知的就是Launcher界面,对于基于Android系统的智能设备来说,对Launcher进行定制是非常重要的一个环节,那么了解Launcher的加载和启动就显得尤为重要,因此接下来会用几章的篇幅进行分析阐述其中的原理。
Launcher启动流程图
对上述流程图略作说明,当按下启动电源时,系统启动后会加载引导程序,引导程序又会启动 Linux 内核,在加载 Linux 内核时,它会在系统文件中寻找 init.rc 文件,并启动 init 进程,之后会按着上图中绿色部分依次启动直至最后启动 Launcher , 至此Android桌面展现。实际上Android系统的启动流程要比上图复杂的多,为了便于理解做了简化,而接下来的几篇会按着 init、Zygote、SystemService、ActivityManagerService、Launcher 这五个方面分别论述。
各进程间关系
由图中可以看出,他们之间的通讯过程大致是:
- Launcher通过AIDL(Binder的一种)通知SystemService
- SystemService通过Socket通知Zygote
- Zygote 收到SystemService请求,fork自身生成应用程序
- 应用程序进程与SystemService通过Binder与AMS,WMS进行交互
Init 进程任务
- 创建和挂载启动所需要的文件目录
- 初始化和启动属性服务
- 解析 init.rc 配置文件并启动 Zygote 进程
Init 源码分析
为避免陷入到庞杂的源码中,本小节会尽量的抽取 init 进程中的主要脉络作为说明.
init 进程是 Android 启动的第一个进程,进程号为 1 ,其源码主要存在于 "/system/core/init" 目录下。
(1) Init 进程入口
在 init 进程的入口函数为 main ,代码如下所示:
int main(int argc, char** argv) {
...
bool is_first_stage = (getenv("INIT_SECOND_STAGE") == nullptr);
if (is_first_stage) {
boot_clock::time_point start_time = boot_clock::now();
// Clear the umask.
umask(0);
clearenv();
// 创建和挂载启动所需的文件目录 注释 ①
mount("tmpfs", "/dev", "tmpfs", MS_NOSUID, "mode=0755");
mkdir("/dev/pts", 0755);
mkdir("/dev/socket", 0755);
mount("devpts", "/dev/pts", "devpts", 0, NULL);
mount("proc", "/proc", "proc", 0, "hidepid=2,gid=" MAKE_STR(AID_READPROC));
chmod("/proc/cmdline", 0440);
mount("sysfs", "/sys", "sysfs", 0, NULL);
mount("selinuxfs", "/sys/fs/selinux", "selinuxfs", 0, NULL);
mount("tmpfs", "/mnt", "tmpfs", MS_NOEXEC | MS_NOSUID | MS_NODEV,
mkdir("/mnt/vendor", 0755);
...
// 初始化 Kernel 的 Log,这样就可以从外界获取 Kernel 的日志 注释②
InitKernelLogging(argv);
}
...
// 初始化属性服务配置 注释 ③
property_init();
...
// 用于设置紫禁城信号处理函数,如果子进程(Zygote进程)异常退出,init 进程会调用该函数中
//设定的信号处理函数来进行处理 注释 ④
sigchld_handler_init();
property_load_boot_defaults();
export_oem_lock_status();
// 启动属性服务 注释 ⑤
start_property_service();
set_usb_controller();
...
// 加载和解析 init.rc 文件 注释⑥
LoadBootScripts(am, sm);
return 0;
}
(2) 属性服务
Windows 平台采用注册表管理器来保存用户、软件的一些使用信息。这样即使系统或者软件重启,就可以根据注册表中的记录,进行相应的初始化工作。Android 提供类似的机制,叫做 属性服务 。
在上述 main 函数中与属性服务相关的代码有两行:
property_init();
start_property_service();
先来看看两个函数的具体实现:"/system/core/init/property_service.cpp"
void property_init() {
mkdir("/dev/__properties__", S_IRWXU | S_IXGRP | S_IXOTH);
CreateSerializedPropertyInfo();
if (__system_property_area_init()) {
LOG(FATAL) << "Failed to initialize property area";
}
}
__system_property_area_init 函数用来初始化属性内存区域,接下来看看 start_property_service 函数的具体实现:
void start_property_service() {
selinux_callback cb;
cb.func_audit = SelinuxAuditCallback;
selinux_set_callback(SELINUX_CB_AUDIT, cb);
property_set("ro.property_service.version", "2");
// 创建非阻塞的 Socket 注释 ①
property_set_fd = CreateSocket(PROP_SERVICE_NAME, SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK,
false, 0666, 0, 0, nullptr);
if (property_set_fd == -1) {
PLOG(FATAL) << "start_property_service socket creation failed";
}
// 调用 listen 函数监听 property_set_fd 注释 ②
listen(property_set_fd, 8);
// epoll 替换 select 处理大批量的文件描述符,handle_property_set_fd 用来处理客户端的请求
register_epoll_handler(property_set_fd, handle_property_set_fd);
}
可以看出在 注释 ② 处调用 listen 函数对 property_set_fd 进行监听,这样创建的 Socket 就成为 server,也就是 属性服务 ;listen 函数的第二个参数设置为 8 ,意味着属性服务最多可以同时为8个试图设置属性的用户提供服务;
(3) 加载和解析 init.rc 文件
init 进程通过解析 init.rc 文件去启动 Zygote 进程,这里通过 LoadBootScripts() 函数实现:
/system/core/init/init.cpp:
static void LoadBootScripts(ActionManager& action_manager, ServiceList& service_list) {
Parser parser = CreateParser(action_manager, service_list);
std::string bootscript = GetProperty("ro.boot.init_rc", "");
if (bootscript.empty()) {
parser.ParseConfig("/init.rc");
if (!parser.ParseConfig("/system/etc/init")) {
late_import_paths.emplace_back("/system/etc/init");
}
if (!parser.ParseConfig("/product/etc/init")) {
late_import_paths.emplace_back("/product/etc/init");
}
if (!parser.ParseConfig("/odm/etc/init")) {
late_import_paths.emplace_back("/odm/etc/init");
}
if (!parser.ParseConfig("/vendor/etc/init")) {
late_import_paths.emplace_back("/vendor/etc/init");
}
} else {
parser.ParseConfig(bootscript);
}
}
如果没有特殊配置ro.boot.init_rc,则解析./init.rc,把 /system/etc/init、/product/etc/init、/product_services/etc/init、/odm/etc/init、/vendor/etc/init 这几个路径加入init.rc之后解析的路径,在init.rc解析完成后,解析这些目录里的rc文件。这里需要说明一下,在 Android 7.0 之后,init.rc 进行了拆分,每个服务都有自己的 rc 文件,它们基本都被加载到这几个目录中,待解析之后,执行相关的动作。关于 init.rc 文件的组成结构可以参见 init.rc结构说明