Handler的基本使用+细节解说Handler源码

一、前言

Handler,大家基本上多多少少都了解吧。我一开始只是用它来做发消息接收消息处理的功能,对它内部的运作还没去解析。最近看了下源码,算是弄懂了吧,来总结和记录下Handler内部的运作过程,本文章字数较多,只要耐心看完后,大家都对它都会有很深的了解。

二、主线程和子线程的使用

先来说说我们最常见的用法吧。主要分为两种情况:一是主线程使用,二是在子线程使用。

先看看主线程中的使用

//主线程中使用
class HandlerActivity: AppCompatActivity() {

    private val mHandler = MyHandler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        // 通过自定义的 Handler 发消息
        mHandler.sendEmptyMessageDelayed(1, 1000)
    }

    // 自定义一个 Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "主线程:handleMessage: ${msg.what}")
        }
    }
}

分析:
Handler主要是有发消息接收消息后处理的作用

① mHandler = MyHandler(),看到MyHandler(),它是一个自定义的Handler,其中重写了handleMessage(msg)方法,不难理解,这是接收到消息后,对消息进行想要的出来里,这一部分就是处理消息。

② 这个mHandler是在HandlerActivity里面初始化的,没做处理的话默认是表示这个mHandler是主线程中初始化的,后面再细说handler跟线程的关系。

③ onCreate()中有,mHandler.sendEmptyMessageDelayed(1, 1000),这一部分就是Handler发消息的作用了。第一个参数是int类型的,是msg.what,这个后续再细讲message中的属性;第二个参数是这个消息延迟时间,1000表示是1s。

所以在这里的流程是,onCreate时,mHandler发了一个消息,msg.what是1,延迟1s执行,之后1s后,打印了"HandlerActivity", "主线程:handleMessage: 1"。

再看看子线程的使用

//子线程中调用
class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //创建一个子线程
        Thread { 
            mHandler = MyHandler()
            mHandler?.sendEmptyMessageDelayed(1, 1000)
        }.start()
    }

    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "子线程:handleMessage: ${msg.what}")
        }
    }
}

按照刚才在主线程中方法,子线程也这么做,看起来逻辑没啥,运行一下,发现报错了,看下日志:Can't create handler inside thread that has not called Looper.prepare(),翻译一下,大概就是说我们不能在一个没有调用Looper.prepare()的线程去创建Handler对象

那为什么主线程我们不需要去手动调用Looper.prepare()就可以直接使用Handler呢?原来是启动App时,系统帮我们创建好了,App的入口,是ActivityThread.main方法,创建后主线程就可以正常运作了。

   //ActivityThread.java
   public static void main(String[] args) {
       //......
       Looper.prepareMainLooper();
       //
       ActivityThread thread = new ActivityThread();
       thread.attach(false);
       if (sMainThreadHandler == null) {
           sMainThreadHandler = thread.getHandler();
       }
       if (false) {
           Looper.myLooper().setMessageLogging(new
                   LogPrinter(Log.DEBUG, "ActivityThread"));
       }
       //
       Looper.loop();

       throw new RuntimeException("Main thread loop unexpectedly exited");
   }

重点看三个部分吧
1、Looper.prepareMainLooper(),点进源码发现最终调用了Looper.prepare()
2、创建Handler对象
3、调用Looper.loop()

回到刚才子线程使用handler,发现少了Looper.prepare()和调用Looper.loop(),加上去再试试

//子线程中调用
class HandlerActivity: AppCompatActivity() {
    private var mHandler: Handler? = null

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        //创建一个子线程
        Thread { 
            Looper.prepare()
            mHandler = MyHandler()
            mHandler?.sendEmptyMessageDelayed(1, 1000)
            Looper.loop()
        }.start()
    }

    class MyHandler(): Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "子线程:handleMessage: ${msg.what}")
        }
    }
}

发现不会报错了,此时handler就可以在子线程处理消息了。那Looper是什么东西呢,有什么作用,后面会讲到的。

小结一下:子线程使用handler时,要在两头添加Looper.prepare()和调用Looper.loop()。

三、Handler 内部是如何运转的?

Handler

