倒杯茶,细细品,我们聊聊--Handler

本文主要探讨了Android中的Handler机制,从应用启动的ActivityThread开始,解析Looper.prepareMainLooper()和Looper.loop()的作用,以及主线程与Looper、MessageQueue的关系。文章详细阐述了Handler创建、发送消息的过程,包括MessageQueue如何插入消息以及如何通过nativeWake唤醒消息队列。此外,还讨论了Handler可能导致的内存泄漏问题及其解决方案,以及UI线程更新的特殊情况。

之前总习惯于写笔记,总结都是写到笔记中,我的博客有点吃醋了,所以我决定也要宠幸下我的博客,把我平时总结的一些东西更新到博客中,如有不对之处,欢迎指出!

今天的回归之作是聊聊我们的老朋友--Handler。

背景:前几年去参加面试的时候,就开始问Handler了,一直都是停留在会用的层面上,那时候面试,只要能答上:Handler是用于解决子线程更新UI的问题;Handler发送消息,存入Message队列,后台有个Looper一直从Message队列去取,然后message处理事件并回调消息更新UI。OK,这样子基本就算过了。但是后面去翻翻源码,发现又有很多新的收获,那今天就一起来聊聊吧。

首先我们需要复习以及准备一些东西,大家都知道,我们平时写一些测试代码的时候,都需要一个主方法(main),这种规则到处都能看到,在Android中,我们启动一个页面的时候,入口也是一个标记为"android.intent.action.MAIN"的activity,然后我们想一下,应用启动之后是做了什么呢,其实应用启动后的事情由ActivityThread开始,这个ActivityThread就是我们平时说的主线程,按照刚才说的原则,我们在ActivityThread类中发现了这个方法

public static void main(String[] args) {   //方法内容有删减
        ……
        Looper.prepareMainLooper();//初始化当前进程的Looper对象
        ……
        if (sMainThreadHandler == null) {
            sMainThreadHandler = thread.getHandler();
        }
        ……
        Looper.loop();//调用Looper的loop方法开启,这里面是一个for无限循环
        throw new RuntimeException("Main thread loop unexpectedly exited");
    }

是不是感到无比温馨,那我们看一下Looper.prepareMainLooper();做了什么

public static void prepareMainLooper() {
        prepare(false);//创建,实质上就是new一个Looper对象,仔细看看下面prepare方法中的new的地方
        synchronized (Looper.class) {
            if (sMainLooper != null) {
                throw new IllegalStateException("The main Looper has already been prepared.");
            }
            sMainLooper = myLooper();//从sThreadLocal中取出Looper并赋值给sMainLooper
        }
    }

public static @Nullable Looper myLooper() {//代码在类中别的位置,拷过来方便看
        return sThreadLocal.get();
    }

public static void prepare() {
        prepare(true);
    }

private static void prepare(boolean quitAllowed) {
        if (sThreadLocal.get() != null) {//如果sThreadLocal已经和Looper绑定,则抛出异常(目的是确保一个线程中只能执行一次Looper.prepare();
            throw new RuntimeException("Only one Looper may be created per thread");
        }
        sThreadLocal.set(new Looper(quitAllowed));//将new出的Looper设置到(主)线程的本地变量中,与当前变量进行了绑定
    }

原来做了这么多事情,总结来说就是启动了一个主线程,主线程中默认new了一个Looper对象,并和主线程中的变量sThreadLocal进行绑定,并且这个线程只能有一个Looper对象,如果我们在我们的MainActivity中执行Looper.prepare();方法的话,直接就崩了,异常提示就是上面的prepare方法中的异常信息。既然Looper是new出来的,然后我们扒一扒Looper构造方法

private Looper(boolean quitAllowed) {
        mQueue = new MessageQueue(quitAllowed);
        mThread = Thread.currentThread();
    }

看到了吧,是创建了一个MessageQueue对象,真的是好基友啊,有福同享,要生一起生。那么这个MessageQueue就厉害了,既然一个线程只存在一个Looper,那么,也就只new了一个MessageQueue,这样的话,后续我们用handler发消息的话,都是被送到这个MessageQueue中。

