Android启动篇
http://blog.csdn.net/u011014707/article/details/46744867
Android启动过程相当复杂,从引导器加载系统映像、通过init.rc脚本进行初始化配置到系统完全启动均属于启动过程的范畴。在系统启动过程中,根据系统内存的情况,还涉及垃圾回收、进程终止等内容;就单个应用的启动而言,涉及APK包解析、证书校验、权限检查等内容。
1.系统的启动过程
在Android中,在BootLoader加载系统映像后,会通过system\core\rootdir\目录下的init.rc脚本进行初始化配置。在init.rc中可以配置系统时区、设置日志等级、设置全局环境变量、挂载文件系统、初始化网络配置、配置系统属性、启动守护进程等,具体的实现过程如下图所示:
启动过程中的配置是系统正常运行的基本保证。
(1)系统属性配置
系统属性包括多个方面,在开机启动时,Android将进行一系列的配置。
配置系统时区:在系统启动之初,最重要的工作自然是进行环境配置,其中最重要的配置是时间配置。Android默认设置系统时区为GMT 0。设置系统时区的方法是:sysclktz 0 //北京位于东八区,北京时间为GTM 8
设置日志登记:在Android中,日志分为多个等级,包括2(VERBOSE)、3(DEBUG)、4(INFO)、5(WARN)、6(ERROR)、7(ASSERT)。通常并不是所有日志均具有重要价值,因此必须设置控制台输出的日志等级,默认为3。设置日志登记为3的方法是:loglevel 3。
设置全局变量:在系统的启动过程中,Android需要设置的全局变量包括PATH、LD_LIBRARY_PATH、ANDROID_BOOTLOGO、ANDROID_ROOT、ANDROID_ASSETS、ANDROID_DATA、EXTERNAL_STORAGE、ASEC_MOUNTPOINT、LOOP_MOUNTPOINT和BOOTCLASSPATH等,具体如下:
export PATH /sbin:/vendor/bin:/system/sbin:/system/bin:/system/xbin
export LD_LIBRARY_PATH /vendor/lib:/system/lib
export ANDROID_BOOTLOGO 1
export ANDROID_ROOT /system
export ANDROID_ASSETS /system/app /加载应用的路径
export ANDROID_DATA /data
export EXTERNAL_STORAGE /mnt/sdcard
export ASEC_MOUNTPOINT /mnt/asec
export LOOP_MOUNTPOINT /mnt/obb
export BOOTCLASSPATH /system/framework/core.jar:/system/framework/bouncycastle.jar:/system/framework/ext.jar:/system/framework/framework.jar:/system/framework/android.policy.jar:/system/framework/services.jar:/system/framework/core-junit.jar
初始化网络配置:在Android中,网络配置包括对io和其他网络接入点的信息进行配置,配置网络参数的工具是ifup,具体方法是:
ifup lo
hostname localhost
domainname localdomain
lo的网络参数配置完成后的其他相关问题可查阅init.goldfish.rc。
配置系统属性:在Android中,系统属性是非常重要的一个概念,可以在编译脚本中对其进行设置。ActivityManagerService中用到的一些配置oom_adj值的系统属性如下:
setprop ro.POREGROUND_APP_ADJ 0 //前台进程
setprop ro.VISIBLE_APP_ADJ 1 //可视进程
setprop ro.PERCEPTIBLE_APP_ADJ 2 //感知进程
setprop ro.HEAVT_WEIGHT_APP_ADJ 3 //重量级进程
setprop ro.SECONDARY_SERVER_ADJ 4 //后台服务
setprop ro.BACKUP_APP_ADJ 5 //备份进程
setprop ro.HOME_APP_ADJ 6 //启动器
setprop ro.HIDDEN_APP_MIN_ADJ 7 //隐藏进程
setprop ro.EMPTY_APP_ADJ 15 //空进程
oom_adj值通常被Android特有的内存管理驱动Low memory killer使用,它会在系统内存低于设定值时释放相应进程,保证系统的稳定运行。Low memory killer根据两个原则(进程的重要性和释放这个进程可获取的空闲内存数量)来决定释放的进程。oom_adj值越小,表示该类型的重要性越高。在oom_adj相同的情况下,占用内存大的进程优先被撤销,进程占用的内存可以通过get_mm_rss进行判断。
(2)文件系统挂载
配置好文件系统分区并设置好分区表后,在设备实际启动前,需要挂载文件系统。
1)创建挂载点并设置权限
完成环境变量设置后,接下来就是创建挂载点并根据安全需要设置相应的权限,这是非常重要的一步。对于敏感信息,应避免普通用户对其拥有可写甚至可读的权限。权限的主要设置方法如下:
mkdir /mnt 0775 root system
mkdir /mnt/sdcard 0000 system system
symlink /mnt/sdcard /sdcard
mkdir /system
mkdir /data 0771 system system
mkdir /cache 0770 system cache
mkdir /config 0500 root root
从以上设置可以看出,Android拥有两个比较重要的用户权限,即root、system,其中root权限是最高的,对于系统和读写的路径至少应设置system权限。事实上目前刷机盛行,虽然OEM设置诸多屏障,但是仍有人能轻易获得Android的root权限,甚至还开发出了专门破解root权限的应用,这在一定程度上使Android的安全性让人担忧。
2)挂载文件系统
挂载点配置完成后,就该挂载文件系统了。目前Android默认的几个文件系统为system、date、cache等,挂载文件系统的方法如下:
mount yaffs2 mtd@system /system
mount yaffs2 mtd@system /system ro remount
mount yaffs2 mtd@userdata /data nosuid nodev
mount yaffs2 mtd@cache /cache nosuid nodev
在默认情况下,Android支持的文件系统类型为yaffs2(当然针对/tmp,Android采用的是tmpfs文件系统,此处为狭义理解),当然OEM厂商可以根据自己的需要自行制作其他类型的文件系统进行挂载。
至于分区大小,各厂商可根据自己的实际情况确定。
(3)守护进程启动
守护进程时运行在后台的Android核心的进程,主要包括servicemanager、vold、netd、debuggerd、ril-daemon、zygote、drm、drmio、media、bootanim、dbus、bluetoothd、hfag、hsag、opush、pbap、installd、flash_recovery、racoon、mtpd、keystore、dumpstate等。
1)守护进程的配置
由于不同守护进程之间可能存在依赖关系,或者守护进程对其他配置存在依赖,在启动守护进程时,需要做些配置。在system\core\init\目录下的readme.txt中介绍了包括守护进程在内的启动项配置方法,其中守护进程的配置方法如下:
service <name> <pathname> [<argument>] *
<option>
<option>
Critical:关键服务,如果这类服务在4分钟内4次退出,系统将重启进入恢复模式。
Disabled: 服务不能根据类名自动重启,必须显示启动。
setenv<name> <value>:设置环境变量。
socket<name><type><perm>[<user>[<group>]]:创建套接字,type可以为dgram、stream、seqpacket、user和group默认为0.
user<username>:服务所属的用户名,默认为root。
group<groupname>[<groupname>]*:服务所属的组名,默认为root。
oneshot:当服务重启时,执行相应的命令。
class<name>:服务的类名,归属于同一类的服务必须一起启动或关闭,默认的类为default。
2)守护进程的启动
(1)servicemanager
servicemanager是系统服务的管理器,它通过一个HashMap<String, IBinder>来管理系统服务。当servicemanager重启时,会导致zygote和media重启。下面是servicemanager的启动配置:
service servicemanager /system/bin/servicemanager
user system
critical
onrestart restart zygote
onrestart restart media
(2)vold的配置
vold(volume daemon)主要用来处理热插拔,其实际上是负责完成系统的CDROM、USB大容量存储和MMC卡等扩展存储的挂载任务的守护进程。vold和linux标准的udev类似,均通过sysfs的内核和用户层提供通信。下面是vold的启动配置:
service vold /sysyem/bin/vold
socket vold stream 0660 root mount
ioprio be 2
热插拔的处理框架如下图:
和vold相关的代码主要位于system/vold目录下,有兴趣的读者可以做进一步的研究。
(3)netd
netd(network daemon)主要用来监控网络状态,进行网络管理,其实现位于system\netd目录下,其启动配置如下:
service netd /system/bin/netd
socket netd stream 0660 root system
socket dnsproxyd stream 0660 root inet
(4)debuggerd
debuggerd(debugger daemon)主要用于调试,启动后会监听UNIX套接字Android:deguggerd,其实现位于system\core\debuggerd目录下,其启动配置如下:
service debuggerd /system/bin/debuggerd
(5)ril-daemon
RIL守护进程会初始化芯片厂商的RIL,管理所有来自Android通信服务的通信,通过套套接字实现与芯片厂商的RIL的通信,其实现与hardware/ril/rild无关,其启动配置如下:
service ril-daemon /system/bin/rild
socket rild stream 660 root radio
socket rild-debug stream 660 radio system
user root
group radip cache inet misc audio sdcard_rw
(6)zygote
zygote服务是Android启动后启动的第一个Linux进程,其他的Linux进程,比如各个应用,均是由zygote产生的。关于zygote的实现可以参考ZygoteInit.Java。zygote服务的重启会导致media和netd服务的重启,其启动配置如下:
service zygote /system/bin/app_process -Xzygote /system/bin --zygote --start -system-server
socket zygote stream 666
onrestart write /sys/android_power/request_state wake
onrestart write /sys/power/state on
onrestart restart media
onrestart restart netd
(7)drm
drm 用于数字版权保护,其启动配置如下:
service drm /system/bin/drmserver
user drm
group system root inet
(8)drmio
drmio同样用于数字版权保护,其启动配置如下:
service drmio /system/bin/drmioserver
user drmio
(9)media
media服务是提供多媒体服务的守护进程,它会启动AudioFlinger、MediaPlayerService、CameraService、AudioPolicyService等服务。media服务的入口实现如下:
int main(int argc, char**argv)
{
sp<PrecessState> proc(ProcessState::self());
sp<IServiceManager> sm=defaultServiceManager();
AudioFlinger::instantiate();
MediaPlayerService::instantiate();
CameraService::instantiate();
AudioPolicyService::instantate();
ProcessState::self()->startThreadPool();
IPCThreadState::self()->joinThreadPool();
}
media服务的启动配置如下:
service media /system/bin/mediaserver
user media
group system audio camera graphics inet net_bt net_bt_admin net_raw
ioprio rt 4
(10)bootanim
boottanim服务即所谓的开机动画服务,其具体实现位于BootAnimation.cpp中。SurfaceFlinger在完成准备工作后会在其readToRun()方法中通过property_set("ctl.start", "bootanim")启动bootanim服务。在bootanim服务启动的过程中,会加载用户开机动画和系统开机动画,这些动画均为ZIP压缩文件,所在位置为\data\local\bootanimation.zip和\system\media\bootanimation.zip,动画中图片的格式应为RGB565,其实现位于frameworks\base\cmds\bootanimation目录中。bootanim服务的启动配置如下:
service bootanim /system/bin/bootanimation
user graphics
group graphics
disabled
oneshot
(11)dbus
dbus和OpenBinder一样是进程间的通信机制,在Linux中dbus应用的非常广泛,如著名的Linux桌面环境Qt和Gnome。在Android中,进程间通信主要利用的是OpenBinder,dbus仅用于蓝牙协议栈Bluez中。dbus的启动配置如下:
service dbus /system/bin/dbus-daemon --system --nofork
socket dbus stream 660 bluetooth bluetooth
user bluetooth
group bluetooth net_bt_admin
(12)bluetoothd
bluetoothd是Bluez的守护进程,默认是不启动的。bluetoothd的启动配置如下:
service bluetoothd /system/bin/bluetoothd -n
socket bluetooth stream 660 bluetooth bluetooth
socket dbus_bluetooth stream 660 bluetooth bluetooth bluetooth
group bluetooth net_bt_admin_misc
disabled
(13)hfag
hfag也用于Bluez,作用是启动蓝牙免提音频网关(Bluetooth Handsfree Audio Gateway)。hfag默认是不启动的,其启动配置如下:
service hfag /system/bin/sdptool add --channel=10 HFAG
user bluetooth
group bluetooth net_bt_admin
disabled
oneshot
(14)hsag
hsag也用于Bluez,作用是启动蓝牙耳机音频网关(Bluetooth headset audio gateway)。hsag默认是不启动的,其启动配置如下:
service hsag /system/bin/sdptool add --channel=11 HSAG
user bluetooth
groupbluetooth net_bt_admin
disabled
oneshot
(15)opush
opush即OBM Push,实现了Exchange ActiveSync服务器协议,同样也用于Bluez。opush默认是不启动的,其启动配置如下:
service opush /system/bin/sdptool add --channel=12 OPUSH
user bluetooth
group bluetooth net_bt_admin
disabled
oneshot
(16)pbap
pbap即电话簿访问协议(Phonebook Access Profile),同样也用于Bluez。pbap默认是不启动的,其启动配置如下:
service pbap /system/bin/sdptool add --channel=19 PBAP
user bluetooth
group bluetooth net_bt_admin
disabled
oneshot
(17)installd
install即安装守护进程,用于APK的安装,其启动配置如下:
service installd /system/bin/installd
socket installd stream 600 system system
(18)flash_recovery
flash_recovery用于系统发生故障时的恢复,其启动配置如下:
service flash_recovery /system/etc/install_recovery.sh
oneshot
(19)racoon
racoon是VPN的守护进程,其启动配置如下:
service racoon /system/bin/racoon
socket racoon stream 600 system system group net_admin
disabled
oneshot
(20)mtpd
mtpd是媒体传输协议(Media Transfer Protocol, MTP)的守护进程,其启动配置如下:
service mtpd /system/bin/mtpd
scoket mtpd stream 600 system system
user vpn
group vpn net_admin net_raw
disabled
oneshot
(21)keystore
keystore和数字签名证书有关,其启动配置如下:
service dumpstate /system/bin/dumpstate -s
socket dumpstate stream 0660 shell log
disabled
oneshot
2.应用的启动过程
AAPT(Android Asset Packaging Tool)允许开发者查看、创建、更新与ZIP兼容的压缩文件(ZIP、JAR、APK),同时还将资源编译到断言(assert)中。
通过WinRAR查看APK包可以看到,APK包大致包括res、AndroidManifest.xml、classes.dex、resource.arsc、META-INF和libs等几项。其中res文件夹包含的是资源文件,res文件夹下的XML是已经编译过的,减小了存储空间。AndroidManifest.xml为应用的配置文件,但是已经编译过。而classes.dex为应用的DEX字节码文件,是CLASS字节码去除冗余信息后的集合。resource.arsc为资源文件,在Android 2.3前采用的是UTF-16编码,在Android 2.3及以后的版本中,采用的是UTF-8编码,这样改变以为这在Android 2.3及以后版本中,基于英文应用的APK包会比原来的小,而基于中文应用的APK包会比原来大些。这是因为在UTF-16编码中,中文和英文均采用了2字节编码,而在UTF-8中,英文占用1字节,而中文要占用3字节。在META-INF中存放的是Android的数字签名证书。在libs中存放的通常是JAR和原生代码生成的共享库文件。
需要说明的是,在Android中,采用的Java混淆器为开源的ProGuard 4.4。ProGuard可以在一定程度上防止别有用意的人的窥视,但对于需要保护的敏感信息,其安全性是无法保证的,因为专业人员极易渗透其信息含义。所以,Android发展至今,盗版应用泛滥,这对于保护开发者的知识产权和商业机密十分不利。如果需要增强实现的安全性,建议将敏感信息放置在原生代码中实现,尽量不要在Java代码中实现。原生代码和Java代码的互操作需要通过JNI进行。
Android生成的Java字节码为DEX字节码,而非传统的CLASS字节码,但是在编译过程中,Android会先将Java文件编译为CLASS字节码,然后再将CLASS字节码转化为DEX字节码。商业因素是制定这一策略的主要因素。
DEX字节码借鉴了Linux的BusyBox和Qt中的SingleBuild编译模式采用的最大限度复用信息的设计思想,能够有效地减少生成文件的大小。
Android不允许安装同名的不同程序,否则会提示证书有误。
(1)应用的启动配置
对于所有应用程序而言,均有唯一的入口,在Java和C/C++中,这个入口均为main函数。
在Android中,从AndroidManifest.xml中可以看到,Android应用由一个继承了ContextWrapper的Application构成,其中Application有Activity、Service、Receiver、Provider、user-library等组件构成。
Android应用程序的组件之间的关系如下图:
需要注意的是,应用程序并非必须拥有Activity,如果仅是后台程序,那么仅有Application也是可以的。下面是一个基于Service的应用的实现:
public class NfcService extends Application{
public void onCreate(){
super.onCreate();
}
public void onTerminate(){
super.onTerminate();
}
}
在AndroidManifest.xml文件中,对应用程序的定义如下:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android=http://schemas.android.com/apk/res/android
package="com.android.nfc"
android:sharedUserId="android.uid.nfc"
android:sharedUserLabel="@string/nfcUserLabel">
<application android:name=".NfcService"
android:icon="@drawable/icon"
android:lable="@string/app_name"
android:persistent="ture">
</application>
</manifest>
如果不希望应用在系统低内存时被系统销毁,需将application标签的android:persistent属性设置为true。
(2)应用的启动过程
要启动一个应用,首先要创建一个进程,然后启动UI主线程,接着打开Activity,具体流程见下图所示:
Android进程分为zygote进程和普通进程。当启动一个应用时,Dalvik虚拟机会先通过NativeStart接口初始化JNI,为通过zygote进程创建新进程做好准备。原生代码的启动位于app_main.cpp中,具体如下:
if(0==strcmp("--zygote", arg)){//zygote进程
bool startSystemServer=(i<argc)?strcmp(argv[i], "--start-system-server")==0, false;
setArgv0(argv0,"zygote");
set_process_name("zygote");
runtime.start("com.android.internal.os.ZygoteInit", startSystemServer);
}else{//普通进程
set_Process_name(argv0);
runtime.mClassName=arg;
runtime.mArgC=argc-i;
runtime.mArgV=argv+i;
runtime.start(); //调用AndroidRuntime::start()方法
}
在AndroidRuntime.cpp中,需要设置环境变量、启动虚拟器等,具体实现如下:
void AndroidRuntime::start(const char* className, const bool startSystemServer)
{
char* slashClassName=NULL;
char* cp;
JNIEnv* env;
blockSigpipe();
if(startSystemServer){
}
const char* rootDir=getenv("ANDROID_ROOT"); //环境变量
if(rootDir==NULL){
rootDir="/system";
if(!hasDir("/system")){
goto bail;
}
setenv("ANDROID_ROOT", rootDir, 1);
}
if(startVm(&mJavaVM, &env)!=0) //启动虚拟机
goto bail;
if(startReg(env) < 0){ //注册方法
goto bail;
}
}
}
所有的普通进程均是通过zygote进程创建出来的,新进程的创建主要是ZygoteInit()中进行的。方法如下:
public static void main(String argv[]){
try{
VMRuntime.getRuntime().setMinimumHeapSize(5*1024*1024); //设置最小堆
SamplingProfilerIntegration.start();
registerZygoteSocket(); //注册套接字
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_START, SystemClock.uptimeMillis());
preloadClasses(); //加载公共实现
preloadResources(); //加载公共资源
EventLog.writeEvent(LOG_BOOT_PROGRESS_PRELOAD_END, SystemClock.uptimeMillis());
SamplingProfilerIntegration.writeZygoteSnapshot();
gc(); //启动垃圾回收
if(argv.length!=2){
throw new RuntimeException(argv[0] + USAGE_STRING);
}
if(argv[1].equals("true")){ //如果是系统服务
startSystemServer(); //启动系统服务
else if(!argv[1].equals("false")){ //普通进程
throw new runtimeException(argv[0] + USAGE_STRING);
}
if(ZYGOTE_FORK_MODE){ //Fork进程,默认为false
runForkMode();
}else { //构建事件循环
runSelectLoopMode();
}
closeServerSocket();
}catch (MethodAndArgsCaller caller){
caller.run(); //抛出MethodAndArgsCaller异常
}catch(RuntimeException ex){
closeServerSocket();
throw ex;
}
}
runSelectLoopMode()方法的本质为启动一个死循环,利用zygoteConnection通过套接字来处理消息。
当触发MethodAndArgsCaller异常时,系统会通过Method的invoke方法调用invokeNative方法创建出UI主线程Activitythread,以及设置事件循环等。
当收到LAUNCH_ACTIVITY事件时,ActivityThread的handleMessage方法会处理消息并调用handleLaunchActivity方法的实现。
在启动Activity的过程中,为了进行JUnit测试,具体Activity的onCreate方法的调用是通过Instrumentation的callActivityOnCreate方法进行的,在应用的启动过程中会不断调用Instrumentation。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· 开发者必知的日志记录最佳实践
· SQL Server 2025 AI相关能力初探
· Linux系列:如何用 C#调用 C方法造成内存泄露
· 无需6万激活码!GitHub神秘组织3小时极速复刻Manus,手把手教你使用OpenManus搭建本
· Manus爆火,是硬核还是营销?
· 终于写完轮子一部分:tcp代理 了,记录一下
· 别再用vector<bool>了!Google高级工程师:这可能是STL最大的设计失误
· 单元测试从入门到精通