发消息的入口是什么呢?就是刚才说的Handler,但实际上它有好多种构造方式,看下Handler.java的源码吧

//Handler.java
//构造一
public Handler() {
    this(null, false);
}
//构造二
public Handler(@Nullable Callback callback, boolean async) {
    if (FIND_POTENTIAL_LEAKS) {
        final Class<? extends Handler> klass = getClass();
        if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass()) &&
                (klass.getModifiers() & Modifier.STATIC) == 0) {
            Log.w(TAG, "The following Handler class should be static or leaks might occur: " +
                klass.getCanonicalName());
        }
    }

    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;
}
//构造三
public Handler(@NonNull Looper looper) {
    this(looper, null, false);
}
//构造四
public Handler(@NonNull Looper looper, @Nullable Callback callback) {
    this(looper, callback, false);
}
//构造五
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

分析:
① 构造一最终是调用了构造二;构造三、构造四最终调用了构造五

② 两种构造的差别就是有无Looper传入。

③ 当没有Looper传入时,会调用mLooper = Looper.myLooper(),来看看Looper.myLooper()是啥,点进去

//Looper.java

// sThreadLocal.get() will return null unless you've called prepare().
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

public static @Nullable Looper myLooper() {
    return sThreadLocal.get();
}

看到ThreadLocal就可以知道,looper是和线程相关的,同时也说明了一个线程中只会有一个Looper。再看到注释,翻译下就是:如果要获取一个非空的Looper,那么必须在这个线程中调用一次Looper.prepare()。最终返回个Looper对象回去。

④ 当传入Looper时,只需要将传入的Looper保存起来,但是要注意的是,Looper不能为null。Handler创建时传入的Looper,一般是传Looper.myLooper(),就是跟线程有关,可以理解成,你在哪个线程创建Handler,就跟looper所在线程有关,looper在子线程,handler就在子线程;looper在主线程,handler就在主线程。只不过在传入前,要确定已经调用了Loop.prepare()。

知道了怎去创建Handler后,接着看看怎么它是怎么发送消息的吧

Handler发消息有很多种方法,看下源码

// Handler.java

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

public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r), uptimeMillis);
}

public final boolean postDelayed(@NonNull Runnable r, long delayMillis) {
    return sendMessageDelayed(getPostMessage(r), delayMillis);
}

public final boolean sendEmptyMessage(int what) {
    return sendEmptyMessageDelayed(what, 0);
}

public final boolean sendEmptyMessageDelayed(int what, long delayMillis) {
    Message msg = Message.obtain();
    msg.what = what;
    return sendMessageDelayed(msg, delayMillis);
}

public final boolean postAtTime(@NonNull Runnable r, @Nullable Object token, long uptimeMillis) {
    return sendMessageAtTime(getPostMessage(r, token), uptimeMillis);
}

public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) {
    if (delayMillis < 0) {
        delayMillis = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis);
}

public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    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);
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

不要看它有那么发送消息的方法就怕了,仔细看下,到最后都会去到enqueueMessage方法中,最终去到了MessageQueue(消息队列)中的enqueueMessage方法,我们只要分析这个方法就可以了,注意,两个类的方法名都相同,不要混淆了,只要记住是从Handler到MessageQueue就可以了。至于MessageQueue的具体作用,后续会讲到,一步步来,Handler从创建到发消息基本已经讲完了,接下来看看之前说的Looper到底是啥!

Looper

Looper是啥?其实它就是个轮询器,我们从handler发送的消息,最终一般都会来到这个Looper(中间其他细节先省略,先看下Looper怎么处理消息),然后对消息进行分发处理。先看下刚才一直说的Looper.prepare()。

// Looper.java

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));
}

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

分析:
① 只要调过Looper.prepare()后,再掉此方法,会报异常,也就是说一个线程只能调用一次Looper.prepare()。

② 第一次调用时,sThreadLocal.set(new Looper(quitAllowed)),会new一个Looper实例出来,其中会新建一个消息队列MessageQueue,还有会把当前线程记录下来。注意:在Looper创建时,就对应的生成一个消息队列