大家刚才在ActivtiyThread的main方法中有看到:Looper.loop();这个方法吧,Looper做的事情就是不断地从MessageQueue中取出Message,然后处理指定的任务,怎样不断地获取呢,我们平时写一个程序,没有指定具体的结束时机的,是不是要把执行的程序放到一个死循环中去执行,那么这里的loop方法,就是执行一个死循环,而主线程就靠着这个死循环一直运行着,从而保证app进程能够持续地运行。(这里是不是可以联想一下我们整个手机系统,从开机开始,就是开启了一个死循环然后一直运行,哇,那如果一直不关机,就得一直运行,都没有休息,好可怜!)

好了,到这里呢,我们的背景知识就准备好了,现在我们开始来去扒一扒Handler~

使用handler最终还是从new 一个Handler对象开始,看看源码:

public Handler(@Nullable Callback callback, boolean async) {
       ……

        mLooper = Looper.myLooper();//准备looper对象
        if (mLooper == null) {
            throw new RuntimeException(
                "Can't create handler inside thread " + Thread.currentThread()
                        + " that has not called Looper.prepare()");
        }
        mQueue = mLooper.mQueue;//准备messagequeue对象
        mCallback = callback; //这里要注意,如果是sendMessage的方式,需要用到这个变量
        mAsynchronous = async;
    }


public static @Nullable Looper myLooper() {
        return sThreadLocal.get();//就是主线程初始化绑定的那个looper
    }

在主线程new一个handler的时候,就把前面new好的looper和messagequeue准备好了,(子线程也是可以创建handler对象的,但是需要执行Looper.prepare()方法,不然这里的mLooper就为null,会报异常),然后经过一顿操作,不管是通过什么方式,最终都是走  sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);这个方法,这里的SystemClock.uptimeMillis() + delayMillis 是系统时间加上我们设置的延迟的时间相加,在message插入队列的时候会用到。

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
        MessageQueue queue = mQueue;//获取handler绑定的消息队列,这里的mQueue就是上面 mLooper.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);
    }

总结来说,就是handler调用sendMessage方法时,首先会获取到当前的handler绑定的messagequeue (mQueue),而handler发出的消息会将该handler实列对象赋值给该message的target属性,然后handler中最终会调用enqueueMessage方法,该方法的功能就是向消息队列插入这个handler  发送的消息

enqueueMessage方法就是核心处理方法了,

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this; //这里还是在handler类中,把当前Handler实例对象作为msg的target属性
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);//调用MessageQueue 的enqueueMessage方法
    }

这里是MessageQueue的enquueMessage方法: 

boolean enqueueMessage(Message msg, long when) {
        ……

        synchronized (this) {
            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);//调用nativ方法来唤醒
            }
        }
        return true;
    }

这里就是把发送的消息插入到队列中的合适位置,最后通过needWake判断是否需要调用底层nativeWake唤醒整个消息队列

handler发送消息的诸多方法中有一个需要分析下:

public final boolean post(@NonNull Runnable r) {
       return  sendMessageDelayed(getPostMessage(r), 0);
    }

private static Message getPostMessage(Runnable r) {
        Message m = Message.obtain();
        m.callback = r;
        return m;
    }

在Message中有一个callback变量,而post的发送方式,将runnable赋值给message的callback,然后执行dispatchMessage方法的时候,分两种情况:一、msg的callback 不为空,就是通过post方法发送消息的,会直接执行runnable中的run方法,实质上就是一个回调接口。二、msg中的callback为空,则是通过sendmessage发送消息的,直接调用handler的handlemessage方法。看一下handler中的dispatchMessage方法,

 public void dispatchMessage(@NonNull Message msg) {
        if (msg.callback != null) {   //post 的方式,传入了runnable
            handleCallback(msg);
        } else {
            if (mCallback != null) {   //这个在上面谈到handler的构造函数的时候有说到
                if (mCallback.handleMessage(msg)) {
                    return;
                }
            }
            handleMessage(msg);
        }
    }

private static void handleCallback(Message message) {
        message.callback.run();//runnable实际上是一个回调接口,和线程没关系
    }

public void handleMessage(@NonNull Message msg) {
    }

至此handler的执行流程就结束了,然后我们继续探究几个比较重要的点:

