最新的Handler源码解析和使用

时间:2021-7-20 作者:qvyue

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流程

  1. 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;
}
  1. 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));
}
  1. 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();
}
  1. 是什么时候为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是怎么发消息的。

  1. 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 
  1. 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()只是对消息进行排序,并没有触发消息,是入队操作。

  1. 有入队就有出队,那出队是怎样的呢?

首先要先回顾一下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);
    }
}

好了,现在我们总算讲完了整体的源码分析,接下来我们需要梳理下我们了解到的源码,方便我们形成记忆点,不然看完就忘。

简单做个逻辑总结图:

最新的Handler源码解析和使用
image-20210315101250303.png

图片来源:https://blog.csdn.net/yus201120/article/details/82015980

  1. 我们要初始化一个Handler对象时,就要确保当前的Thread中的ThreadLocalmap对象,能通过Looper的静态ThreadLocal获取到Looper对象,如果获取不到则代表Looper还未在该线程初始化过,需要调用ActivityThread中的prepare()方法进行初始化并对ThreadLocalmap赋值。

  2. Handler进行sendMessage()时,由于在Handler的构造中通过Looper.myLooper()拿到Looper对象,所以我们可以拿到Looper中的Messqueue消息队列。消息进行入队操作,该操作会按时间戳链表顺序排序消息,此时消息队列中就有消息了。

  3. Looper.loop()方法,在ActivityThread中初始化完myLooper就会调用,作用是,不断轮询消息队列。随后循环内部会调用Messqueue的next()方法取出消息,让消息出队。

    为什么不会造成线程阻塞?

    主要原因是next()方法中通过nativePollOnce()方法,该方法本质是用c语言执行Linux的poll机制,从而实现在轮询闲置时让出CPU。并且,在消息闲置时,如果有Idle Handler的话,会去执行Idle Handler操作,直到为空时,在下一次next()方法中的轮序,继续调用nativePollOnce让出CPU给其他线程。

  4. 当looper()轮序取到消息时,会跳出轮询执行msg.target.dispatchMessage()进行回调,本质就是取出消息中的handler对象,调用callback回调或执行handler中的handleMessage(msg)回调。

声明:本文内容由互联网用户自发贡献自行上传,本网站不拥有所有权,未作人工编辑处理,也不承担相关法律责任。如果您发现有涉嫌版权的内容,欢迎发送邮件至:qvyue@qq.com 进行举报,并提供相关证据,工作人员会在5个工作日内联系你,一经查实,本站将立刻删除涉嫌侵权内容。