小结一下:至此的顺序,先是Looper.prepare()后,新建了个Looper,同时生成这个Looper的消息队列MessageQueue,并把当前线程记录下来,后续创建Handler时,就可以把Looper传进去或者调用Looper.myLooper()。

看完准备工作后,接下来看看Looper.loop()

// Looper.java

public static void loop() {

    final Looper me = myLooper();
    
    if (me == null) {
        throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    
    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.dispatchMessage(msg);
            //省略一些代码...
        } catch (Exception exception) {
            //省略一些代码...
            throw exception;
        } finally {
            //省略一些代码...
        }

        //省略一些代码...

        msg.recycleUnchecked();
    }
}

分析:
① 先得到当前线程的Looper对象,判断是否Looper.prepare(),然后再取到当前线程创建好的MessageQueue对象,之后跳进死循环不断的调用MessageQueue中的next方法取出消息队列中的Message消息,注意,当MessageQueue中没消息时,next方法会阻塞,导致线程挂起,并不是返回null,只有返回null的时候才return出死循环,后续会讲到这点。

② 如果msg不为空时,会调用它的target的dispatchMessage方法,这个方法在Handler中,最终都会处理Handler中发送的消息,只是分发处理的形式有三种,target后续在MessageQueue中会一起讲到

// Handler.java

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

private static void handleCallback(Message message) {
    message.callback.run();
}

/**
 * Subclasses must implement this to receive messages.
 */
public void handleMessage(@NonNull Message msg) {
}

public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    boolean handleMessage(@NonNull Message msg);
}

这里其实就是把你当前的消息分发出去,有三种情况:
1、msg.callback不为null时,直接调handleCallback方法,其实就是执行msg.callback里面的代码,那msg.callback是什么呢?还记得我们用Handler用post方法发消息时,传了个Runnable对象,可以理解成一些操作,比如打印日志或者弹吐司...,然后这个对象呢又给了getPostMessage()处理

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

首先新建一个消息m,再把传入的Runnbale对象保存在消息m的callback属性里,这样,消息的callback就不为null了,所以,一般我们用post方式发的消息,都会执行Runnable里面的代码,这里的msg主要用来存放这些代码。

2、msg.callback为null时,判断mCallback是否为null。注意,这个mCallback和上面的callback不同,mCallback它是Handler里面的接口,而callback是msg.callback里面的属性。还记得在构建Handler时,我们传入一个Handler.Callback对象吗?传入的Handler.Callback对象对应的就是这里的mCallback,也就是说,在使用Handler的时候,并不一定要继承Handler来处理消息,也可以通过实现Callback这个接口,来处理消息。有传则mCallback.handleMessage(msg)处理消息,注意,这个方法有返回值,如果返回了true,表示已经处理 ,不再调用Handler的handleMessage方法(第3点);如果mCallback为空,或者不为空但是它的handleMessage返回了false,则会继续调用Handler的handleMessage方法,直接走到第3点

3、msg.callback为null且mCallback为null时,直接走handleMessage(msg),这个方法也就是我们在继承Handler时复写的方法,也就是说,发送的消息将在这里面处理。


Message分发.png

③msg.recycleUnchecked(),处理后的消息,将走到这里,将消息回收到回收池里面,并标记这个msg.flags = FLAG_IN_USE,表示被使用了,然后消息里面其他信息都被清除。

注:Loop是一个循环器,它也有停止循环的功能,之前在第①点说到了,消息队列MessageQueue中next返回null时才退出循环,Looper提供了quit和quitSafely方法来停止Looper,后续在MessageQueue中会分析下流程。

小结一下:Handler是用来发消息和处理消息的,而Looper是一个轮询器,只要有用的消息都会把消息丢回给Handler通知去分发处理。简单点说,只要有Handler发消息,中间一系列操作后(MessageQueue接下来会将),消息Message会来到Looper里面,Looper分析完这个消息Message可以用后,就把消息丢回给Handler,按照Handler创建的方式去分发处理消息。

MessageQueue

