Android大脑--systemserver进程

用心坚持输出易读、有趣、有深度、高质量、体系化的技术文章,技术文章也可以有温度。

本文摘要

系统native进程的文章就先告一段落了,从这篇文章开始写Java层的文章,本文同样延续自述的方式来介绍systemserver进程,通过本文您将了解到systemserver进程是啥?它包含那么多的服务,是如何管理它们的?如何启动它们的?(文中代码基于Android13)

本文大纲

我是谁

我的出生

我的启动

总结

1. 我是谁

我是一个进程,是一个既可以运行Java代码也可以运行C和C++代码的全能型选手,不像我的“叔叔辈儿”如logd进程、lmkd进程只能运行C和C++代码。我拥有全能型能力还要感谢我的“父亲”zygote,因为他是全能型的,所以我才把这些能力继承了过来。

我的uid是1000,uid是啥呢?它与你们人类的身份证一样,每个安装到Android设备上的程序都有一个uid,只要apk被安装就分配一个唯一的uid,只要apk不卸载,这个uid是不会变化的。uid为0也就是root用户的程序它可是拥有无限超级能力。

我其实还有一个真名叫system_process,我觉得这个名字不霸气、不能体现我的职责,因此我对外让大家都称呼我为systemserver,你看这名字多好系统服务,这名字多霸气、多威武。

1.1 我的父亲

我的“爷爷”init进程有非常非常多的孩子,这或许是偶像的力量吧,我的“父亲”zygote也有非常非常多的孩子。凡是Android中能运行Java代码的进程都是zygote的孩子,并且它们也都是我的“弟弟”,为啥是弟弟呢?因为我是zygote的第一个孩子,也就是“长子”。

其实我对我的“父亲”zygote也是有蛮多怨言的,为啥这样说呢?因为他只负责孵化子进程,而孵化完毕后,他就做一个甩手掌柜的,对于子进程不管不顾,他甚至完全不知道自己孵化了多少子进程,而这些任务却偏偏都落在了我的身上,我还得负责管理这些子进程。这或许就是“”长兄如父“”的完美体现吧。

1.2 我的弟弟们

我所有的“弟弟们”,它们若想要“出生”的话,都需要经过我向zygote发送孵化它们的请求,zygote孵化出它们后,我负责来管理它们,比如当Android设备处于低内存状态时候,我会尝试把最不常用、处于后台的子进程杀掉。**不知道你们有没有发现个问题,我的uid是1000并非是0,那我怎么具有杀其他进程的能力的呢?**先卖个关子,在后面会介绍到我如何具有此能力。

我不单单充当“长子”的角色,我还把我自己比作Android的大脑,这可不是我骄傲事实确实是这样的,所有可运行Java代码进程都需要以我为中心,我离了它们是可以的,它们可是万万离不开我,比如这些进程想要启动Activity,那需要经过我的ActivityTaskManagerService服务的校验,再比如某个进程需要发出振动的话,那也需要经过我的VibratorManagerService服务进而由它发向hal层发出振动的命令。

(如下图展示app进程与systemserver进程的一些交互信息)

image

大家可别把我想象的如超人那样,我也不是事无巨细的“诸葛亮”,我能完成这么多复杂的工作,完全是靠我拥有的各种service (服务),这些service会处理各自擅长的事情,像上面提到的ActivityTaskManagerService和VibratorManagerService就是其中两个service,那来看下我拥有的service吧。

1.3 拥有的service

下图是我拥有的部分service
image

那就来简单介绍下部分service吧

Installer

在installd进程中介绍过它,它与apk的安装有关系,它对installd进程做了封装,这样就可以在Java层使用installd的功能了。

PowerStatsService

想要知道电量的使用情况,可以找它啊,它可是电量使用情况的专家

ActivityTaskManagerService

该服务与启动Activity有非常大的关系,进程中启动Activity必定要经过该服务

PowerManagerService

电源管理服务是一个非常重要的系统服务,它负责管理系统的电源状态,包括设备的唤醒、休眠、屏幕亮度调节、电池电量管理等

ThermalManagerService

该服务是温度监控服务,当温度超过一定阈值后,手机会做出一些策略比如cpu降频操作,保证手机温度不至于一直上升

DisplayManagerService

该服务负责管理设备上的所有显示相关的资源和操作。它负责屏幕的开启与关闭、屏幕亮度的调整、显示分辨率的变更、屏幕旋转以及多窗口管理等任务

UserManagerService

该服务主要是管理设备用户,一个设备默认情况下有一个主用户它的id是0,可以为设备增加用户,用户之间的数据是隔离的

好了,由于时间关系,我不可能把所有的service都介绍完毕,就先介绍以上这些service

service在什么环境下工作呢?

先来说下这里的环境一词,其实指的是在哪个线程工做。

首先需要肯定的一点是不可能所有的service都在我systemserver的唯一的一个主线程里面工作,如果是这样这些service肯定会出现各种问题,其次是性能低下。

其实对于这一点我是没有制定规则的,每个service的复杂程度不一样,因此有的service会启动自己的线程来做工作,而有的service是与别的service共享一个线程来做工作,有的service就在主线程里面做自己的工作。

1.4 service如何让使用者使用

我既然拥有这么多的service,那如何让使用者使用这些service的能力呢?

如果使用者都与我systemserver位于同一进程的话,这些事情就特别好办了,但却不是这样的使用者它们绝大多数都是位于别的进程,那这种情况就有些棘手了。

