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 <= 0) {
mBtnVerify.setText(R.string.sign_up_post_verification);
releaseTimer();
setSentStatus(false);
return;
} else {
mBtnVerify.setText(time + "s");
}
break;
}
}
};
//子线程计时器通知
private void initTimer() {
if (mTimer == null) {
mTimer = new Timer();
}
mTimer.schedule(new TimerTask() {
int totalTime = 300;
@Override
public void run() {
Message msg = new Message();
msg.what = COUNT_DOWN;
msg.obj = totalTime--;
mHandler.sendMessage(msg);
}
}, 1000, 1000);//延迟1秒后执行周期为1秒的事件触发
}
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
//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
- 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 < 0) {
delayMillis = 0;
}
//获取当前时间戳+延迟时间
return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}
public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
//mQueue哪冒出来的?
MessageQueue queue = mQueue;
if (queue == null) {
RuntimeException e = new RuntimeException(
this + " sendMessageAtTime() called with no mQueue");
Log.w("Looper", e.getMessage(), e);
return false;
}
return enqueueMessage(queue, msg, uptimeMillis);
}
//回顾我们的构造
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()");
}
//用到了looper的mQueue对象,那就看看Looper构造
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
//Looper.class
private Looper(boolean quitAllowed) {
//在构造中就为它创建了
mQueue = new MessageQueue(quitAllowed);
mThread = Thread.currentThread();
}
- 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 < p.when) {
// 1.p为空就为发送消息的下一位置空,p不为空,代表发送消息的下一位指向第一条消息.
msg.next = p;
// 第一条消息赋值为发送消息
mMessages = msg;
//needWake:是否需要唤醒主线程去执行消息发送,mBlocked是否阻塞,默认false
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通常不为true,原因是通常一般mBlocked不会阻塞,target即Handler对象也不会为空,isAsynchronous()则代表当前消息同步锁是否生效中
needWake = mBlocked && p.target == null && msg.isAsynchronous();
Message prev;
//开一个死循环
for (;;) {
//prev赋值为第一条消息
prev = p;
//p赋值为第一条消息的下一条
p = p.next;
//下一条消息为空,或者下一条消息时间戳大于发送消息,跳出for循环
if (p == null || when < p.when) {
break;
}
//在前面有为needWake赋值,需要修改一下
if (needWake && p.isAsynchronous()) {
needWake = false;
}
}
//满足上面的条件break,那代表当前消息触发时间在下一条消息之前,修改消息顺序
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;
}
小节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 < msg.when) {
// Next message is not ready. Set a timeout to wake up when it is ready.
//如果当前时间戳小于消息要执行的时间戳,计算剩余时间差,用于下次轮询让出cpu
nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
} else {
// Got a message.
//取到消息
mBlocked = false;
if (prevMsg != null) {
prevMsg.next = msg.next;
} else {
mMessages = msg.next;
}
msg.next = null;
if (DEBUG) Log.v(TAG, "Returning message: " + msg);
msg.markInUse();
return msg;
}
} else {
// No more messages.
//没有消息赋值为-1,用于下次循环调用nativePollOnce()方法,放弃CPU进行无限等待
nextPollTimeoutMillis = -1;
}
// Process the quit message now that all pending messages have been handled.
//当消息退出消息队列时才会为true
if (mQuitting) {
dispose();
return null;
}
// If first time idle, then get the number of idlers to run.
// Idle handles only run if the queue is empty or if the first message
// in the queue (possibly a barrier) is due to be handled in the future.
//第一次闲置、消息队列为空或者为第一条消息,才会执行idleHandler的操作,由名字可知IdleHandler就是当队列闲置时,才会执行,可以通过MessageQueue中的addIdleHandler()方法添加。
//默认第一次pendingIdleHandlerCount = -1, size()为IdleHandler数量
if (pendingIdleHandlerCount < 0
&& (mMessages == null || now < mMessages.when)) {
pendingIdleHandlerCount = mIdleHandlers.size();
}
//无闲置Handler,直接下次轮询让出cpu
if (pendingIdleHandlerCount <= 0) {
// No idle handlers to run. Loop and wait some more.
mBlocked = true;
continue;
}
if (mPendingIdleHandlers == null) {
mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
}
mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
}
// Run the idle handlers.
// We only ever reach this code block during the first iteration.
// 到现在已经退出同步锁了,剩下代表主要是执行IdleHandler操作
for (int i = 0; i < pendingIdleHandlerCount; i++) {
final IdleHandler idler = mPendingIdleHandlers[i];
mPendingIdleHandlers[i] = null; // release the reference to the handler
boolean keep = false;
try {
keep = idler.queueIdle();
} catch (Throwable t) {
Log.wtf(TAG, "IdleHandler threw exception", t);
}
if (!keep) {
synchronized (this) {
mIdleHandlers.remove(idler);
}
}
}
// Reset the idle handler count to 0 so we do not run them again.
// 执行完毕idle handler
pendingIdleHandlerCount = 0;
// While calling an idle handler, a new message could have been delivered
// so go back and look again for a pending message without waiting.
nextPollTimeoutMillis = 0;
}
}
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)回调。