前面一直说的MessageQueue,究竟是啥?其实它就是一个单向链表队列的数据结构,用来存放消息的一个队列,都叫它消息队列,每个单位都存有一个Message消息,也就是用Handler发送的消息,基本都会来到这个队列里面去排序,而排序的标准就是按照发送过来带有的延迟时间。还记得Handler发送消息的时候,最后调用的是MessageQueue的enqueueMessage吗?来看看这个方法

// MessageQueue.java

boolean enqueueMessage(Message msg, long when) {

    // 省略一些代码...
        if (msg.target == null) {
            throw new IllegalArgumentException("Message must have a target.");
        }
        // 一个Message,只能发送一次
        if (msg.isInUse()) {
            throw new IllegalStateException(msg + " This message is already in use.");
        }
    
    synchronized (this) {
        if (mQuitting) {
            IllegalStateException e = new IllegalStateException(
                    msg.target + " sending message to a Handler on a dead thread");
            msg.recycle();
            return false;
        }

        msg.markInUse();
        msg.when = when;
        
        //【1】拿到队列头部
        Message p = mMessages;
        boolean needWake;
        
        //【2】如果消息不需要延时,或者消息的执行时间比头部消息早,插到队列头部
        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 {
            //【3】消息插到队列中间
            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;
        }

        if (needWake) {
            nativeWake(mPtr);
        }
    }
    return true;
}

分析:
① 消息进来后,首先,判断了Message是否已经使用过了,如果使用过,则直接抛出异常,这是可以理解的,如果MessageQueue中已经存在一个Message,但是还没有得到处理,这时候如果再发送一次该Message,可能会导致处理前一个Message时,出现问题。接着标记一下msg.markInUse()表示已经使用了,再把延迟时间when给了msg.when,再接着拿到队列的头部消息mMessage。

注:msg.target是消息在Handler到MessageQueue之前,先把当前Handler与消息绑定的,如果msg.target为null时,会直接抛出异常。

//Handler.java
    private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
        msg.target = this;
        msg.workSourceUid = ThreadLocalWorkSource.getUid();

        if (mAsynchronous) {
            msg.setAsynchronous(true);
        }
        return queue.enqueueMessage(msg, uptimeMillis);
    }

② 判断消息队列是不是空的,是则将当前的消息放到队列头部;如果当前消息不需要延时,或当前消息的执行时间比头部消息早,也是放到队列头部。

③ 如果不是以上情况,说明当前队列不为空,并且队列的头部消息执行时间比当前消息早,需要将它插入到队列的中间位置。这里也有个死循环,首先把头消息取出来给prev,再把头消息的下一个消息给到p,进行判断,假如p == null,在这里就是头消息后面没有消息了,跳出循环,把当前消息插到头结点;或者when < p.when,当前这个传入的消息比p的延迟时间还要短时,跳出循环,把当前消息插到p的前面。如果没有符合条件的位置,就一直循环从头消息一个个找下去,找到符合的条件的位置插入消息。

④ 插入消息后,会去判断当前线程处于挂起的状态,则需要唤醒。nativeWake是NDK里面的方法,用于唤醒挂起的线程,看下needWake这个标志是怎么取到最终的值的:
1、【队列为空 || 消息无需延时 || 或消息执行时间比队列头部消息早】 && 【线程处于挂起状态时(mBlocked = true)】就会唤醒线程。
2、【线程挂起(mBlocked = true)&& 消息循环处于同步屏障状态】,这时如果插入的是一个异步消息,则需要唤醒。(同步屏障状态后续会讲到)

小结一下:handler发送消息后,会把消息传到消息队列MessageQueue,先检查消息是否异常,无异常标记消息被使用后继续判断消息应该插入的位置,插入成功最后判断是否要唤醒线程。

接下来说一下之前Looper.loop()里面提到的,MessageQueue的next方法,Loop.loop()会在这里面把消息取出来(next返回了Message)

// MessageQueue.java

Message next() {
    //····
    int pendingIdleHandlerCount = -1; // -1 only during first iteration
    int nextPollTimeoutMillis = 0;
    for (;;) {
        
        //【1】调用 NDK 层方法,线程阻塞挂起,进入等待
        // nextPollTimeoutMillis = -1 时,进入无限等待,直到有人唤醒
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            //【2】判断队列是否插入了同步屏障,是则只执行异步消息
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    //【3】消息没到时间,重新计算等待时间
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    //【4】消息时间已到,重新拼接链表,并返回该消息
                    mBlocked = false;
                    if (prevMsg != null) {
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    msg.markInUse();
                    
                    return msg;
                }
            } else {
                // 没有消息,进入无限等待
                nextPollTimeoutMillis = -1;
            }

            if (mQuitting) {
                dispose();
                return null;
            }

            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {
                pendingIdleHandlerCount = mIdleHandlers.size();
            }
            
            // 只有for循环的第一次为 -1 ,
            // 执行一次以后,pendingIdleHandlerCount变成 0,
            // 不再执行空闲监听回调
            if (pendingIdleHandlerCount <= 0) {
                // mBlocked 为true,表明线程阻塞挂起
                mBlocked = true;
                continue;
            }
        
        // 这里设置为0,则上面的空闲监听器不再执行了
        pendingIdleHandlerCount = 0;
        
        nextPollTimeoutMillis = 0;
    }
}

分析:
① 开始设定了(空闲监听器)pendingIdleHandlerCount = -1,nextPollTimeoutMillis = 0,进入死循环,首先调用了本地方法的nativePollOnce方法,用于阻塞MessageQueue,使其进入等待状态。
1、如果nextPollTimeoutMillis=-1,一直阻塞不会超时。
2、如果nextPollTimeoutMillis=0,不会阻塞,立即返回。
3、如果nextPollTimeoutMillis>0,最长阻塞nextPollTimeoutMillis毫秒(超时),如果其间有程序唤醒会立即返回。
这里的状态是0,所以刚进来时不会阻塞。

② 接着到了msg != null && msg.target == null,这里是判断队列是否插入同步屏障(后续会讲到,可以先跳过这一点),如果插入了则只执行异步的消息,同步屏障存在时,同步消息均无法执行,只有异步消息可以。

③ 取到了消息,先判断消息是否为null,如果不是则进入第④点,如果是则表明当前消息队列里面没消息,进入无限等待,nextPollTimeoutMillis = -1。

④ 消息不为null时,判断是否now < msg.when,如果是则时间没到,重新计算等待时间,如果不是则时间已经到了,mBlocked = false取消挂起状态,重新拼接链表,最后把消息返回出去。

⑤ 如果消息没被处理返回出去,接着往下走,判断mQuitting,为true则返回null回去,Looper.loop()取到null后,跳出loop()的死循环,也就结束了轮询,之前有提到,next里面取到的消息为null时结束轮询。Looper可以调quit和quitSafely

    //Looper.java
    public void quit() {
        mQueue.quit(false);
    }

    public void quitSafely() {
        mQueue.quit(true);
    }

    //MessageQueue.java
    void quit(boolean safe) {
        synchronized (this) {
            if (mQuitting) {
                return;
            }

            // MessageQueue正在停止,用于next方法退出死循环
            mQuitting = true;

            if (safe) {
                // 删除MessageQueue中的延时消息
                removeAllFutureMessagesLocked();
            } else {
                // 删除MessageQueue中的所有消息
                removeAllMessagesLocked();
            }

            // We can assume mPtr != 0 because mQuitting was previously false.
            nativeWake(mPtr);
        }
    }

    private void dispose() {
        if (mPtr != 0) {
            nativeDestroy(mPtr);
            mPtr = 0;
        }
    }

首先把mQuitting设置为true,主要用于MessageQueue的next方法退出死循环,然后通过safe去判断逻辑逻辑,这里就可以看出Looper的quit和quitSafely的区别了
1、quit: 删除MessageQueue中所有消息
2、quitSafely: 删除MessageQueue中的延时消息
这样mQuitting为true就会return null出去了。再看下dispose(),其实就是调用了nativeDestroy方法,它是一个native方法,用于在底层停止MessageQueue。

⑥ 接着看,一般都会满足pendingIdleHandlerCount <= 0条件,然后mBlocked设置为true,继续continue,之后又设置了pendingIdleHandlerCount = 0,nextPollTimeoutMillis = 0,用于下个消息进来时用。

小结一下:next的操作主要是从队列里面取消息用于Looper里面回调给Handler分发处理,next中如果有符合条件的Message则会结束next里面的死循环,并返回Message,如果没有合适的消息则会一直循环去取,直到取到;若next返回了null出去,则Looper里面取到null,之后Looper也结束它的轮询(死循环)。

消息池

网上常有文章提到,在生成消息的时候,最好是用Message.obtain()来获取一个消息,这是为什么呢?先来看看这个方法:

// Message.java

public static Message obtain() {
    synchronized (sPoolSync) {
        if (sPool != null) {
            Message m = sPool;
            sPool = m.next;
            m.next = null;
            m.flags = 0; // clear in-use flag
            sPoolSize--;
            return m;
        }
    }
    return new Message();
}

可以看到,这是一个消息池,如果消息池不为空,就会从池中获取一个消息,达到复用的效果。那消息是如何被回收到池子里面的呢?之前在Looper.loop()里面对消息分发处理完成后,会做msg.recycleUnchecked()

// Message.java

private static final int MAX_POOL_SIZE = 50;

void recycleUnchecked() {
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = UID_NONE;
    workSourceUid = UID_NONE;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

原来,消息池也是一个消息链表队列,除去了消息所有的信息以后,就会把消息加入队列头部。同时可以看到,消息池的最大缓存数量为 50 。消息池,可以避免大量的消息被创建,节省内存。所以,尽量通过 Message.obtain() 来创建消息。

三、Handler同步消息屏蔽

前面在MessageQueue.next()里面有说到了判断同步消息屏蔽,如果设置了则之后的只要是同步消息都会被屏蔽掉,只有异步消息才能执行。我们平时默认的发出的消息都是同步消息,那怎么设定异步消息呢?还记得在创建Handler时,要传入一个boolean值,传的话系统默认传false,这个Boolean表示是否设定为异步消息。

// Handler.java
public Handler(@Nullable Callback callback, boolean async) {
    //··· 
    // 设置为异步
    mAsynchronous = async;
}

private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
            long uptimeMillis) {
    //···
    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

异步:async = true -> mAsynchronous = true -> msg.setAsynchronous(true)

启动/清除同步消息屏障

//MessageQueue.java
public int postSyncBarrier() {
    return postSyncBarrier(SystemClock.uptimeMillis());
}

private int postSyncBarrier(long when) {
    synchronized (this) {
        final int token = mNextBarrierToken++;
        final Message msg = Message.obtain();
        msg.markInUse();
        msg.when = when;
        msg.arg1 = token;

        Message prev = null;
        Message p = mMessages;
        if (when != 0) {
            while (p != null && p.when <= when) {
                prev = p;
                p = p.next;
            }
        }
        if (prev != null) { // invariant: p == prev.next
            msg.next = p;
            prev.next = msg;
        } else {
            msg.next = p;
            mMessages = msg;
        }
        return token;
    }
}

同步屏障是通过MessageQueue的postSyncBarrier方法开启的。
分析:
① 获取屏障的的唯一标示,标示从0开始,自加1。
② 从Message消息对象池中获取一个msg,设置msg为正在使用状态,并且重置msg的when和arg1,arg1的值设置为token值。但是这里并没有给tareget赋值。所以msag的target是否为空是判断这个msg是否是屏障消息的标志。
③ 创建变量pre和p,为下一步做准备。其中p被赋值为mMessages,mMessages指向消息队列中的第一个元素,所以此时p指向消息队列中的第一个元素。
④ 通过对队列中的第一个Message的when和屏障的when进行比较,决定屏障消息在整个消息队列中的位置,因为消息队列中的消息都是按时间排序的。
⑤ prev != null,代表不是消息的头部,把msg插入到消息队列中。
⑥ prev == null,代表是消息队列的头部,把msg插入消息的头部。

小结一下:开启屏障需要msg.target为null,postSyncBarrier中从Message消息对象池中获取一个msg,插入到消息队列中,这个msg的target == null,所以这个消息的作用就是开启同步消息屏蔽。

//MessageQueue.java
public void removeSyncBarrier(int token) {
    synchronized (this) {
        Message prev = null;
        Message p = mMessages;
        while (p != null && (p.target != null || p.arg1 != token)) {
            prev = p;
            p = p.next;
        }
        if (p == null) {
            throw new IllegalStateException("The specified message queue synchronization "
                    + " barrier token has not been posted or has already been removed.");
        }
        final boolean needWake;
        if (prev != null) {
            prev.next = p.next;
            needWake = false;
        } else {
            mMessages = p.next;
            needWake = mMessages == null || mMessages.target != null;
        }
        p.recycleUnchecked();

        if (needWake && !mQuitting) {
            nativeWake(mPtr);
        }
    }
}

同步屏障的移除是在MessageQueue.java的removeSyncBarrier()方法。
删除屏障消息的方法很简单,就是不断遍历消息队列,知道找到屏障消息,退出循环的条件有两个,一是p.target == null,说明是屏障消息,二是p.arg1 == token,也说明p是屏障消息,因为在屏障消息入队的时候,设置过 msg.arg1 = token。找到屏障消息后,把它从消息队列中删除并回收。

四、总结

Handler机制最主要的部分有:
1、Handler:用来发送和接收处理消息的,一个线程可以有多个Handler对象。
2、Looper:轮询器,用于消息的遍历,使用Handler前需Looper.prepare(),创建一个Looper和一个MessageQueue,再把当前线程与Looper绑定。一个线程最多只有一个Looper。
3、MessageQueue:消息队列,用于存放Handler发送的消息,对消息进行时间排序,供Looper循环取消息,一个线程最多只有一个MessageQueue。
4、Message:信息的携带者,持有了Handler,存在MessageQueue中,一个线程可以有多个


图解.png

五、关于Handler的经典问题

Handler的内存泄漏

通常 Handler 导致的内存泄漏,都是引起 Activity 的内存泄漏。可能日常是这样使用 Handler 的:

class HandlerActivity: AppCompatActivity() {

    private val mHandler = MyHandler()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        
        mHandler.sendEmptyMessageDelayed(1, 1000)
    }

    // 自定义一个 Handler
    class MyHandler: Handler() {
        override fun handleMessage(msg: Message) {
            Log.i("HandlerActivity", "handleMessage: ${msg.what}")
        }
    }
}

