Handler源码解析和使用场景
备:如果只需了解源码,直接跳转第4点
1.使用场景
场景1:子线中程需要更新UI
new Thread(new Runnable() {
@Override
public void run() {
handler.sendMessage(message);
}
}).start();
场景2:简单的延迟行为或轮询操作
mHandler.sendMessageDelayed(message, 1000);
初始化Handler:
//以下是通常在Activity中创建的短信倒计时使用场景,需要每秒更新UI
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case COUNT_DOWN:
int time = (int) msg.obj;
if (time
2.使用中的问题
问题:以上代码在界面中编辑时,会提示你有内存泄漏风险,那为什么会导致内存泄漏呢?
回答:在java中,类的内部类持有外部类的引用,所以当我们新建Handler对象时,Handler时持有Activity的引用,当Activity正常销毁、或异常销毁时,消息仍在消息队列中(ActivityThread->Looper->MessageQueue)。
由于消息机制是轮询消息队列,按时间排序,顺序触发,而消息即Message对象需要持有msg.target,target就是Handler对象引用,目的是为了在触发消息时能够分发事件dispatchMessage(Message msg),并确使用正确的handler对象发送。在触发消息时,Message对象持有hander,Activity对象被handler持有引用,因而无法销毁,导致内存泄漏问题。
解决方案:
方案1:在onstop()去调用handler.removeCallbacks(),移除handler自身的所有消息队列
方案2:使用静态内部类,让handler持有activity的弱引用
private static class MyHandler extends Handler{
private final WeakReference mActivity;
public MyHandler(MainActivity mainActivity){
mActivity =new WeakReference(mainActivity);
}
@Override
public void handleMessage(Message msg) {
MainActivity mainActivity=mActivity.get();
super.handleMessage(msg);
if(mainActivity!=null){
//执行业务逻辑
}
}
}
3.个人偏好的体验
1.如果不是非要用handler不可,尽量不使用,原因是方案1在异常奔溃情况下可能不生效,方案2代码量又太多。
2.如果需要实时更新,个人建议使用CounDownTimer+view.post(),简述原理就是view在attach到window时,内部会持有一个attachInfo,attachInfo.mHandler会执行对应的消息发送方法,所以不需我们去维护mHandler。
class TimerCountDownUtils {
private var timer: CountDownTimer? = null
//这是倒计时执行方法,分别有俩个回调,一个是结束回调,一个是每秒更新距离结束剩余时间
fun runTimer(
totalSecond: Long, interval: Long = 1000, timerFinish: ()-> Unit, timerTick: (millisUntilFinished: Long) -> Unit) {
timer = object : CountDownTimer(totalSecond * 1000, interval) {
override fun onFinish() {
timerFinish()
cancel()
}
override fun onTick(millisUntilFinished: Long) {
timerTick(millisUntilFinished)
}
}.start()
}
fun cancel() {
if (timer != null) {
timer!!.cancel()
timer = null
}
}
}
//使用方式
timerUtils?.runTimer(totalSecond, 1000L, {
//执行计时器结束代码
},{
//执行每秒更新
//使用view.post(runnable)来代替handler
}
3.如果涉及到需要在子线程通知更新UI,建议kotlin协程,具体原理可以看底部转载的链接
fun setUpUI(){
//切到主线程模式
GlobalScope.launch(Main) {
//执行异步请求
val dataDeferred = requestDataAsync()
//执行获取数据前的操作
doSomethingElse()
//挂起
val data = dataDeferred.await()
//异步请求回调成功时,会执行数据处理代码
processData(data)
}
//执行一个延迟线程
Thread.sleep(1000)
//执行获取到数据后的操作
doSomethingElse2()
}
//Deffered可以简单理解为分发器,当未执行完异步请求时,会挂起,但不会阻塞对应线程
fun requestDataAsync():Deferred{
// 启动一个异步协程去执行耗时任务
return GlobalScope.async {
requestData()
}
}
fun doSomethingElse2(){
println("doSomethingElse2")
}
转载来源:https://blog.csdn.net/qq_36342492/article/details/111945231
4.真正理解Handler流程
- Handler是否能在子线程中创建?
通常我们在使用Handler的时候,都是在主线程中声明创建,但是如果在子线程中创建会怎样呢?
在Handler的构造中,可清晰看到 “mLooper = Looper.myLooper()”, looper为空就会抛出对应异常,在子线程中持有Handler时,就会抛此异常。
public Handler(Callback callback, boolean async) {
...
mLooper = Looper.myLooper();
if (mLooper == null) {
throw new RuntimeException(
"Can't create handler inside thread " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
- Handler为什么在主线程mLooper就不为空呢?
答案是,有人帮我们做了,在应用启动时,会调用ActivityThread中的main方法,这里面调用了
Looper.prepareMainLooper(),而prepareMainLooper()通过prepare(),去初始化Looper对象,所以因为在主线程中不会奔溃。另外需注意的是,sMainLooper在prepare之后加了异步锁,确保不会重复创建,并且当重复创建时会抛出非法状态异常,那结论来了,线程和Looper对象是一一对应的。
//ActivityThread
public static void main(String[] args) {
...
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);
if (sMainThreadHandler == null) {
sMainThreadHandler = thread.getHandler();
}
Looper.loop();
throw new RuntimeException("Main thread loop unexpectedly exited");
}
//Looper.class
public static void prepareMainLooper() {
prepare(false);
synchronized (Looper.class) {
if (sMainLooper != null) {
throw new IllegalStateException("The main Looper has already been prepared.");
}
sMainLooper = myLooper();
}
}
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));
}
- myLooper()为什么能在当前Thread对应的Looper对象?
Looper类中有一个静态的sThreadLocal对象,ThreadLocal类中存在静态内部类ThreadLocalMap,Looper的myLooper()方法会调用sThreadLocal.get()方法。这个get()方法,首先获取到当前线程Thread对象,Thread类中拥有threadLocalMap对象,可以清晰的看到以threadLocal为key,Looper为值。由于在prepare()方法中会初始化Looper对象时,同时会对初始化时Thread的threadLocalMap赋值,而作为key的threadLocal是一个静态对象,即所有Looper对象都是同一key值,那么很轻易地就可以获取到每个线程对应的Looper了。
//Looper.class
static final ThreadLocal sThreadLocal = new ThreadLocal();
public static @Nullable Looper myLooper() {
return sThreadLocal.get();
}
//ThreadLocal.class
public T get() {
//拿到当前线程
Thread t = Thread.currentThread();
//通过t线程,拿到线程自身的threadLocalMap对象
ThreadLocalMap map = getMap(t);
if (map != null) {
ThreadLocalMap.Entry e = map.getEntry(this);
if (e != null) {
@SuppressWarnings("unchecked")
T result = (T)e.value;
return result;
}
}
return setInitialValue();
}
- 是什么时候为Thread中的ThreadLocalMap对象赋值的?
回顾在prepareMainLooper()时的prepare(),因为线程与Looper一一对应,所以当Looper对象在prepare()中创建时,会执行sThreadLocal.set(new Looper(quitAllowed)),在set()执行完毕之后,Thread中的ThreadLocalMap对象就被赋值了。
//Looper.class
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));
}
//ThreadLocal.class
public void set(T value) {
Thread t = Thread.currentThread();
ThreadLocalMap map = getMap(t);
if (map != null)
map.set(this, value);
else
createMap(t, value);
}
小节1:Handler在使用时,需要当前线程存在Looper对象,而ThreadLocal类中的静态内部类ThreadLocalMap,是保证线程与Looper一一对应的关键。原理是Looper类中存在唯一的静态ThreadLocal对象,Thread类中存在ThreadLocalMap对象,初始化Looper对象时,会为当前Thread的ThreadLocalMap对象赋值,key值是所有Looper对象共用的ThreadLocal,value是不同Looper对象在初始化时传进的对象引用。好了,现在我们脑海里就一个概念,Handler和Looper是一一对应的关系,接下来探究下,handler是怎么发消息的。
- handler.sendMessage(message)这个方法做了什么保证消息的传递?
接下来的源码可读性都很高,直接跳到sendMessageAtTime()方法,我们发现冒出来了个MessageQueue对象,如果该对象为空,也会抛异常。
mQueue是什么时候初始化的?
Handler中拥有mQueue对象,该对象在Handler的构造方法中通过mLooper.mQueue赋值,Handler在构造中持有对应的Looper对象,而Looper对象的构造方法中会初始化mQueue,间接地为Handler的mQueue赋值。
好了,了解完初始化,我们就要继续了解MessageQueue消息队列。
//Handler.class
public final boolean sendMessage(Message msg)
{
//熟悉不?延迟消息
return sendMessageDelayed(msg, 0);
}
public final boolean sendMessageDelayed(Message msg, long delayMillis)
{
if (delayMillis
- enqueueMessage()做了什么?
先Handler的enqueueMessage(),主要就是让Message对象持有handler,方便回调,同时还调用了MessageQueue的enqueueMessage()。重头戏来了,该方法的作用就是,对新进的消息进行排序,让消息在指定的时间点调用,因为结合代码解释更清晰,所以每行代码我都进行了主要的注释,这对理解整个消息队列的数据结构有很大帮助。
//Handler.class
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
long uptimeMillis) {
//让消息持有Handler对象引用,用于消息轮询时回调对应handler的handleMessage方法
msg.target = this;
msg.workSourceUid = ThreadLocalWorkSource.getUid();
if (mAsynchronous) {
msg.setAsynchronous(true);
}
return queue.enqueueMessage(msg, uptimeMillis);
}
//MessageQueue
boolean enqueueMessage(Message msg, long when) {
//如果msg都没有Handler,那等于没有回调
if (msg.target == null) {
throw new IllegalArgumentException("Message must have a target.");
}
//消息是否在使用中,每个消息中都有flag=0,如果在使用中就会置为1,进而inUse
if (msg.isInUse()) {
throw new IllegalStateException(msg + " This message is already in use.");
}
//同步语句块,我们知道,线程和Looper对象一一对应,那么主线程中可能有多个Handler对象调用发送消息,为了统一管理同一线程下的消息能够有序触发,就需要同步锁。
synchronized (this) {
//如果消息所在的线程销毁了,mQuitting才会为true,那么就不需要再执行此消息了
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;
}
//让当前信息inUse
msg.markInUse();
//msg代表着当前消息,when是该消息的延迟时间戳
msg.when = when;
//mMessages是消息队列中的第一条消息,消息队列按时间排序
Message p = mMessages;
boolean needWake;
//(1.第一条消息为空 | 发送消息时间戳为0 | 发送时间小于第一条消息时间)
if (p == null || when == 0 || when
小节2:mHandler发送消息以后,先是执行自己的enqueueMessage(),为msg.target=Hander,随后会调用MessageQueue的enqueueMessage(),而MessageQueue对象伴随着Handler的构造会将Looper对象的MessageQueue赋值给当前Handler的MessageQueue。MessageQueue的enqueueMessage()方法中,主要是做了消息排序,如果发送消息时间小于队列中第一条消息,那么就插入为队列头部,如果大于等于,则会取队列第一条消息的下一条消息进行比较,最终得到有序的消息队列。注意:现在enqueueMessage()只是对消息进行排序,并没有触发消息,是入队操作。
- 有入队就有出队,那出队是怎样的呢?
首先要先回顾一下ActivityThread的代码,在初始化Looper之后,会调用Looper.loop(),目的是让轮询器启动,不断轮询,轮询到消息队列有消息时,就会取出队列头的消息,直到队列头消息处理后置空,才会继续取下一条队列头消息。当没有消息时,就会去查看是否有Idle Handler,有的话就去执行,执到没有才会去让出CPU给其他线程调用。
//Looper.class
public static void loop() {
final Looper me = myLooper();
//校验Looper是否初始化
if (me == null) {
throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
}
//取出Looper中的消息队列
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对象,那么就可以回调到对应handler对象的dispatchMessage()方法
msg.target.dispatchMessage(msg);
...
} 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();
}
}
//MessageQueue.class
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.
//mPtr由nativeInit()赋值,值为该方法对应的内存地址,在消息队列初始化时调用,为0则代表队列销毁
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();
}
//看到native,我们就知道是本地Framwork,一般是用c语言进行复杂运算。该方法作用是让当前线程让出CPU,间隔时间段为nextPollTimeoutMillis,避免其他线程因为获取不到CPU而造成阻塞
nativePollOnce(ptr, nextPollTimeoutMillis);
synchronized (this) {
// Try to retrieve the next message. Return if found.
//取消息
final long now = SystemClock.uptimeMillis();
Message prevMsg = null;
Message msg = mMessages;
//target没有Handler引用取下一条消息
if (msg != null && msg.target == null) {
// Stalled by a barrier. Find the next asynchronous message in the queue.
do {
prevMsg = msg;
msg = msg.next;
} while (msg != null && !msg.isAsynchronous());
}
if (msg != null) {
if (now
8.出完队列后,消息是怎么回调到对应的Handler中的handleMessage方法的?
我们都执到msg.target持有Handler对象,在出队时候回调用dispatchMessage()方法,如果消息传了callback就会执行callback回调,如果没有则走handleMessage()回调。
//Handler.class
public void dispatchMessage(@NonNull Message msg) {
if (msg.callback != null) {
handleCallback(msg);
} else {
if (mCallback != null) {
if (mCallback.handleMessage(msg)) {
return;
}
}
handleMessage(msg);
}
}
好了,现在我们总算讲完了整体的源码分析,接下来我们需要梳理下我们了解到的源码,方便我们形成记忆点,不然看完就忘。
简单做个逻辑总结图:

图片来源:https://blog.csdn.net/yus201120/article/details/82015980
-
我们要初始化一个Handler对象时,就要确保当前的Thread中的ThreadLocalmap对象,能通过Looper的静态ThreadLocal获取到Looper对象,如果获取不到则代表Looper还未在该线程初始化过,需要调用ActivityThread中的prepare()方法进行初始化并对ThreadLocalmap赋值。
-
Handler进行sendMessage()时,由于在Handler的构造中通过Looper.myLooper()拿到Looper对象,所以我们可以拿到Looper中的Messqueue消息队列。消息进行入队操作,该操作会按时间戳链表顺序排序消息,此时消息队列中就有消息了。
-
Looper.loop()方法,在ActivityThread中初始化完myLooper就会调用,作用是,不断轮询消息队列。随后循环内部会调用Messqueue的next()方法取出消息,让消息出队。
为什么不会造成线程阻塞?
主要原因是next()方法中通过nativePollOnce()方法,该方法本质是用c语言执行Linux的poll机制,从而实现在轮询闲置时让出CPU。并且,在消息闲置时,如果有Idle Handler的话,会去执行Idle Handler操作,直到为空时,在下一次next()方法中的轮序,继续调用nativePollOnce让出CPU给其他线程。
-
当looper()轮序取到消息时,会跳出轮询执行msg.target.dispatchMessage()进行回调,本质就是取出消息中的handler对象,调用callback回调或执行handler中的handleMessage(msg)回调。