1、之前我们都是说UI更新必须在主线程(UI线程)中操作,其实也不一定,这里是指通常情况下,当然也有特殊场景,SurfaceView在非UI线程中也是可以更新的,而且有时候在某些特定的场景下,子线程会趁主线程不注意,偷偷更新了UI,这里是指一些简单的view的更新。解释说明一下,这个场景是之在oncreate方法中创建子线程更新UI,因为在onresume执行之前,viewroot还没创建,不会有checkThread的检查机制,所以线程执行在onresume之前的话,那些简单的更新UI操作会被保留生效。

2、Looper.loop()是个死循环,为什么不会卡死UI?这个就涉及到一些native的知识了,看一下MessageQueue中的next()方法:

@UnsupportedAppUsage
    Message next() {  //内容有删减
        int pendingIdleHandlerCount = -1; // -1 only during first iteration
        int nextPollTimeoutMillis = 0;
        for (;;) {
            if (nextPollTimeoutMillis != 0) {
                Binder.flushPendingCommands();
            }
            nativePollOnce(ptr, nextPollTimeoutMillis);//该函数涉及到linux中的pipe和epoll机制,这里就不过多扩展了

            synchronized (this) { //说明messagequeue是线程安全的
                // Try to retrieve the next message.  Return if found.
                final long now = SystemClock.uptimeMillis();
                Message prevMsg = null;
                Message msg = mMessages;
                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());
                }
            }
        }
    }

当message的队列为空时,就会阻塞在message.next 的nativePollOnce()方法里,执行nativePollOnce方法时,主线程会释放CPU资源进入休眠,然后等到下一条消息到达时,这里采用的是epoll机制,往pipe管道写入数据来唤醒主线程。然后从上面的代码也可以看出messagequeue是线程安全的,因为使用了synchronized (this)  同步锁。

3、前面提到我们也可以在子线程中维护looper,使用完之后记得调用Looper.quitSafely()的方法释放掉,这里最终会调用messagequeue的removeAllFutureMessagesLocked()方法,在该方法中,会判断当前消息队列中的头消息的时间是否大于当前时间,如果大于当前时间就会removeAllMessagesLocked()方法,也就是回收全部消息,反之,则回收部分消息,同时没有被回收的消息任然可以被取出执行。

private void removeAllFutureMessagesLocked() {
        final long now = SystemClock.uptimeMillis();
        Message p = mMessages;
        if (p != null) {
            if (p.when > now) {
                removeAllMessagesLocked();
            } else {
                Message n;
                for (;;) {
                    n = p.next;
                    if (n == null) {
                        return;
                    }
                    if (n.when > now) {
                        break;
                    }
                    p = n;
                }
                p.next = null;
                do {
                    p = n;
                    n = p.next;
                    p.recycleUnchecked();
                } while (n != null);
            }
        }
    }

4、handler怎么引起内存泄漏的?我们使用handler的时候通过new的方式,这里handler是一个内部类,默认持有activity的引用,然后前面谈到handler发送的message对象的target属性设置的就是handler,如此,message就持有handler的引用,当我们调用postDelay()执行演示任务,如果还没到我们设置的delay时间,只要app一直在运行,messagequeue就一直存在,其message对象一直持有handler的引用。这样一来当我们要销毁该activity时,GC回收内存的时候会从GcRoot检测所有内存,发现MessageQueue -> Message -> Handler -> Activity 引用链,那么只要message没有被销毁,activity的引用就一直存在,这样会间接导致activity无法给回收,造成内存泄露。

5、至于如何解决handler造成的内存泄漏问题,有这几种方案:一、静态内部类 + 弱引用,将 Handler 声明为静态内部类,Handler 也就不再持有 Activity 的引用,所以 Activity 可以随便被回收,但是没有了引用之后,handler就不能操作 Activity 中对象,所以可以在 Handler 中添加一个对 Activity 的弱引用( WeakReference );二、activity销毁的时候,调用handler的removeCallbacksAndMessages()方法清空消息队列,这样就可以释放activity从而进行回收。

那么今天handler的内容大致聊到这里,欢迎大佬指正或者补充哈!

 

评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值