在进入页面以后,发送了一个延时 1s 的消息,如果 HandlerActivity 在 1s 内退出了,由于 Handler 会被 Message 持有,保存在其 target 变量中,而 Message 又会被保存在消息队列中,这一系列关联,导致 HandlerActivity 在退出的时候,依然会被持有,因此不能被 GC 回收,这就是内存泄漏!
那么当这个 1s 延时的消息被执行完以后,HandlerActivity 会被回收吗?答案是会的!
因为消息被执行以后,Message 在放入缓存池之前,target 已经被清除,所以持有链就断开了,最终 HandlerActivity 将会被回收。
但这样并不意味着我们就可以不管内存泄漏的问题,内存泄漏始终是内存泄漏,如果是占用内存很大的页面没有被及时回收,有可能会导致 OOM 。
有两个办法可以解决这个问题:
① 将 MyHandler 改为静态类,这样它将不再持有外部类的引用。可以将 HandlerActivity 作为弱引用放到 MyHandler 中使用,页面退出的时候可以被及时回收。
② 页面退出的时候,在 onDestroy 中,调用 Handler 的 removeMessages 方法,将所有的消息 remove 掉,这样也能消除持有链。
个人还是觉得选择②去解决内存泄漏问题比较简单点。

为什么说 Android 是基于消息驱动的

当我们打开Apk时,系统会为Apk创建一个线程,这个就是主线程,而主线程的入口就在ActivityThread.java 中的 main() 方法。

// ActivityThread.java

