Android进程/线程
本文讲解Android系统中的进程/线程的概念
author:psaa
date:2020年9月16日
参考资料:《深入理解Andoird内核设计思想》
Android进程和线程
进程(Process)是一个程序的一个运行实例,以区别于“程序”这一静态概念;而线程则是CPU调度的基本单位。
我们知道,一个应用程序的主入口一般都是main函数,这基本上成了程序开发的一种规范——它是“一切事物的起源”。而main()函数的工作也是前篇一律:
- 初始化 (比如创建对象、申请资源等)
- 进入死循环 (在循环中处理各种事件,直到进程退出
这种模型是“以事件为驱动”的软件系统的必然结果,因此几乎存在于任何操作系统和编程语言中。
对于Android应用开发者而言,通常面对的都是Activity、Service等组件,并不需要特别关心进程是什么。因而产生了一些误区,如部分开发者认为系统四大组件就是进程的载体。
实际上四大组件只是application的零件,只能算作进程的组成部分。
ActivityThread
主线程由ZygoteInit启动,经由一系列调用后最终才执行Activity本身的onCreate()函数。最重要的是Zygote为Activity创建的主线程是ActivityThread。
一个没有任何实际功能的Activity程序回启动一个main Thread以及多个Binder Thread。
public static void main(String[] args) {
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");
AndroidOs.install();
CloseGuard.setEnabled(false);
Environment.initForCurrentUser();
final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
TrustedCertificateStore.setDefaultUserDirectory(configDir);
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();/*只有主线程才能调用这个函数,普通线程应该使用prepare()*/
long startSeq = 0;
if (args != null) {
for (int i = args.length - 1; i >= 0; --i) {
if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
startSeq = Long.parseLong(
args[i].substring(PROC_START_SEQ_IDENT.length()));
}
}
}
ActivityThread thread = new ActivityThread();/*这个main()是static的,因此这里需要创建一个实例*/
thread.attach(false, startSeq);/*Activity是有界面显示的,这个函数将与WindowManagerSerice建立联系*/
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); /*主线程对应的Handler*/
}
if (false) {
Looper.myLooper().setMessageLogging(new
LogPrinter(Log.DEBUG, "ActivityThread"));
}
// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();/*主循环开始*/
throw new RuntimeException("Main thread loop unexpectedly exited");/*如果程序运行到这里说明退出了上面的Looper循环*/
}
除此之外上述Binder线程并不是Activity应用进程独有的。
- Service也是寄存于ActivityThread之中的,并且启动流程和Activity基本一致。
- 启动Service时,也同样需要多个Binder线程的支持。
上述对于启动时Binder线程的描述按照书上为两个,启动Acitivyt和Service时都需要两个Binder线程的支持,但在本人验证中单独启动一个Activity会启动很多个线程(10个左右),Binder线程为3个(很有可能是其他线程所需要的,暂时无法确定),不过我倾向于2个,这是因为在IPC中的进程间通信所需要的工具都是成对出现。
进程与线程的总结:
- 四大组件并不是程序(进程)的全部,而只是它的“零件”
- 应用程序启动后,将创建ActivityThread主线程
- 同一个包中的组件将运行在相同的进程空间中
- 不同包中的组件可以通过一定的方式运行在一个进程空间中
- 一个Activity应用启动后至少会有3个线程:即一个主线程和两个Binder线程
Handler,MassageQueue,Runnable和Looper
概述:Looper不断的获取MessageQueue中的一个Message,然后由Handler来处理
- Runnable和Message可以被压入某个MessageQueue中,形成一个集合
注意,一般情况下某种类型的MeassageQueue只允许保存相同类型的Object。所以实际源码中需要先对Runnable进行相应的转换。
- Looper循环地去做某件事
它不断的去除message然后传给Handler进行处理,如此循环往复。假如队列为空,那么他会进入休眠。
- Handler是真正处理事情的地方
它利用自身的处理机制,对传入的各种Object进行相应的处理并产生结果。
Handler
public class Handler {...
final MessageQueue mQueue;
final Looper mLooper;
final Callback mCallback;
}
- 每个Thread只对应一个Looper;
- 每个Looper只对应一个MessageQueue;
- 每个MessageQueue中有N个Message;
- 每个Message中最多指定一个Handler来处理事件
由此可以推断出,Thread和Handler是一对多的关系。
Handler主要有两方面的作用:
- 处理Message,这是它作为处理者的本职所在。
- 将某个Message压入MessageQueue中。
处理Message
相关实现函数如下
public void dispatchMessage(@NonNull Message msg)
public void handleMessage(@NonNull Message msg)
Looper从MessageQueue中取出一个Message后,首先会调用Handler.dispatchMessage进行消息派发;后者则根据具体的策略将Message分发给相应的责任人。默认情况下Handler的派发流程是:
Message.callback(Runnable对象)是否为空
在不为空的情况下,将优先通过callback来处理。
Handler.mCallback是否为空
在不为空的情况下,调用mCallback.handlerMessage
如果两个对象都不存在,才调用Handler.handlerMessage
将Message压入MessageQueue
相关功能函数:
1、Post系列
public final boolean post(@NonNull Runnable r)
public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis)
public final boolean postDelayed
...
2、Send系列
public final boolean sendEmptyMessage(int what)
public final boolean sendEmptyMessageDelayed(int what, long delayMillis)//return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg)
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis)
send处理的参数直接是Message,post则需要先把需要先把Runnable转换成Message。
Handler通过如下方法将Runnable转换成Message
private static Message getPostMessage(Runnable r) {
Message m = Message.obtain();
m.callback = r;
return m;
}
@UnsupportedAppUsage
private static Message getPostMessage(Runnable r, Object token) {
Message m = Message.obtain();
m.obj = token;
m.callback = r;
准备好Message之后,程序调用sendMessageDelayed来执行下一步操作。这个函数可以设定延迟多长事件后再发送消息,其内部又通过当前事件+延时时长(SystemClock.uptimeMillis() + delayMillis)计算出具体是在哪个时间点发送消息
SystemClock.uptimeMillis()表示系统开机到当前的时间总数,单位是毫秒,但是,当系统进入深度睡眠(CPU休眠、屏幕休眠、设备等待外部输入)时间就会停止,但是不会受到时钟缩放、空闲或者其他节能机制的影响。
既然最终都是依靠Handler来处理消息,为什么不直接执行要压入MessageQueue呢?这其实体现了程序设计一个良好的习惯,即“有序性”。
MessageQueue
正如其名,MessageQueue是一个消息队列,因而它具有“队列”的所有常规操作。
- 新建队列
- 元素入队
- 元素出队
- 删除元素
- 销毁队列
MessageQueue(boolean quitAllowed) {
mQuitAllowed = quitAllowed;
mPtr = nativeInit(); //nativeInit会在本地创建一个NativeMessageQueue对象,然后直接赋值给成员变量。这一系列操作实际上都是通过内存指针进行
}
boolean enqueueMessage(Message msg, long when)//元素入队
Message next() //@UnsupportedAppUsage 元素出队
void removeMessages(Handler h, int what, Object object)//删除元素
void removeMessages(Handler h, Runnable r, Object object)/删除元素
private void dispose() {
if (mPtr != 0) {
nativeDestroy(mPtr);//调用本地方法销毁MessageQueue
mPtr = 0;
}
}
Looper
需要注意的是Looper在主线程和普通线程钟的调用方式不一样。
普通线程范例
class LooperThread extends Thread {
public Handler mHandler;
public void run() {
Looper.prepare();
mHandler = new Handler() {
public void handleMessage(Message msg) {
...//处理消息的地方。继承Handler的子类通常需要修改该函数
}
};
Looper.loop();//进入主循环
}
}
概括来看只有3个步骤:
- Looper的准备工作(prepare)
- 创建处理消息的handler
- Looper开始运作(loop)
但是mHandler是如何保证把外部的消息投递到Looper所管理的MessageQueue中的。
Looper中有一个非常重要的成员变量
// sThreadLocal.get() will return null unless you've called prepare().
@UnsupportedAppUsage
static final ThreadLocal<Looper> sThreadLocal = newThreadLocal<Looper>();
这是一个静态类型的变量,意味着一旦import了Looper后,sThreadLocal就已经存在并构建完毕。ThredLocal对象是一种特殊的全局变量,因为它的“全局”性只限于自己所在的线程,而外界所有线程(即使是同一进程)一概无法访问到它。这说明每个线程的Looper都是独立的。
为什么在Looper的调用中不需要传入handler对象或者handler对象没有指定Looper呢?
sThreadLocal会创建一个只针对当前线程的Looper及其他相关的数据对象
public static void prepare() {
prepare(true);
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {//这个判断保证了一个Thread中只有一个Looper存在
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Handler是如何与Looper关联起来的呢?
答案是通过Handler的构造函数
public Handler() {
...
mLooper = Looper.myLooper();//还是通过sThreadLocal.get来获取当前线程中的Looper实例
...
mQueue = mLooper.mQueue;//mQueue是Looper与Handler之间沟通的桥梁
mCallback = null;
}
这样Handler、Lopper、MessageQueue即联系起来,Handler执行Post/Send方法时,会将消息压入Queue中(也就是mLooper.mQueue)。一旦Looper处理到这条消息,它又会从中调用Handler来进行处理。
UI主线程——ActivityThread
public static void main(String[] args){
...
Looper.prepareMainLooper();
ActivityThread threwad = new ActivityThread();
thread.attach(false);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler(); /*主线程对应的Handler*/
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
比较主线程与普通线程中Looper的使用可以发现
- 主线程使用prepareMainLooper和普通线程prepare
- Handler不同,普通线程生成一个与Looper绑定的Handler就行,主线程是从当前线程中获取的Handler(thread.getHandler)
preapareMainLooper与prepare的比较
public static void prepareMainLooper() {
prepare(false);//参数false表示该线程不允许退出
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();//
}
}
public static void prepare() {
prepare(true);//普通线程默认允许线程退出
}
private static void prepare(boolean quitAllowed) {
if (sThreadLocal.get() != null) {
throw new RuntimeException("Only one Looper may be created per thread");
}
sThreadLocal.set(new Looper(quitAllowed));
}
Main Thread使用的prepareMainLooper(),这个函数通过prepare为主线程生成一个ThreadLocal的Looper对象,并让sMainLooper指向它。这样的目的是其他线程如果要求获取主线程Looper,只需调用getMainLooper()即可。
Google通过这样的方式巧妙的区分开各线程的Looper并界定了他们的访问权限。
sMainThreadHandler
当ActivityThread对象创建时,会在内部同时生成一个继承自Handler的H对象。
@UnsupportedAppUsage
final H mH = new H();
...
class H extends Handler {...
AcitivityThread.main中调用的thread.getHandler()返回的就是mH。这个mH就是主线程中的负责处理各种消息的Handler了。
最后分析一下looper()函数,在文章开头提到的“以事件为驱动”的经典模型如下:
伪代码
main(){
initialize();//初始化操作
while(getMessage())//不断的获取斌执行消息,直到收到退出的消息
{
TranslateMessage();//对消息进行必要的前期处理
DispatchMessage();//分配消息给相应的元素进行处理
}
}
这个模型在每个系统平台上的实现细节会有所差异,但本质都是一样的。即
- 创建处理消息的环境
- 循环处理消息
也就是说,消息是推动整个系统动起来的基础,即便操作系统本身也是如此。
再来看Looper.loop的实现:
public static void loop() {
final Looper me = myLooper();//loop函数也是静态的,所以它只能访问静态数据。函数myLooper则调用sThreadLocal.get()来获取与之匹配的Looper实例(其实就是去除之前prepare中创建的那个Looper对象)
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
final MessageQueue queue = me.mQueue;
...
for (;;) {//消息循环开始
Message msg = queue.next(); // might block
if (msg == null) {
// No message indicates that the message queue is quitting.
return;
}
...
try {
msg.target.dispatchMessage(msg);
if (observer != null) {
observer.messageDispatched(token, msg);//开始派发消息,变量target实际上是一个Handler,所以dispatchMessage最终调用的是Handler中的处理函数
}
dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
} catch (Exception exception) {
if (observer != null) {
observer.dispatchingThrewException(token, msg, exception);
}
throw exception;
} finally {
ThreadLocalWorkSource.restore(origWorkSource);
if (traceTag != 0) {
Trace.traceEnd(traceTag);
}
}
...
msg.recycleUnchecked();//消息处理完毕会将消息回收
}
loop()函数的主要工作就是不断地从消息队列中去除需要处理的事件,然后分发给相应的责任人(msg.target).如果消息队列为空,它很可能会进入睡眠让出CPU资源。儿在具体事件的处理过程中,程序会post新的事件到队列中。另外其他进程也可能投递新的事件到这个队列中。apk应用程序就是不停的执行“处理队列事件”的工作,直至它退出运行。
最后,可以给出基于AcitivityThread的进程模型: