首先android是基于Linux的内核,只有先加载了kernel才能启动安卓,对Linux来讲android只是其上的1个利用程序。Android的启动大致可以形象的划分为3个进程:
Init->init.rc->zygote。从事嵌入式开发的人都知道,Linux加载完内核驱动后会挂载‘/’根文件系统,挂载完成后会履行‘/init’2进制程序,这也是内核启动后履行的第1个用户程序,android里面也是这样。这个程序的main函数位于android/system/core/init/init.c中,作为1个操作系统,初始化1般要完成以下几个工作:
1.创建需要的目录,挂载文件系统,输入和读取文件硬盘数据。
2.装载和设定全局的环境变量,为程序的运行搭建好必要的环境。
3.对android来讲,还需要运行Java虚拟机,这是安卓独有的跨平台特性。
4.加载和运行Framework框架,加载窗口桌面程序,也就是系统的GUI,最后将控制权交给用户后算启动完成。
源码可以看出,init首先会创建1些必须的目录,如‘/dev’、‘/proc’、‘/sys’等。然后将装备mount到该目录下,mount完成后才可以创建装备节点。例如:
创建串口输出节点,串口重定向输出:
open_devnull_stdio();
在klog_init函数中创建了"/dev/__kmsg__"节点,以后很快unlink掉了,该函数复制了1份串口输出日志,日志包括从kernel启动到android初始化之间的打印信息,通过dmesg命令就能够打印出来。
其中还有以下代码:
property_init();该函数主要是加载1些默许的property属性,这些属性存在于build.prop,default.prop文件中。
get_hardware_name(hardware, &revision);该函数用于从"/proc/cpuinfo"节点中获得cpu的硬件和版本信息,将这些信息写入prop属性变量中。
process_kernel_cmdline();从节点“/proc/cmdline”中获得bootargs的环境变量,并设置到全局变量中以便后续使用。
在程序中系统会读取和解析init.rc文件,这个文件中更像是1些命令集合,但是程序并没有立即履行这些命令。而是先解析出来,依照1定规则进行整理放入1个链表中,init.rc中的命令其实不是像shell1样是1个直接的履行命令,而是有1个内部的映照,这些映照存在于init/keywords.h文件中,例如:
KEYWORD(mkdir, COMMAND, 1, do_mkdir);
KEYWORD(mount, COMMAND, 3, do_mount);
KEYWORD(rm, COMMAND, 1, do_rm);
KEYWORD(rmdir, COMMAND, 1, do_rmdir);
mkdir是rc中的命令,但do_mkdir才是真实的命令履行实体。
在init的后面有1个for循环,命令的履行是在其中完成的:
for(;;) {
execute_one_command();
restart_processes();
。。。。。。。。。。。。。。。
}其中execute_one_command会将命令提取出来,1条接1条的进行履行。命令中如果注册了1个service,那末它如果履行失败了,也还可以进行重新启动履行。
Service常常是单独的进程进行履行的,这些程序履行成功与否是系统关心的问题,系统需要监管这些进程,所以注册了1个信号处理函数handle_signal。如果子进程出现毛病比如内存溢出等,这时候会触发1个信号量,父进程在接收到该信号量后会到handle_signal中进行处理,父进程的有效管理,使得这些进程避免变成“野进程”或“僵尸进程”。信号处理函数wait_for_one_process中,如果等待的进程程序出错或超时会有“waitpid returned pid %d, status = %08x”的打印,平时如果某个service出错反复履行时就会有该打印。直到所有的service成功启动后,init才会正常退出for循环。
分析init.rc文件,通过该文件可以知道系统都做了哪些事情,比如其中的几个关键命令:
on property:ro.debuggable=1
start console
这类语句表示系统会读取prop属性(全局注册表)中的值,如果property:ro.debuggable=1则履行条件以下的语句start console,所以打开android的打印串口是在这地方做的。
sysclktz 0
设置时区。
loglevel 3
设置log等级。
on property:ro.kernel.qemu=1
start adbd
根据build.prop中的该属性设置决定是不是启动远程调试adbd。
service bootanim /system/bin/bootanimation
启动开机动画。
如果留意,Init.rc中会有1个以on开头后面跟1个字段的关键字,如:
on early-init 这类关键字和上面的prop后面随着1个值的方式不相同。它主要用于表示android的启动阶段,init在加载这些命令时其实不是依照从文件头到文件尾的方式,而是寻觅这些关键字,依照启动顺序顺次加载,履行时也依照这个顺序履行,例如这些阶段还有:
early-init、init、early-fs、early-boot、boot等。
在init.rc中还有1个很关键的阶段字,代码以下:
on emmc-fs
mount ext4 ext4@system /system ro
mount ext4 ext4@userdata /data nosuid nodev
mount ext4 ext4@cache /cache nosuid nodev
这几条命令的意思是将@system分区,以ext4的文件系统格式挂载到根目录/system下,ro表示只读的意思。emmc-fs阶段要开始于其它的阶段,由于只有将系统的核心目录挂载了,才能进行后续操作。Init.rc除支持emmc,还支持nand,决定用哪个是系统自动辨认完成的,代码以下:
if ( check_flash_type() == NAND_TYPE) {
action_for_each_trigger("fs", action_add_queue_tail);
}
else if ( check_flash_type() == EMMC_TYPE) {
action_for_each_trigger("emmc-fs", action_add_queue_tail);
}
通过检查bootargs中的关键字“hinand”或“mmcblk”知道flash的类型。
rc文件中有1个核心的关键字service,使用该关键字就是要把其后的命令扩大为1个服务,这个关键字的规则也是最复杂的,例以下面这条命令:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start-system-server
class main
socket zygote stream 660 root system
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
这条命令使用service指令告知系统将zygote加入到系统服务中,service的语法为:
service service_name 可履行程序路径 可履行程序的入口参数
/system/bin/app_process为实际可履行的程序,后面‘-’为履行的参数。socket用于服务所使用到的socket,后面参数顺次为名称、类型、端口、地址。onrestart命令指定该服务重启的条件,即当满足这些条件后,zygote服务就需要重启;固然这些都是1些异常条件,也就是说如果media或netd产生异常重启时,zgote就需要重启。
如果sevices中有oneshot关键字,则表示该service只履行1次,例如:
service bootanim /system/bin/bootanimation
class main
user root
group graphics
disabled
oneshot
表示开机动画履行1次,disabled表示如果出错或超时不会再次履行,更多具体的命令可参照init.rc文件。
从上面的分析可以看出,到目前为止都没有真正触及到android的东西,如果没有zygote那末安卓顶多算1个linux系统。Zygote进程是所有APK利用的父进程,其它进程都是由该进程孵化产生的,所以将其拟化为1个孵化器。通过android系统的虚拟机可以加载Java的开发环境,从功能上来讲,Java语言更类似于Shell和Base语言(属于伪代码),与硬件打交道的任务交给了虚拟机去完成。虚拟机的任务就是屏蔽平台的差异属性,使得同1份Java语言可以同时运行于X86平台和ARM平台。Java语言另外的优势是面向对象,在利用开发方面比C语言更好用,缺点是运行效力比C差。
Zygote的实际履行程序是/system/bin/app_process,app_process的代码位于 frameworks/base/cmds/app_process/app_main.cpp中。
分析app_main.cpp的代码,app_process会在AndroidRuntime.startVm函数中创建第1个虚拟机:
if (JNI_CreateJavaVM(pJavaVM, pEnv, &initArgs) < 0) {
ALOGE("JNI_CreateJavaVM failed ");
goto bail;
}
同时app_process启动后会装载与framework的相干类和resource资源,检查/system/framework/下的jar包,将其中的jar文件通过dexopt优化变成dex文件,同时安装到/data/dalvik-cache/目录中,这样下1次履行时直接到该目录下寻觅。最后启动两个核心的类ZygoteInit.java和SystemServer.java类,这两个类都是以两个单独的进程启动的。SystemServer进程是Android系统的神经中枢,安卓利用直接交互的大部份系统服务都是在该进程中运行的,最关键的有:WindowManagerServer(Wms)、ActivityManagerSystemService(AmS)、PackageManagerServer(PmS)等,这些服务都是在该进程中以线程的方式启动的。
PackagemanagerService主要用来管理apk,该服务会解析apk中的组件,例如Activiey、Service等。系统初始化时会遍历/system/app/和/data/app/目录,将apk以包名的情势拷贝到/data/data/<pkgName>目录下,将apk中的class文件保存到/data/dalvik-cache/目录下,同时以相应的apk进行命名。服务利用PackageParser类解析apk中的AndroidManifest.xml文件获得包的1些信息,并将这些信息保存到/data/system/packages.xml和packages.list中,以便系统后续使用。这些信息应当只会读取1次,下次启动后会检查是不是有更新?如果没有就使用上1次的文件。
第1次开机时该服务还会去创建1些目录,如/data/目录下的data、app-asec、app-lib、app-private等。同时解析/system/etc/permissions/platform.xml文件,知道系统都定义了哪些系统权限和该权限对应可履行程序的uid。当用户安装利用时会弹出1个利用要求的权限,这些权限都是在这里定义的。从以上的步骤可以看出,第1次开机会做很多工作,这也就是为何android系统第1次启动都会很慢的缘由。
当所有的线程服务都启动完成后,其中的ActivityManagerService(AmS)服务会去检测其它服务是不是完成,这个是通过调用systemReady()函数来完成的,最后会履行以下代码:
mMainStack.resumeTopActivityLocked(null);
也就是说AmS会履行最顶层的TopActivity,但是第1次开机TopActivity是没有的,这时候候就会有:
if (next == null) {
// There are no more activities! Let's just start up the
// Launcher...
if (mMainStack) {
ActivityOptions.abort(options);
return mService.startHomeActivityLocked(mCurrentUser);
}
}
也就是去加载HomeActivity,Android不像其它系统1样,将1个固定的Activity作为主界面程序加载,而是在AmS的starHomeActiviyLocked()中,系统发出1个catagory字段包括CATEGORY_HOME的intent。以下:
intent.setComponent(mTopComponent);
if (mFactoryTest != SystemServer.FACTORY_TEST_LOW_LEVEL) {
intent.addCategory(Intent.CATEGORY_HOME);
}
不管是哪一个利用程序,只要声明自己为该类型后,那就能够被认为是Home程序,系统并没有选取任何1个“Home”程序,而是将这个权利交给了用户,用户的选择决定了系统启动的Home,这个就是第1个启动的Acivity(当用户使用安卓系统启动后,会有1个选取界面的操作框,通过按键用户可以选取适合的Activity作为主界面)。当AmS要启动1个activity时,需要根据intent携带的activity名称,调用内部的PackageManager查询该名称对应的具体信息,如果存在就启动activity,否则返回失败。
当系统的Lancher界面启动显现完成后,系统的启动也就算完成了。
【本代码基于android4.2源码进行分析】