虽然棘手,但是是有解的,大家上眼了先看下下面这张类图

image

看了这张图是不是有一种似曾相识的感觉,没错这就是使用aidl生成的类图结构,大家都知道aidl其实是为binder通信设计的,那service如果让使用者使用的话那就是使用binder通信了,先暂停一下,继续把上面的类图交代清楚 (熟悉的小伙伴可以跳过这部分)

IXXXManager

它是一个接口,定义了该service提供的能力,它继承了IInterface接口。每个service都有自己的对应接口,比如ActivityManagerService对应的是IActivityManager接口、ActivityTaskManagerService对应的是IActivityTaskManager

IXXXManager.Stub

该类是一个抽象类,它实现了IXXXManager接口,同时也继承了Binder类。

XXXService

该类就是每个service了,每个service又继承了IXXXManager.Stub,也就是说每个service其实又是Binder的子类。下面是几个例子

public class ActivityTaskManagerService extends IActivityTaskManager.Stub public class ActivityManagerService extends IActivityManager.Stub
IXXXManager.Stub.Proxy

该类如它的名字就是一个代理类,它实现了IXXXManager接口,该类有一个属性mRemote它一般是BinderProxy类型的。而该类是让使用者来使用的,调用该类的相应方法,就可以通过binder通信最终调用到对应service的能力了。

同样每个service都有自己对应的IXXXManager.Stub.Proxy,比如ActivityManagerService对应的是IActivityManager.Stub.Proxy、ActivityTaskManagerService对应的是IActivityTaskManager.Stub.Proxy

上面的类图就是每个service让使用者使用的一个框架,说直白点就是使用了binder通信,因为binder通信是client/server模式,每个service是Binder的子类,那它就是server端,而使用者要想使用对应service的能力那就需要使用对应的IXXXManager.Stub.Proxy类了。

既然每个service是一个Binder,那就有必要把自己注册在ServiceManager中,只有注册了使用者才可以使用。(如下伪代码)

XXXService service = new XXXService()
ServiceManager.addService("XXXService",service)

使用者使用的话从ServiceManager中获取对应的service的BinderProxy对象,并把它作为IXXXManager.Stub.Proxy类的参数,来构造IXXXManager.Stub.Proxy这样的一个对象 ,通过该对象即可使用service的能力。(如下伪代码)

BinderProxy bp = ServiceManager.getService("XXXService")
IXXX.Stub.Proxy proxy = new IXXX.Stub.Proxy(bp)
# 就可以调用proxy对象来使用service的功能了

1.5 小结

我是一个既能运行Java代码又能运行C/C++代码的全能型进程,我在所有可运行Java代码的进程中的地位犹如人类的大脑,那可是妥妥的C位。我的地位虽然如此高贵,但我可不是一个集权者,我拥有很多的service,是它们替我分担了我的职责,它们有条不紊的处理着各种需求。其他可运行Java代码的进程要想使用这些service的能力,那就需要通过binder通信来使用。

好了,关于我就先介绍到此,作为C位的我,大家对我的出生一定很感兴趣,那就来讲下我的出生吧。

2. 我的出生

我的出生其实是被动的,为什么说是被动的呢?我的出生并不是我主动发起的,像我的“叔叔辈儿”、“弟弟们”他们的出生基本都是由他们主动发起的,比如logd进程会主动配置rc脚本文件,在把脚本文件交给init进程。而我的出生是在我完全不知晓的情况下由我的“父亲”zygote直接决定的。

我的出生大致分为三步:孵化前孵化孵化后,那就按这三步来讲下我的出生吧

2.1 孵化前

在孵化前,我的“父亲”zygote已经做了很多的工作的:加载JVM、加载公共的jni方法、提前预加载classes、预加载resource、预加载字体等。

其中预加载classes,会把这些classes加载到BootClassLoader.getInstance()对象中,该ClassLoader可是顶级ClassLoader,还有预加载的这些classes主要是framework.jar下面的类。预加载resource,会把这些resource加载到Resources.getSystem()对象中。

除了上面的工作,还有一个工作就是准备孵化时候要用到的参数,这些参数有几个是需要重点介绍下的。

setuid
--setuid=1000

如上设置了我systemserver进程的uid为1000

setgid
--setgid=1000

设置了我systemserver进程所属的组id是1000

capabilities

Linux Capabilities是Linux操作系统中的一种权限管理机制,它允许对进程的权限进行细粒度的控制,以实现最小特权原则。这一机制将root用户的特权划分为具体的功能集,允许将部分root特权授予非root进程,从而增强了系统的安全性。

Linux Capabilities是在Linux内核2.2之后引入的,它将传统上与超级用户root(UID=0)关联的特权细分为不同的功能组。这些功能组作为线程(Linux中并不真正区分进程和线程)的属性存在,每个功能组都可以独立启用和禁用。这样一来,权限检查的过程就变成了:在执行特权操作时,如果线程的有效身份不是root,就去检查其是否具有该特权操作所对应的capabilities,并以此为依据,决定是否可以执行特权操作

上面是capabilities功能的介绍,用一句话概括:就是说非root用户进程也可以通过设置capabilities,来让该进程具有相应的特权。

还记得我systemserver可以杀掉一些处于后台的进程来释放内存吗?杀掉其他进程的特权就是通过capabilities设置的,当然还设置了别的特权,如下相关代码:

