准备对基于Android应用开发Framework层的内容进行学习回顾,学习一个新技术前我们一般都会灵魂三问:What-Why-How(是什么、为什么、怎么用)。源码的学习一定要亲自去看,用IDE或者Source Insight都没问题,如果看不懂就再看,多看,反复地看,相信我每看一次都有收获。尤其是Android中AMS这里,内容太多了,看资料和文档,并找到适合自己的方法论,先了解总体流程,再去抠细节,一上来就看细节会淹死在代码里。AMS学习基于Handler、Binder、Activity…ActivityThread最终围绕着Handler玩的,Handler是Framework学习的第一步,相对简单易学,也是基础,所以从Handler开始吧。
Framework学习过程:Handler->Binder->应用启动流程/Zygote->dex文件构成/差分包->AMS->…
网上关于Android中Handler的文章已经多到数不清, 入门使用、工作流程、深入解析源码……等等,可见,Handler在Android中是一个基础且重要的知识块。我想写一篇从入门到深入,系统全面的讲解Handler机制的文章,这里总结一下自己对Handler的分析,记录一下自己的学习成果。内容应该会比较多,篇幅会很长。Let’s go.
一、概述
首先明确“是什么”的问题,准确的来讲,这里指的是Handler机制,Handler只是这个机制里面的一个角色,主角。开发中与Handler接触的比较多,所以用Handler代称Handler机制。Handler机制是Android中的一套线程间的通信机制,它的本质是负责消息的分发和处理,事务处理贯穿了整个Android系统层,Handler就是整个系统的维护者。看ActivityThread(AMS范畴)源码就可以理解这个点。
1.1 Handler存在的意义
- 与web端开发的Ajax有异曲同工之妙,spring中就有
- 降低Android开发难度
- 几乎看不到多线程死锁问题
1.2 数据通信在开发中会带来问题
- 线程间如何通信?
Handler通信的实现方案本质是内存共享。 - 为什么线程间不会干扰?
内存管理设计思路优秀,加了锁,用锁机制设计了方案(这里需要了解为什么加锁和加锁的意义,后续一并解答)。 - 为什么wait()/notify()没有用武之地?
Java语言的wait()/notify()是基于Java虚拟机的,而虚拟机是用C语言写,最终 wait()/notify()通过JVM编译成c/c++的wait()/notify(),所以,Handler设计的时候直接在Linux层将这部分逻辑进行了封装。
可以用以上三点评估一下自己对Handler的理解程度,理解了以上三个点,对Handler已经有了简单的理解,否则只是知道有、用过Handler,一知半解的水平。
1.3 Handler学习的重要性
思考一下,应用程序App是如何启动的?
lancher(app):zygote -> jvm -> ActvityThread.main()
点击一个应用图标就是点击Lancher(App,桌面程序)的一个"图标按键",Lancher管理整个屏幕并会做出响应,去启用Zygote,为每个App应用fork一个进程,对应分配、启动一个独立的JVM虚拟机(每个App单独分配JVM有什么好处?隔离,独立,自己出了问题不影响别的应用和整体系统)。
在Java中,每个程序都会有个main()方法,作为整个应用程序启动的入口。同样的每个App也都有自己的JVM,在ActivityThread就会有独立的main()方法,内部会初始化环境,打日志,重要的是执行Looper.prepareMainLooper()
和Looper.loop()
,启动了主线程独有的Looper,主线程(App)就运行起来了。Looper.loop()内部是个死循环,意味着App所有的代码都是由Handler管理的。不断的把要做的事情以消息的形式分发出去处理。
Handler不仅仅用来做线程间通信的,线程间通信只是Handler的一个附属功能,Handler真正的功能是App应用所有代码都在Handler中运行,维持着Android应用的运行的整个框架。故此,Handler是至关重要的!
1.4 Handler机制里面涉及到如下几个类
包括但不限于:
- Handler:负责收发消息
- Looper:负责传送消息
- MessageQueue:消息队列,仓库
- Message:消息载体
- ThreadLocal:Java中线程内部储存数据的工具类
MessageQueue是Looper内部的一个属性,MessageQueue和Looper每个线程有且只有一个,而Handler是可以有很多个的(一定要记牢,要考)。
- 使用者使用线程的Looper构建Handler之后,通过Handler的send和post方法传送信息
- 信息会加入到MessageQueue中,等待Looper获取处理
- Looper会不断地从MessageQueue中获取Message然后交付给对应的Handler处理
这就是大名鼎鼎的Handler机制内部模式了,说难,其实也是很简单。
Handler内容很多:源码分析(理解epoll)、设计思路、设计模式,异步消息和同步消息,消息屏障、Handlerthread、IntentService……做好心理准备。
有了初步的认识之后,先看一下如何使用。
二、使用
平常使用Handler有两种不同的建立方式,但总体流程是相同的:
- 建立Looper
- 使用Looper建立Handler
- 启动Looper
- 使用Handler发送消息
首先,Looper可理解为循环器,就像“流水线”上的传送带,后面会详细讲到。每个线程只有一个Looper,通常主线程ActivityThread已经为我们建立好了,了解应用程序启动流程就知道,启动过程中调用了Looper.prepareMainLooper()
,而在子线程就必须使用如下方法来初始化Looper:
Looper.prepare();
第二步是建立Handler,也是开发者最熟悉的一步。有两种方法来建立Handler:传入callBack回调和继承。如下:
// 开发中,线程间通信:子线程携带Javabean数据-->主线程做显示
private TextView tvTxt;
// 第一种方法:使用callBack建立handler
// 主线程中处理收到的消息
private Handler mHandler = new Handler(msg -> {
// 接收到消息,通常把数据交给主线程刷新界面
if(msg.what == 100){
Log.d("Handler", msg.obj.toString());
// 问题一:Android不能在非UI线程操作界面UI刷新,需要在主线程完成
tvTxt.setText(msg.obj.toString());// 界面刷新数据
}
// false 重写Handler类的handleMessage会被调用,true 不会被调用
return false;
});
// 子线程使用Handler发送消息
private void useHandler() {
// 问题二:Android不能在主线程/UI线程执行耗时任务,需要交给子线程处理
new Thread(() -> {
Message msg = Message.obtain();
msg.what = 100;
msg.obj = "土豆土豆,这是子线程发出的消息,听到请回答";
mHandler.sendMessage(msg);
}).start();
}
//-----------------------------
// 第二种方法:继承Handler并重写handlerMessage方法
static MyHandler extends Hanlder{
public MyHandler(Looper looper){
super(looper);
}
@Override
public void handleMessage(Message msg){
super.handleMessage(msg);
// TODO(重写这个方法)
}
}
注意第二种方法,要使用静态内部类,不然可能会造成內存泄露。原因是非静态内部类会持有外部类的引用,而Handler发出的Message会持有Handler的引用。如果这个Message是个延迟的信息,此时activity被退出了,但Message依然在“流水线”上,Message->handler->activity,那么activity就无法被回收,导致內存泄露。
两种Handler的写法各有千秋,继承方式可以写比较复杂的逻辑,callback方式适合比价简单的逻辑,看具体的业务来选择。
然後再调用Looper的loope方法来启动Looper:
Looper.loop();
最后,就是使用Handler来传送信息了。当我们获得handler的实例之后,就可以通过他的sendMessage相方法和post相关方法来传送信息,如下:
handler.sendMessage(msg);
handler.sendMessageDelayed(msg,delayTime);
handler.post(runnable);
handler.postDelayed(runnable,delayTime);
Handler的使用就到这里结束。
如果在非UI线程去刷新界面我们就获得下面这个异常成就:
android.view.ViewRoot$CalledFromWrongThreadException:
Only the original thread that created a view hierarchy can touch its views.
第一个问题,为什么非主线程不能更新UI?因为界面一般都是由主线程绘制的,所以界面的更新也就被限制在主线程内。这个异常是在viewRootIimpl.checkThread()方法中抛出来的,那可以绕过它吗?当然可以,在它还没建立的时候就可以偷偷更新UI了。阅读过Activity启动流程的会知道,ViewRootImpl是在onCreate()方法之后被建立的,所以可以在onCreate()方法中创建子线程偷偷更新UI。但还是那句话,可以,但没必要去绕过这个限制,这是Google为程序更加安全而设计的。
为什么不能在子线程去更新UI?因为这会让界面产生不可预期的结果。例如,主线程在绘制一个按钮过程中,另一个线程突然过来把按钮的大小改成两倍大,这个时候再回到主线程继续执行绘制逻辑,这个绘制效果就会出现问题。所以UI的访问是决不能是并发的。但,子线程又想更新UI,怎么办?加锁。加锁确实可以解决这个问题,但是会带来另外的问题:界面卡顿(开发大忌,会被用户骂娘的)。锁是比较重量级的操作,对于性能是有消耗的,而UI操作讲究快准狠,加锁会让UI操作性能降低。那有什么更好的方法?Handler就是解决这个问题的。
第二个问题,不能在主线程执行耗时操作。通常说,耗时操作包括网络请求、数据库操作等,这些操作会导致ANR(Application Not Responding)。这个是比较好理解的,没有什么问题,但是这两个问题结合起来,就有大问题了。数据请求一般是耗时操作,必须在子线程进行请求,而当请求完成之后又必须更新UI,UI又只能在主线程更新,这就导致代码必须切换线程执行,上面讨论了加锁是不可取的,那么Handler的重要性就体现出来了。
不用Handler可以吗?可以,但没必要。Handler是Google设计来方便开发者切换线程以及处理信息,然后你说我偏不用,我自己用Java工具类,自己弄个出来不可以吗?那……当然……也可以。早些年用BroadcastReceiver、接口回调,现在又涌现出通信总线类框架EventBus、RxBus,Jetpack又提供了LiveData,kotlin协程,Android消息通信仍在不断的演进之中,这些方案就不在本文讨论的范畴了。
三、Handler的工作流程
先上一张Handler工作流程的灵魂手绘,哈哈。
Handler整个工作流程就像一个传送带,Looper作为动力角色,不停的去轮询消息队列MessageQueue。Looper断开的唯一条件是返回一个Message为null的消息,可以停止循环,这里带出一个面试题:什么时候返回一个空Message?放到后面去解答。
sendMessage()/postMessage()的时候会把消息发送到传送带(MessageQueue,队列)上面,队列会拉着整个传输系统滚动,由Looper.loop()实现,里面是个死循环,相当于是动力,一旦启动就源源不断的滚动,不断地轮询MessageQueue的next()方法,获取队列上所有的消息Message对象,从执行时间最早消息开始,一旦达到了消息的执行时刻,获取到消息通过dispatch()进行分发,完成了整个消息的通信。
一般,子线程发送消息,主线程处理消,这样就实现了线程间的通信。
看Handler源码思路
从使用开始:sendMessage()
…->…// 如果中间打断点看执行流程,需要自己编译模拟器,很复杂
结束处:handleMessage()
在Handler内提供了很多发送消息的方法,包括sendXXX()和postXXX():
网上有一副经典的方法的调用关系如下:
去源码看,从sendMessage()方法开始,send和post这些方法最终都会调用sendMessageAtTime(),sendMessageAtTime()->Handler.enqueueMessage()->MessageQueue.enqueueMessage(),
这个过程就完成了把Message消息放到消息队列MessageQueue的工作,入队插入,把消息放到了传送带上。
消息放好了,有入就有出,怎么出去?
MessageQueue的next()方法从消息队列取出消息,返回Message对象
Message next() {
// Return here if the message loop has already quit and been disposed.
// This can happen if the application tries to restart a looper after quit
// which is not supported.
final long ptr = mPtr;
if (ptr == 0) {
return null;
}
int pendingIdleHandlerCount = -1; // -1 only during first iteration
int nextPollTimeoutMillis = 0;
for (;;) {
if (nextPollTimeoutMillis != 0) {
Binder.flushPendingCommands();
}
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
...
谁来取消息,调用next()方法?
Looper。Looper内部有个loop()方法,内部死循环,调用next()返回Message对象
public static void loop() {
...
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;
}
...
再调用dispatchMessage()最终到达handleMessage()
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
Looper.loop() -> MessasgeQueue.next() -> handler.dispatchMessage() -> handler.handleMessage()
整个Handler调度流程到此为止,整个流程中唯一奔跑的就是Message消息。
细节上说,Message在动的过程中,Message对象是new或者obtain出来的,都是一块内存,就形成了一个内存的共享。如何体现从子线程到主线程的?内存不分线程,主线程和子线程都可以用,在能把一块内存进行移动拿走。那么,不断地new Message会不会挂掉、卡死?肯定会。
3.1 MessageQueue
MessageQueue是什么数据结构?单列表实现的优先级队列。
为什么叫优先级?看enqueueMessage()方法,Handler机制里面这个方法很关键,需要多看几遍。
boolean enqueueMessage(Message msg, long when) {
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
synchronized (this) {
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
if (mQuitting) {
IllegalStateException e = new IllegalStateException(
msg.target + " sending message to a Handler on a dead thread");
Log.w(TAG, e.getMessage(), e);
msg.recycle();
return false;
}
msg.markInUse();
msg.when = when;
Message p = mMessages;
boolean needWake;
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
// Inserted within the middle of the queue. Usually we don't have to wake
// up the event queue unless there is a barrier at the head of the queue
// and the message is the earliest asynchronous message in the queue.
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
看到msg.next = p;
这个next属性我们要看Message对象
public final class Message implements Parcelable {
public int what;
public int arg1;
public int arg2;
public Object obj;
...
public long when;
...
Message next;
...
Message对象里面next还是一个Message,这样看来Message对象就是一个单向结构:Message->next(Message)->next(Message)
接下来,Message的先后顺序,也就是优先级,看enqueueMessage()
boolean enqueueMessage(Message msg, long when) {
...
synchronized (this) {
...
if (p == null || when == 0 || when < p.when) {
// New head, wake up the event queue if blocked.
msg.next = p;
mMessages = msg;
needWake = mBlocked;
} else {
...
for (;;) {
prev = p;
p = p.next;
if (p == null || when < p.when) {
break;
}
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
msg.next = p; // invariant: p == prev.next
prev.next = msg;
}
// We can assume mPtr != 0 because mQuitting is false.
if (needWake) {
nativeWake(mPtr);
}
}
return true;
}
看到,按时间排序,死循环轮询中对比执行时间if (p == null || when < p.when) {
。无论哪个方式发送Message都会加上时间。(小声逼逼:这里是排序算法?插入排序)
MessageQueue为什么叫队列?
前面讲到ThreadLocalMap是一个“修改版的HashMap”,而MessageQueue就是一个“修改版的LinkQueue”。他也有两个关键的方法:入队(enqueueMessage)和出队(next)。这也是MessageQueue的重点所在。队列本身会要求先进先出,这里由于时间排序保证不了先进,MessageQueue的next()方法执行时每次取消息,Message不为空的时候直接取now时间点作比较
Message next() {
...
for (;;) {
...
synchronized (this) {
...
if (msg != null) {
if (now < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
...
MessageQueue总结
Message两大重点:阻塞休眠和队列操作。基本都是围绕着两点来展开。而原始码中还涉及到了同步屏障以及IdleHandler,这两部分内容我分开到了最后一部分的相关问题中讲。平时用的比较少,但也是比较重要的内容。
3.2 Looper
Looper可以说是Handler机制中的一个非常重要的核心。Looper相当于线程消息机制的引擎,驱动整个机制执行。Looper负责从队列中取出信息,然后交给对应的Handler去处理。如果队列中没有消息,则MessageQueue的next()方法会阻塞线程,等待新的信息的到来。每个线程有且只能有一个“引擎”,也就是Looper,如果没有Looper,那么信息机制就执行不起来,而如果有多个Looper,则会违背单线操作的概念,造成并发操作。
每个线程仅有一个Looper,由不同Looper分发的Message执行在不同的线程中。Looper的内部维护一个MessageQueue,当初始化Looper的时候会顺带初始化MessageQueue。Looper使用ThreadLocal来保证每个线程都有且只有一个相同的副本。
学习Handler机制源码看哪里?核心?Looper部分必须掌握的3点:
- 构造函数
- loop()
- ThreadLocal
如何初始化?私有的构造函数
private Looper(boolean quitAllowed) {
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
私有化谁来初始化?自己在prepare()
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));
<