public static void main(String[] args) {
    Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, "ActivityThreadMain");

    AndroidOs.install();

    CloseGuard.setEnabled(false);

    Environment.initForCurrentUser();

    final File configDir = Environment.getUserConfigDirectory(UserHandle.myUserId());
    TrustedCertificateStore.setDefaultUserDirectory(configDir);

    Process.setArgV0("<pre-initialized>");

    //【1】准备主线程 Looper
    Looper.prepareMainLooper();

    long startSeq = 0;
    if (args != null) {
        for (int i = args.length - 1; i >= 0; --i) {
            if (args[i] != null && args[i].startsWith(PROC_START_SEQ_IDENT)) {
                startSeq = Long.parseLong(
                        args[i].substring(PROC_START_SEQ_IDENT.length()));
            }
        }
    }
    
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    //【2】获取 Handler
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }

    if (false) {
        Looper.myLooper().setMessageLogging(new
                LogPrinter(Log.DEBUG, "ActivityThread"));
    }

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    
    //【3】开启消息循环
    Looper.loop();

    throw new RuntimeException("Main thread loop unexpectedly exited");
}

这根本就是启动 Handler 最基本的方法,来看看 getHandler() 方法:

// ActivityThread.java

final H mH = new H();

final Handler getHandler() {
    return mH;
}

简单看下 H 的部分代码:

// ActivityThread.java

class H extends Handler {
    public static final int BIND_APPLICATION        = 110;
    @UnsupportedAppUsage
    public static final int EXIT_APPLICATION        = 111;
    @UnsupportedAppUsage
    public static final int RECEIVER                = 113;
    @UnsupportedAppUsage
    public static final int CREATE_SERVICE          = 114;
    @UnsupportedAppUsage
    public static final int SERVICE_ARGS            = 115;
    @UnsupportedAppUsage
    public static final int STOP_SERVICE            = 116;
    
    // 省略部分代码...
    
    public static final int EXECUTE_TRANSACTION = 159;
    public static final int RELAUNCH_ACTIVITY = 160;
    
    // 省略部分代码...
    
    public void handleMessage(Message msg) {
        if (DEBUG_MESSAGES) Slog.v(TAG, ">>> handling: " + codeToString(msg.what));
        switch (msg.what) {
            case BIND_APPLICATION:
                AppBindData data = (AppBindData)msg.obj;
                handleBindApplication(data);
                break;
            case EXIT_APPLICATION:
                if (mInitialApplication != null) {
                    mInitialApplication.onTerminate();
                }
                Looper.myLooper().quit();
                break;
            case CREATE_SERVICE:
                handleCreateService((CreateServiceData)msg.obj);
                break;
                
            // 省略部分代码...
            
            // 处理 Activity 生命周期相关的事件
            // 如 onCreate/onResume/onPause等
            case EXECUTE_TRANSACTION:
                final ClientTransaction transaction = (ClientTransaction) msg.obj;
                mTransactionExecutor.execute(transaction);
                
                // 省略部分代码...
                break;
            case RELAUNCH_ACTIVITY:
                handleRelaunchActivityLocally((IBinder) msg.obj);
                break;
            case PURGE_RESOURCES:
                schedulePurgeIdler();
                break;
        }
        
        // 省略部分代码...
    }
}

可以看到,几乎从 App 启动,到 Activity 相关的生命周期,Services 相关的生命周期,到 App 退出等等,都是通过 Handler 来驱动的。

注:App在启动的时候,系统就已经创建好了主线程的Looper。

Loop.looper() 死循环为什么不会阻塞主线程

App 的主线程中通过 Loop.looper() 开启了消息“死循环”,那为什么 App 依然可以正常运转,没有阻塞到主线程呢?主线程无时无刻都在处理消息,因为手机是有屏幕刷新率的,会不断的发消息通知刷新,光这一点就说明主线程不会被阻塞。

以上,是笔者对Handler机制的理解,还有一些涉及到NDK的内容,还不太了解,本章只涉及到了线程的挂起和唤醒。有写的不好的地方麻烦大佬们帮忙指出~

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,214评论 6 481
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,307评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 152,543评论 0 341
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,221评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,224评论 5 371
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,007评论 1 284
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,313评论 3 399
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 36,956评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,441评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 35,925评论 2 323
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,018评论 1 333
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,685评论 4 322
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,234评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,240评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,464评论 1 261
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,467评论 2 352
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,762评论 2 345

推荐阅读更多精彩内容