long capabilities = posixCapabilitiesAsBits(//主要用于控制进程对共享内存片段的锁定能力            OsConstants.CAP_IPC_LOCK,//杀掉其他进程的能力OsConstants.CAP_KILL,//用于控制对网络配置和管理的访问权限OsConstants.CAP_NET_ADMIN,//它允许进程绑定到1024以下的端口号。这些端口号通常被认为是特权端口OsConstants.CAP_NET_BIND_SERVICE,//它允许进程发送广播消息到网络OsConstants.CAP_NET_BROADCAST,//它允许进程创建RAW和PACKET套接字以及进行任意地址的绑定OsConstants.CAP_NET_RAW,//它允许用户或进程加载(或卸载)内核模块OsConstants.CAP_SYS_MODULE,//它允许进程调整其他进程或自身进程的nice值,从而影响其调度优先级OsConstants.CAP_SYS_NICE,//它允许进程跟踪系统上的任何其他进程,这种能力通常与调试和安全审计相关OsConstants.CAP_SYS_PTRACE,//它允许进程设置系统时间OsConstants.CAP_SYS_TIME,//它允许进程进行TTY设备的配置任务。OsConstants.CAP_SYS_TTY_CONFIG,//它允许进程设置唤醒系统(或唤醒挂起系统)的定时器OsConstants.CAP_WAKE_ALARM,//它允许进程阻止系统进入挂起(suspend)状态OsConstants.CAP_BLOCK_SUSPEND);

在配置了上面的这些capabilities后,我systemserver也就具有了这些特权了。

入口类
com.android.server.SystemServer

入口类就是当systemserver进程被fork成功后,最先开始执行的类。如上这个类就是SystemServer类

这些参数配置完毕后,孵化前的准备工作就完成了。

下面是相关代码,请自行取阅:

//ZygoteInit类private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {long capabilities = posixCapabilitiesAsBits(OsConstants.CAP_IPC_LOCK,OsConstants.CAP_KILL,OsConstants.CAP_NET_ADMIN,OsConstants.CAP_NET_BIND_SERVICE,OsConstants.CAP_NET_BROADCAST,OsConstants.CAP_NET_RAW,OsConstants.CAP_SYS_MODULE,OsConstants.CAP_SYS_NICE,OsConstants.CAP_SYS_PTRACE,OsConstants.CAP_SYS_TIME,OsConstants.CAP_SYS_TTY_CONFIG,OsConstants.CAP_WAKE_ALARM,OsConstants.CAP_BLOCK_SUSPEND);省略代码······String[] args = {"--setuid=1000","--setgid=1000","--setgroups=1001,1002,1003,1004,1005,1006,1007,1008,1009,1010,1018,1021,1023,"+ "1024,1032,1065,3001,3002,3003,3005,3006,3007,3009,3010,3011,3012","--capabilities=" + capabilities + "," + capabilities,"--nice-name=system_server","--runtime-args","--target-sdk-version=" + VMRuntime.SDK_VERSION_CUR_DEVELOPMENT,"com.android.server.SystemServer",};ZygoteArguments parsedArgs;int pid;try {ZygoteCommandBuffer commandBuffer = new ZygoteCommandBuffer(args);try {parsedArgs = ZygoteArguments.getInstance(commandBuffer);} catch (EOFException e) {throw new AssertionError("Unexpected argument error for forking system server", e);}commandBuffer.close();// 设置允许所有的app可以调试Zygote.applyDebuggerSystemProperty(parsedArgs); Zygote.applyInvokeWithSystemProperty(parsedArgs);省略其他代码······} catch (IllegalArgumentException ex) {throw new RuntimeException(ex);}省略其他代码······}

2.2 孵化

现在进入最激动人心的阶段孵化,因为孵化前已经把参数等信息都准备好了,那孵化就是调用系统的fork方法来创建子进程,当然在调用fork方法的时候是需要把从zygote进程继承来的fd关闭掉的,否则会影响原先的父进程。在fork成功后,会把传递的这些参数设置给我systemserver进程。

因为fork机制采用的是写时复制,即如果子进程没有对父子进程共享的区域进行操作的话,则这片区域是共享的。我的“父亲”zygote在孵化之前做的准备工作:如加载JVM、加载jni方法、预加载classes、预加载resource,都是我与我父亲共享的。

上面的话是不是有些懵,还是看下图吧,毕竟一图胜千言
image

在孵化之前zygote加载了JVM、预加载的classes加载到了BootClassLoader.getInstance()方法返回的BootClassLoader对象中、预加载的resources加载到了Resources.getSystem()方法返回的Resources对象中,当然还有别的。

而在孵化之后,systemserver进程就把zygote进程中的JVM实例、BootClassLoader.getInstance()返回的对象、Resources.getSystem()方法返回的Resources对象及其他的实例都完完全全的共享过来了。这也就是zygote提前做各种准备能在子进程中生效的具体原因。

2.3 孵化后

在孵化后会有关闭socket创建ClassLoader打开binder进入入口类这几件事情要做,那接下来就介绍下。

2.3.1 关闭socket

因为fork机制创建子进程,会把父进程打开的socket对应的fd也继承过来,因此需要把继承过来的socket对应的fd关闭掉。zygote是会启动一个ServerSocket,如果想要孵化子进程,则其他进程与该ServerSocket建立连接即可。因此systemserver进程需要把继承过来的ServerSocket关闭掉。

相关的代码,请自行取阅:

  //ZygoteInit类private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {省略代码······//在fork成功后,下面的代码会分别由zygote的线程和systemserver的线程执行,//pid==0 代表 systemserver的线程会执行if (pid == 0) {省略代码······//在systemserver进程把zygote socket关闭掉,因为systemserver进程不需要它//查看下面的closeServerSocket方法zygoteServer.closeServerSocket();return handleSystemServerProcess(parsedArgs);}return null;}//ZygoteServer类void closeServerSocket() {try {if (mZygoteSocket != null) {FileDescriptor fd = mZygoteSocket.getFileDescriptor();mZygoteSocket.close();if (fd != null && mCloseSocketFd) {Os.close(fd);}}} catch (IOException ex) {Log.e(TAG, "Zygote:  error closing sockets", ex);} catch (ErrnoException ex) {Log.e(TAG, "Zygote:  error closing descriptor", ex);}mZygoteSocket = null;}
2.3.2 创建ClassLoader

为啥要创建ClassLoader呢?

原因是这样的,当前被孵化出来的systemserver进程,它其实与zygote共享一个BootClassLoader对象,而该ClassLoader只是加载了进程之间公用的classes,而systemserver自己特有的各种classes在共享的BootClassLoader对象中是找不到的,比如入口类SystemServer在BootClassLoader找不到。那咋办呢,答案当然是创建自己的ClassLoader,并且把自己的各种jar、so文件路径设置给ClassLoader,同时该ClassLoader的parent是共享的BootClassLoader对象。这样通过自己的创建的ClassLoader就可以找到systemserver进程自己特有的classes,同时也能找到zygote之前预加载的classes。

下面是相关代码,请自行取阅:

  //ZygoteInit类private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {省略代码······//在fork成功后,下面的代码会分别由zygote的线程和systemserver的线程执行,//pid==0 代表 systemserver的线程会执行if (pid == 0) {省略代码······//查看下面handleSystemServerProcess方法return handleSystemServerProcess(parsedArgs);}return null;}private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {省略代码······if (parsedArgs.mInvokeWith != null) {省略代码······} else { //创建ClassLoaderClassLoader cl = getOrCreateSystemServerClassLoader();if (cl != null) {//为当前线程设置ClassLoaderThread.currentThread().setContextClassLoader(cl);}省略代码······}/* should never reach here */}
3.2.3 打开binder

打开binder这可是非常重要的一步,只有执行了这步被孵化的进程才具有了binder通信的能力,直接看代码吧

//ZygoteInit类private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {省略代码······//在fork成功后,下面的代码会分别由zygote的线程和systemserver的线程执行,//pid==0 代表 systemserver的线程会执行if (pid == 0) {省略代码······//查看下面handleSystemServerProcess方法return handleSystemServerProcess(parsedArgs);}return null;}private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {省略代码······if (parsedArgs.mInvokeWith != null) {省略代码······} else { 省略代码······//查看下面 zygoteInit 方法return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,parsedArgs.mDisabledCompatChanges,parsedArgs.mRemainingArgs, cl);}}public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,String[] argv, ClassLoader classLoader) {省略代码······//nativeZygoteInit 方法会调用到下面的 com_android_internal_os_ZygoteInit_nativeZygoteInit 方法ZygoteInit.nativeZygoteInit(); }//core/jni/AndroidRuntime.cppstatic void com_android_internal_os_ZygoteInit_nativeZygoteInit(JNIEnv* env, jobject clazz){//会调用到下面的onZygoteInitgCurRuntime->onZygoteInit();}//cmds/app_process/app_main.cppvirtual void onZygoteInit(){//打开binder驱动sp<ProcessState> proc = ProcessState::self();//启动binder线程池proc->startThreadPool();}
3.2.4 进入入口类

这是孵化后的最后一步,也是systemserver进入自己代码的起始点,还记得入口类是SystemServer吧,而这一步就是要进入SystemServer的main方法。

下面是相关方法,请自行取阅:

//ZygoteInit类private static Runnable forkSystemServer(String abiList, String socketName,ZygoteServer zygoteServer) {省略代码······//在fork成功后,下面的代码会分别由zygote的线程和systemserver的线程执行,//pid==0 代表 systemserver的线程会执行if (pid == 0) {省略代码······//查看下面handleSystemServerProcess方法return handleSystemServerProcess(parsedArgs);}return null;}private static Runnable handleSystemServerProcess(ZygoteArguments parsedArgs) {省略代码······if (parsedArgs.mInvokeWith != null) {省略代码······} else { 省略代码······//查看下面 zygoteInit 方法return ZygoteInit.zygoteInit(parsedArgs.mTargetSdkVersion,parsedArgs.mDisabledCompatChanges,parsedArgs.mRemainingArgs, cl);}}public static Runnable zygoteInit(int targetSdkVersion, long[] disabledCompatChanges,String[] argv, ClassLoader classLoader) {省略代码······//查看下面的 applicationInit方法return RuntimeInit.applicationInit(targetSdkVersion, disabledCompatChanges, argv,classLoader);}//RuntimeInit类protected static Runnable applicationInit(int targetSdkVersion, long[] disabledCompatChanges,String[] argv, ClassLoader classLoader) {省略代码······//查找 static main方法//查看下面的 findStaticMain 方法return findStaticMain(args.startClass, args.startArgs, classLoader);}protected static Runnable findStaticMain(String className, String[] argv,ClassLoader classLoader) {Class<?> cl;try {//查找SystemServer的Classcl = Class.forName(className, true, classLoader);} catch (ClassNotFoundException ex) {省略代码······}Method m;try {//查找SystemServer的main方法m = cl.getMethod("main", new Class[] { String[].class });} catch (NoSuchMethodException ex) {throw new RuntimeException("Missing static main on " + className, ex);} catch (SecurityException ex) {throw new RuntimeException("Problem getting static main on " + className, ex);}int modifiers = m.getModifiers();if (! (Modifier.isStatic(modifiers) && Modifier.isPublic(modifiers))) {throw new RuntimeException("Main method is not public and static on " + className);}//返回MethodAndArgsCaller实例,它持有找到的main方法的Method实例return new MethodAndArgsCaller(m, argv);}

2.4 小结

以上就是我的出生过程,我的“父亲”zygote为了加快子进程的启动速度,在孵化之前做了很多准备工作:加载JVM、加载通用jni方法、预加载classes、预加载resource等,而我systemserver进程在fork成功后与zygote共享在准备阶段产生的这些实例,同时我还打开了binder驱动,这样我才具有了binder通信能力,我还创建了自己的ClassLoader,并且进入了SystemServer类的main方法,至此我“出生”了。

3. 我的启动

我虽然“出生”了,但是我就像人类的婴儿一样,还没有任何的能力,因此我需要经历启动过程,只有经历了启动过程我才真正的“长大成人”。

3.1 解决多service带来的启动问题

大家都知道我有很多很多的service,而启动过程其实就是把所有的service启动起来,service多是好事也同时是坏事,这不在启动过程中就体现出来了,因为service多,在启动过程中肯定会出现某个service的启动要依赖于另外的service,而另外的service又要依赖于别的service;甚至会出现更复杂的情况,某个或多个service要依赖多个其他的service,这种依赖关系就像一团乱麻着实令人头疼。

针对这种情况,确实花费了我很多的脑细胞,想出了管理service对service分类分发启动阶段信号这三个办法来解决此问题。

3.1.1 管理service

管理service就是要对这些service进行管理,不管理确实会出现各种乱套,那该如何管理呢?

老规矩先看下我设计的类图吧
image

如图,我主要设计了SystemService和SystemServiceManager两个类,先来解释下这两个类

SystemService

每个service作为能力提供方的话是Binder类的子类,而要作为一个service被systemserver进程管理的话,那它需要继承SystemService类

该类是一个抽象类,它定义了一些service都会用到的方法比如publishBinderService,它的主要作用就是service可以把自己注册到ServiceManager中。

还定义了一些一些抽象方法,其中onStartonBootPhase特别类似Activity的生命周期方法。

onStart方法

onStart被调用的话,则代表当前的service可以调用publishBinderService方法,把自己注册到ServiceManager中。

onBootPhase方法

systemserver会通过onBootPhase方法告知service当前启动到了哪一步了。

onUserStarting方法

systemserver会通过该方法告知service当前是哪个用户正在启动,其他的onUserXXX方法也是类似作用。

图中ActivityManagerService类为啥没有继承SystemService呢?主要原因是Java只支持单继承,因为ActivityManagerService已经继承了IActivityManager.Stub类

SystemServiceManager

而该类的主要作用就是管理所有的SystemService,它的mService属性存储了所有启动的SystemService。同时它拥有几个简化创建service流程的方法,暂且拿下面这个方法举例子

startService(Class)

该方法把构建service对象添加service对象调用service的onStart方法这几个步骤都集中起来。启动一个service只需要调用该方法,service只需要实现自己的onStart方法逻辑,onStart方法具体啥时候调用,service不需要关心。

3.1.2 对service分类

对service进行分类,把service分为bootstrap servicescore servicesother services,如下图:

image

bootstrap services代表别的service对它们的依赖最多,因此需要最先启动。

3.1.3 分发启动阶段信号

分发启动阶段信号就是告知每个service当前处于启动的哪个阶段了,service根据相应的阶段信号可以做一些自己的事情。

分发启动阶段信号这个词如果不好理解的话,那用个例子来解释:在下载文件的时候,是不是会显示下载进度,而启动阶段信号其实与下载进度非常类似,就是告知service当前的启动进度是啥样。

3.1.4 小结

service继承SystemService,并且把它们都添加到SystemServiceManager,这样就可以把所有的service管理起来;再对service进行分类,按类别来分别启动它们;把启动阶段信号通过SystemServiceManager发送给所有的service,这样service根据启动阶段信号来做自己的事情。以上三种办法可以解决启动service混乱的问题。

3.2 启动

既然多service启动的问题解决了,那咱就进入正题开始介绍我的启动过程吧,我把启动过程分为启动前启动bootstrap services启动core services启动other services进入无休工作模式,当然还有启动Apex services这一步,因为它不是重点在这就不赘述了,那就从这几步来介绍启动。

3.2.1 启动前

启动前是需要做一些准备工作的设置binder线程池个数开启Looper加载android_servers可执行文件创建Context,那就依次来介绍下。

设置binder线程池个数

普通进程打开binder驱动后,能启动最大的binder线程个数是15个,前面也一直在强调我可不是普通的进程,更何况我拥有非常多的service,这些service每个都是binder server,因此我启动的最大binder线程个数是31个

不知道大家有没有考虑过这样的问题:最大binder线程个数少的话会有啥问题?

其中一个重要的问题就是性能降低。我举个例子比如工厂里面正常工作量的话,15个人刚好干完,但是由于工作量加大,15个人的话就干不完了,那这种情况该咋办呢?其中一种是15个人加班干,另外一种是增加工人个数。同样binder最大线程数小的话,就会导致大量的请求积压在队列中,这样就导致性能下降。

下面是相关代码,请自行取阅:

//SystemServer方法
public static void main(String[] args) {//run方法在下面new SystemServer().run();
}private void run() {省略代码······//sMaxBinderThreads的值为31,该方法设置开启binder线程的最大个数BinderInternal.setMaxThreads(sMaxBinderThreads);省略代码······
}
开启Looper

Looper在Android中的作用不必说,开启了Looper后,其他的线程或者当前线程就可以通过Handler把Message传递到当前线程。

加载android_servers可执行文件

android_servers可执行文件中包含了jni方法和很多native方法,使用System.loadLibrary方法可以把android_servers可执行文件加载到内存中,这样就可以保证jni方法可用

创建Context

Context在Android中的作用那可是非常重要,而在systemserver进程中也是需要Context对象的,会创建两个Context,一个用来做业务逻辑等处理,另一个用来做UI显示,在systemserver也是会显示一些UI的 (比如关机对话框)

下面是相关代码,请自行取阅:

//SystemServer方法
public static void main(String[] args) {//run方法在下面new SystemServer().run();
}private void run() {省略代码······//createSystemContext方法在下面createSystemContext();省略代码······
}private void createSystemContext() {//创建systemserver具有的ActivityThread对象ActivityThread activityThread = ActivityThread.systemMain();//获取系统Context对象mSystemContext = activityThread.getSystemContext();//为Context设置主题mSystemContext.setTheme(DEFAULT_SYSTEM_THEME); //该方法会根据mSystemContext创建UI类型的Context,它主要用来显示UIfinal Context systemUiContext = activityThread.getSystemUiContext(); //为systemUiContext设置主题systemUiContext.setTheme(DEFAULT_SYSTEM_THEME);
}
小结

启动前期主要是为后面启动各种service做准备,接下来看剩下的步骤吧。

3.2.2 启动bootstrap services

还记得我已经对service进行了分类,该阶段就是对bootstrap services进行启动,在此阶段同时还会启动一个Watchdog类,它的作用是监控systemserver进程的对应线程是否发生了锁等待情况,后面会再次详细介绍它。

在此阶段,会发出第一个启动阶段信号PHASE_WAIT_FOR_DEFAULT_DISPLAY (它的值为100),此信号的意思是现在进入等待获取默认显示屏阶段,已经启动的service都会收到该信号,只不过只有DisplayManagerService才关心此信号。DisplayManagerService会尝试获取是否有可用的显示屏,有的话则进入下阶段;否则等待一定时间后还是没有可用的显示屏,则整个启动过程失败。

下面是相关代码,请自行取阅:

//DisplayManagerService
//下面方法会阻塞整个启动流程
public void onBootPhase(int phase) {//收到 PHASE_WAIT_FOR_DEFAULT_DISPLAY 信号if (phase == PHASE_WAIT_FOR_DEFAULT_DISPLAY) {synchronized (mSyncRoot) {//获取超时时间long timeout = SystemClock.uptimeMillis()+ mInjector.getDefaultDisplayDelayTimeout();//若没有可用显示屏,则进行等待while (mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY) == null|| mVirtualDisplayAdapter == null) {long delay = timeout - SystemClock.uptimeMillis();//若超时依然没有获取到可用显示屏,则抛异常if (delay <= 0) {throw new RuntimeException("Timeout waiting for default display "+ "to be initialized. DefaultDisplay="+ mLogicalDisplayMapper.getDisplayLocked(Display.DEFAULT_DISPLAY)+ ", mVirtualDisplayAdapter=" + mVirtualDisplayAdapter);}省略代码······try {mSyncRoot.wait(delay);} catch (InterruptedException ex) {}}}} 省略代码······}

3.2.3 启动core services

该阶段主要是启动类别为core的所有service,启动的service确实很多,我在这就不给大家一一介绍了,咱们进入下个阶段。

3.2.4 启动other services

同样此阶段会启动剩下的service,由于启动的service多,也不在此一一赘述了,当然在这个阶段还会启动systemui进程,并且还会启动launcher,在这里介绍下几个关键的启动阶段信号。

PHASE_WAIT_FOR_SENSOR_SERVICE (它的值是200),此信号的意思是进入等待传感器服务阶段,同样所有启动的service都会收到此信号,只有SensorService才会关心它,SensorService在等待期间会阻塞启动过程。

下面是相关代码,请自行取阅:

//SensorService
public void onBootPhase(int phase) {if (phase == SystemService.PHASE_WAIT_FOR_SENSOR_SERVICE) {ConcurrentUtils.waitForFutureNoInterrupt(mSensorServiceStart,START_NATIVE_SENSOR_SERVICE);synchronized (mLock) {mSensorServiceStart = null;}}
}

PHASE_LOCK_SETTINGS_READY (它的值是480),此信号的意思是settings相关的数据你们各service可以使用了,因此已经启动的service,收到该信号后若需要读取settings的数据就可以读取了。

PHASE_SYSTEM_SERVICES_READY (它的值是500),此信号的意思是core services可以被安全的使用了,比如PowerManagerService、PackageManagerService等可以被别的service使用了。

PHASE_ACTIVITY_MANAGER_READY (它的值是550),此信号的意思是ActivityManagerService已经准备好了,广播等都可以使用了

PHASE_BOOT_COMPLETED (它的值是1000),此信号的意思是启动阶段完成,那啥时候发该信号呢?launcher界面展示成功后才会发出该信号,所有已经启动的service收到此信号后就可以做自己该做的事情了。

下面是相关代码,请自行取阅:

//ActivityManagerService//下面的方法是launcher界面做完动画后会调用
final void finishBooting() {省略代码······//让所有的service都知道启动完成了mSystemServiceManager.startBootPhase(t, SystemService.PHASE_BOOT_COMPLETED);省略代码······}

3.2.5 进入无休工作模式

在ActivityManagerService发出PHASE_BOOT_COMPLETED信号后,所有的service都欢腾了起来,每个service身上紧绷着的弦也终于放松了,从此刻开始我和我的service小伙伴们就进入了无休工作模式,人类啊千万别在抱怨你们的工作时长长了,在长能有我长吗?

进入无休工作模式的代码也特别简单,如下

//SystemServer
private void run() {省略代码······Looper.loop(); 省略代码·····
}

其实就是调用了Looper.loop()方法,该方法作用是让当前线程会一直循环下去。

3.2.6 小结

我的启动其实就是启动所有的service,而由于service很多它们之间又存在复杂的依赖,导致启动过程很复杂,为了解决此问题,我使用SystemService和SystemServiceManager来管理这些service;并且对这些service进行分类,这样启动过程就可以按照分类来依次启动它们;在启动过程中某些service肯定要依赖于别的service或者别的事件,针对这种情况我想了分发启动阶段信号这么个办法,发出相应的信号后,对该信号关心的service就可以着手处理相关的事情,其中PHASE_BOOT_COMPLETED阶段信号是最关键信号,代表整个启动完成,而发该信号的时机是launcher界面展示完成。收到该信号后,我和我的service就完全准备好了,我们进入无休的工作模式

总结

我是systemserver进程,我虽然是zygote进程的“长子”,但我同时也当担着“父亲”的职责,我会管理由zygote孵化的所有进程。我把我比作Android的大脑,App进程如果想启动Activity那需要找我的ActivityTaskManagerService,如果需要让设备振动那也需要找我的VibratorManagerService,当然还有很多很多的事情都需要找我的service。

这就是我一个独一无二的进程。

请添加图片描述

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.rhkb.cn/news/405600.html

如若内容造成侵权/违法违规/事实不符,请联系长河编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

8个我平时每天都会看的网站,涵盖办公、娱乐、学习等

分享8个我平时每天都会看的网站&#xff0c;涵盖办公、娱乐、学习等多种类别&#xff0c;试过就知道有多好用&#xff01; 1、MyFreeMP3 tools.liumingye.cn/music/#/ 一个可以免费听歌的平台&#xff0c;不用充会员&#xff0c;里面收录了大多数的国内外知名流行歌手、乐队的…

电脑开机LOGO修改教程_BIOS启动图片替换方法

准备工具&#xff1a;刷BIOS神器和change logo&#xff0c;打包下载地址&#xff1a;https://download.csdn.net/download/baiseled/89374686 一.打开刷BIOS神器&#xff0c;点击备份BIOS&#xff0c;保存到桌面 二.打开change logo&#xff0c;1.点击load image&#xff0c;选…

Linux云计算 |【第二阶段】SECURITY-DAY1

主要内容&#xff1a; 监控基础&#xff08;系统监控命令、监控软件&#xff09;、Zabbix监控服务端部署、Zabbix监控客户端部署、创建监控主机、调用监控模板、自定义key、创建模板、应用集、监控项、绑定模板&#xff1b; 一、监控概述 1&#xff09;监控的目的 ① 实时报…

LED电子看板优化生产线的管理

在当今竞争激烈的制造业领域&#xff0c;企业不断寻求提高生产效率、降低成本和提升产品质量的方法。而 LED 电子看板作为一种先进的管理工具&#xff0c;正逐渐成为优化生产线管理的关键利器。 一、LED电子看板能够清晰地展示生产进度信息 在繁忙的生产线上&#xff0c;工人和…

18105 银行的叫号顺序

### 详细分析 为了模拟银行的叫号过程&#xff0c;我们可以使用优先队列&#xff08;堆&#xff09;来管理客户的服务顺序。优先级越高的客户会先得到服务&#xff0c;同级别的客户按到达时间先后顺序得到服务。如果优先级和到达时间都相同&#xff0c;则按输入顺序服务。 ##…

表达式求值 - 整形提升和截断

文章目录 一、整形提升二、为什么要整形提升&#xff1f;三、截断四、示例1&#xff0c;23① c1 c2② c3 c1 c2 4 一、整形提升 C语言的整形算数运算总是至少以缺省整形类型的精度来进行的。 为了获得这个精度&#xff0c;表达式中的字符类型和短整型操作数在使用之前被转换…

深度学习基础之前馈神经网络

目录 基本结构和工作原理 神经元和权重 激活函数 深度前馈网络 应用场景 优缺点 深度前馈神经网络与卷积神经网络&#xff08;CNN&#xff09;和循环神经网络&#xff08;RNN&#xff09;的具体区别和联系是什么&#xff1f; 具体区别 联系 如何有效解决前馈神经网络…

爬虫案例4——爬取房天下数据

简介&#xff1a;个人学习分享&#xff0c;如有错误&#xff0c;欢迎批评指正 任务&#xff1a;从房天下网中爬取小区名称、地址、价格和联系电话 目标网页地址&#xff1a;https://newhouse.fang.com/house/s/ 一、思路和过程 目标网页具体内容如下&#xff1a; ​​​​ …

成为Python砖家(3): 何时产生字节码 .pyc 文件

好奇&#xff1a;.pyc和 __pycache__是啥&#xff1f; 你是否好奇&#xff0c;在某些 Python 工程中&#xff0c;当执行了 xxx.py脚本后&#xff0c;多出了 __pycache__目录&#xff1f;这个目录下存放的是一些 .pyc结尾的文件。 这些文件&#xff0c;叫做 python bytecode。 …

深度剖析数字媒体产业链的无限潜力与创新生态

在当今信息爆炸的时代&#xff0c;数字媒体产业链正以势不可挡的姿态展现出其令人瞩目的无限潜力与创新生态。 数字媒体的发展潜力简直无可限量。从在线视频的爆发式增长&#xff0c;到虚拟现实和增强现实技术带来的沉浸式体验&#xff0c;再到社交媒体平台上丰富多彩的内容创…

Windows 应用程序加密 - 功能强大、可定制、开源且完全免费

先进而优雅的 Windows 应用程序加密 - 功能强大、可定制、开源且完全免费&#xff01; 项目地址&#xff1a;FadCrypt GitHub 工作原理&#xff1a; 1. 密码创建&#xff1a;设置密码后&#xff0c;密码会与锁定应用程序的配置文件一起加密保存。监控期间&#xff0c;这些文…

望繁信科技入选2024年第3批上海市高新技术成果转化项目名单

近日&#xff0c;上海望繁信科技有限公司&#xff08;以下简称“望繁信科技”&#xff09;凭借其自主研发的“数字北极星流程挖掘分析软件”项目&#xff0c;成功入选2024年第3批上海市高新技术成果转化项目名单。这一殊荣根据《上海市高新技术成果转化项目认定办法》&#xff…

linux 中docker git 容器磁盘占满如何解决

1.问题描述 git之前还使用ok&#xff0c;突然出现访问500 错误&#xff0c;懵圈了 2.问题排查 1. 服务器查看&#xff0c;服务正常&#xff0c;没有异常出现。 2. 查找资料&#xff0c;需要查看是否磁盘已经满了果然使用df-h 后显示磁盘已经满了&#xff0c;且容器和本地都…

WPF篇(20)- Menu菜单+ContextMenu上下文菜单+StatusBar状态栏

Menu菜单 Menu控件继承于MenuBase&#xff0c;而MenuBase继承于ItemsControl。所以学习Menu之前&#xff0c;要先了解一下MenuBase基类。它是一个抽象类&#xff0c;拥有一个ItemContainerTemplateSelector模板选择器&#xff0c;并重写了一些关于键盘和鼠标的方法。 Menu的子…

电脑监控怎样看回放视频?一键解锁电脑监控回放,守护安全不留死角!高效员工电脑监控,回放视频随时查!

你是否曾好奇那些键盘敲击背后的秘密&#xff1f;电脑监控不仅是守护企业安全的隐形盾牌&#xff0c;更是揭秘高效与合规的魔法镜&#xff01;一键解锁安企神监控回放&#xff0c;就像打开时间宝盒&#xff0c;让过去的工作瞬间跃然眼前。无论是精彩瞬间还是潜在风险&#xff0…

【Android】adb devices 出现devices offline的问题

1 问题 adb devices 出现devices offline 2 解决方法 adb kill-serveradb start-server 然后&#xff0c;adb devices查看。 adb devices 问题解决啦。。。&#x1f49b; &#x1f499; &#x1f49c; ❤️ &#x1f49a; &#x1f49b; &#x1f499; &#x1f49c; ❤️…

12/24/30v/36转固定5v输出芯片

设计电源芯片的应用方案时&#xff0c;必须保证输入电压在DC6V至30V范围内&#xff0c;输出电压为固定的5V&#xff0c;同时电流需在200至300mA之间。在这种需求下&#xff0c;推荐使用AH1405芯片&#xff0c;因其输入电压范围宽&#xff08;6-40V&#xff09;&#xff0c;内置…

自闭症寄宿语言开发全托学校

在自闭症儿童的世界里&#xff0c;语言往往是一座难以跨越的高山。语言问题作为自闭症儿童的核心障碍之一&#xff0c;给他们的生活、学习和社交带来了极大的困扰。因此&#xff0c;语言开发对于自闭症儿童来说至关重要。那么&#xff0c;怎样才能更好地对自闭症儿童进行语言开…

arthas源码刨析:启动 (1)

文章目录 arthas-bootBootstrap Created with Raphal 2.3.0 开始 检查监听端口 jps 列表java应用 下载 lib 依赖 功能移交给 arthas-core 结束 arthas-boot 该module 的代码只有3个类&#xff1a; Bootstrap 启动类 Bootstrap &#xff0c;开头的注解就是 alibaba 的 cli 中…

Qt 0820作业

一、思维导图 二、闹钟 头文件代码 #ifndef WIDGET_H #define WIDGET_H#include <QWidget> #include <QTime> //时间类 #include <QTimer> //时间事件类 #include <QTimerEvent> //定时器事件类 #include <QMouseEvent> //鼠标…