Android-复盘帮你彻底了解消息机制

29次阅读

共计 17897 个字符,预计需要花费 45 分钟才能阅读完成。

1. 什么是消息机制

说到消息机制,作为一名 Android 开发者一定先想到的是 Handler。Handler 就是 Android 消息机制的上层接口,我们可用通过 Handler 轻松的在不同的线程中切换任务,但 Handler 的实现还有两个很重要的概念 MessageQueueLooper

MessageQueue 的翻译是消息队列,它的内部采用了单链表的结构存储 Handler 对象发送的消息。

Looper 的作用是不断地查询 MessageQueue 中是否有消息,如果 Looper 发现 MessageQueue 中存入了新的消息,它就会去处理这条消息,如果没有新消息,Looper 就会以无限循环的方式去查询 MessageQueue 中是否有新消息。

2. 为什么要有 Handler

2.1)官方文档中 Handler 的主要作用

(1)安排将来某个时间点执行的 MessageRunnables
(2)在不同于当前的线程上执行的操作;

2.2)Handler 被用来做的最多的一件事就是更新主线程的 UI。

在 Android 开发中,默认子线程是不可以更新 UI 的,这一点可以从 View 的最高层级 ViewRootImpl 类中找到答案

void checkThread() {if (mThread != Thread.currentThread()) {throw new CalledFromWrongThreadException("Only the original thread that created a view hierarchy can touch its views.");
    }
}

ViewRootImpl 类中的 checkThread 方法会在更新 UI 前被执行,如果当前线程不是主线程,就会抛出 Only the original thread that created a view hierarchy can touch its views. 的异常

2.3)那么 Android 为什么要设计为只能在主线程中更新 UI 呢?

  • Android 在子线程中更新 UI 是不安全的,如果多个子线程同时修改一个控件的数据,后果是不可控的
  • 如果给 UI 更新机制加锁,会降低 UI 的访问效率,并且可能阻塞某些线程的执行

3. Handler 的用法

3.1)在主线程中创建 Handler

通常,我们在主线程中创建 Handler 的写法如下:

private Handler handler = new Handler(){
    @Override
    public void handleMessage(Message msg) {super.handleMessage(msg);
    }
};

但这样写,系统会这样提示:

This Handler class should be static or leaks might occur (anonymous android.os.Handler)
这个 Handler 类应该是静态的,否则可能会发生泄漏 

出现这个警告但原因是,Handler 在 Activity 中作为一个匿名内部类来定义,它的内部持有来 Activity 的实例。当 Activity 被用户关闭时,因为 Handler 持有了 Activity 的引用,就造成了 Activity 无法被回收,从而导致了内存泄漏。

因此,在这里推荐一种更加安全的写法:

private static class MyHandler extends Handler{
    private WeakReference<Activity> weakReference;
    public MyHandler(Activity activity){weakReference = new WeakReference<>(activity);
    }

    @Override
    public void handleMessage(Message msg) {super.handleMessage(msg);
        switch (msg.what){
                case 0:                     
                  Toast.makeText(weakReference.get(),Thread.currentThread().getName(),Toast.LENGTH_SHORT).show();
                  break;
            }
    }
}

private MyHandler handler = new MyHandler(this);

通过静态内部类的方式实现一个 Handler,此时内部类并不持有外部类对象的应用,需要在内部类的构造方法内增加一个外部类(Activity)的弱应用。这样,即使 Activity 被关闭,Activity 也能顺利被回收。

onCreate() 中的代码如下:

btn_0 = findViewById(R.id.btn_0);
btn_0.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {new Thread(){
            @Override
            public void run() {super.run();
                Message message = Message.obtain();
                message.what = 0;
                handler.sendMessage(message);
            }
        }.start();}
});

这时候点击按钮的运行效果如下:

3.2)在子线程中创建 Handler

在官方文档中 Handler 的主要作用是在不同于当前线程的线程中执行操作,那么如何用 Handler 解决两个子线程之间的通信呢?

请看代码:

btn_1 = findViewById(R.id.btn_1);
btn_1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {new Thread(){
            @Override
            public void run() {super.run();
                Looper.prepare();
                handler = new MyHandler(MainActivity.this);
                try {sleep(1000);
                } catch (InterruptedException e) {e.printStackTrace();
                }
                Looper.loop();}
        }.start();
        new Thread(){
            @Override
            public void run() {super.run();
                Message message = Message.obtain();
                message.what = 0;
                handler.sendMessage(message);
            }
        }.start();}
});

此时点击按钮:

可见当前的处理线程已经变成了子线程。

4. Handler 工作原理

如果细心的观察代码,可以看到在子线程中创建 Handler 的时候调用了 Looper.prepare()Looper.loop() 两个方法。这两句代码有什么用呢?

我们暂时可以把 Looper 理解为消息的管理者,它负责从 MessageQueue 中提取出消息,传递给 Handler 进行处理,每一个 Handler 都必须要有一个 Looper,在 Handler 创建的时候,它会自动使用当前线程的 Looper,而 Looper.prepare() 的作用就是为当前线程准备一个 Looper,Looper.loop() 的作用是开始查找当前 MessageQueue 中是否有了新的消息。

这就是 Handler 工作的第一步:

4.1)采用当前线程的 Looper 创建 Handler

因为这里主要讲 Handler 的工作流程,创建 Looper 的具体过程放到文章的下面讲解。我们只要知道
Looper.prepare() 为当前的线程创建了一个 Looper 对象即可。

但是,在主线程中创建 Handler 的时候,我们并没有看到 Looper.prepare() 的执行,这是因为在 UI 线程,即 ActivityThread 的创建过程中,Looper 已经被创建好了。

我们可以在 ActivityThread 的 main() 方法中看到这样一句代码:

Looper.prepareMainLooper();

这个方法内部也调用了 Looper.prepare() 为 UI 线程创建了一个 Looper。

4.2)通过 Handler 的 sendMessageAtTime() 方法发送 Message

为什么是 sendMessageAtTime?不是还有 sendMessage()sendEmptyMessage()sendEmptyMessageDelayed()sendEmptyMessageAtTime()sendMessageDelayed() 这么多方法吗?

通过阅读这些方法的源码可以发现,这些方法最终调用的都是 sendMessageAtTime()

其次还有 post()postAtTime()postDelayed() 方法最终调用的也都是 sendMessageAtTime() 方法,只是多了一步调用 getPostMessage(Runnable r, Object token) 将 Runnable 封装为一个 Message 对象的 callback 里。

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

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

那么 sendMessageAtTime() 里的具体操作是什么呢?我们去源码里一探究竟

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {
    // 先获取当前 Handler 中的 MessageQueue,mQueue 在 Looper 的构造方法中进行初始化。MessageQueue queue = mQueue;
    if (queue == null) {
        RuntimeException e = new RuntimeException(this + "sendMessageAtTime() called with no mQueue");
        Log.w("Looper", e.getMessage(), e);
        return false;
    }
    // queue 不为空,则执行 Handler.java 里的另一个 enqueueMessage() 方法
    return enqueueMessage(queue, msg, uptimeMillis);
}

    
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
    // 指定 msg 的 Target 对象为当前的 Handler
    msg.target = this;
    if (mAsynchronous) {msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);
}

Handler 中的 enqueueMessage(),最终会调用 MessageQueue.java 中的 enqueueMessage() 方法。

之后,Message 对象最终传递到 MessageQueue 即消息队列里中,在消息队列里的具体处理逻辑在文章的 MessageQueue 工作原理 部分会具体解释。

4.3)Looper 处理消息后调用 Handler 的 dispatchMessage() 方法

在第二步将消息插入消息队列后,Looper 就开始遍历消息队列,找到新的消息,再通知 Handler 去执行这条消息,调用的就是 Handler 的 dispatchMessage() 方法。

public void dispatchMessage(Message msg) {
   // msg 的 callback 对象就是一个 Runnable
   if (msg.callback != null) {handleCallback(msg);
    } else {// 检查 mCallback 是否为空,不为空就执行它内部定义的 handleMessage() 方法
        if (mCallback != null) {if (mCallback.handleMessage(msg)) {return;}
        }
        // 如果 mCallback 为空,就执行在实例化 Handler 过程中我们自己定义的 handleMessage() 方法中的内容
        handleMessage(msg);
    }
}

dispatchMessage() 方法首先会检查 Message 的 Callback 对象是否为空,callback 就是通过 post() 方法传递的 Runnable 对象,如果 callback 不为空,就去执行 handleCallback() 方法。

handleCallback() 方法的实现也很简单,它在内部执行了 Runnable 的 run() 方法

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

如果 callback 对象为空,就检查 mCallback 是否为空,不为空就执行它的定义的 handleMessage() 方法,若没有 mCallback,最终将直接执行我们在继承 Handler 时自己定义的 handleMessage() 方法中的代码。

Callback 是 Handler 中定义的的一个接口,它的代码如下:

/**
 * Callback interface you can use when instantiating a Handler to avoid
 * having to implement your own subclass of Handler.
 */
public interface Callback {
    /**
     * @param msg A {@link android.os.Message Message} object
     * @return True if no further handling is desired
     */
    public boolean handleMessage(Message msg);
}

如果使用 Callback 接口的话,我们可以直接实例化一个 Handler 而不用去实现一个 Handler 的子类,

private Handler mHandler = new Handler(new Handler.Callback() {
    @Override
    public boolean handleMessage(Message msg) {return false;}
});

5. MessageQueue 工作原理

我们从上一部分的 MessageQueue.java 中的 enqueueMessage() 方法开始入手。

5.1)enqueueMessage()

代码量有点多,要耐心看哦!

boolean enqueueMessage(Message msg, long when) {
    // 检查当前 msg 的 target 是否为空
    if (msg.target == null) {throw new IllegalArgumentException("Message must have a target.");
    }
    // msg 如果正在被执行,就抛出异常
    if (msg.isInUse()) {throw new IllegalStateException(msg + "This message is already in use.");
    }

    synchronized (this) {// 在 quit() 方法中,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;
        }

        // 标记当前的 msg 正在执行
        msg.markInUse();
        // 设置 msg 的 when 为传进来的 when 参数,when 是 Message 想要被执行的时间
        msg.when = when;
        // 得到当前消息队列的头部消息
        Message p = mMessages;
        boolean needWake;
        // 当前消息队列为空,新消息的触发时间为 0,或者新消息的触发时间早于消息中第一条消息的触发时间
        // 则将新消息插入到队列的头部,作为当前消息队列的第一条消息
        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 {
            // 将新消息插入到当前消息队列当中,(不是头部)// 通常我们不必唤醒事件队列,// 除非队列头部有消息障碍,并且消息是队列中最早的异步消息。needWake = mBlocked && p.target == null && msg.isAsynchronous();
            Message prev;
            // 开始循环便利消息队列,比较新消息和队列中消息的 when(触发事件)的值,将新消息插入到适当位置
            for (;;) {
                // 循环第一次遍历时,将当前队列中的头部消息赋值给 prev
                prev = p;
                // p 指向队列中的第二个消息
                p = p.next;
                // 如果下一个消息为空,或者新消息的触发时间早于下一个消息,找到了要插入的位置,退出循环
                if (p == null || when < p.when) {break;}
                // needWake 为 true,并且 下一条消息是异步的,则不需要唤醒。if (needWake && p.isAsynchronous()) {needWake = false;}
            }
            // 将新消息插入到 p 之前,头消息之后。msg.next = p; // invariant: p == prev.next
            prev.next = msg;
        }

        // 如果需要唤醒,调用 nativeWake 方法去唤醒
        if (needWake) {nativeWake(mPtr);
        }
    }
    return true;
}

执行完 enqueueMassage 方法,我们新发送的 Message 就成功的插入了消息队列当中。
但是除了插入新消息,我们还需要从消息队列中读取消息,这又要怎么做呢?

5.2)next()

Message next() {
    // 如果消息循环已退出,并且被丢弃,则返回空。// 这个将在应用重启一个 looper 时发生
    final long ptr = mPtr;
    if (ptr == 0) {return null;}

    // 记录空闲时处理的 IdlerHandler 数量,只在第一次迭代时为 -1
    // IdleHandler 只在队列为空 或者 是头部消息时执行
    int pendingIdleHandlerCount = -1;
    //  native 层使用的变量,设置的阻塞超时时长,0 为不阻塞,-1 为阻塞
    int nextPollTimeoutMillis = 0;
    for (;;) {if (nextPollTimeoutMillis != 0) {Binder.flushPendingCommands();
        }

        nativePollOnce(ptr, nextPollTimeoutMillis);
  
        // 尝试检索下一条消息。如果找到则返回。synchronized (this) {
            // 获取系统从开机到现在到时间
            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            // 将队列中到头部消息赋值给 msg
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                // msg 不为空,但是这个 msg 没有 handler,则这个 msg 为栅栏
                // 开始遍历,指到获取第一个异步消息
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                // 如果当前时间不到 msg 的触发时间,则计算时间差,设置阻塞超时时长
                if (now < msg.when) {
                    // Next message is not ready.  Set a timeout to wake up when it is ready.
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {
                    // 当前时间到了 msg 的触发时间,则获取消息并返回
                    mBlocked = false;
                    // 如果当前的 msg 不是头部消息,则上一条消息的 next 指向 msg 的 next
                    if (prevMsg != null) {prevMsg.next = msg.next;} else {
                        // 当前 msg 为头部消息,则将下一个 msg 设置为头部消息
                        mMessages = msg.next;
                    }
                    // msg 的下一个 Message 对象置空,表示从消息队列中取出来了这条 msg
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message:" + msg);
                    // 标记 msg 正在使用
                    msg.markInUse();
                    return msg;
                }
            } else {
                // 如果没有消息,则设置阻塞时长为 -1,直到被唤醒
                nextPollTimeoutMillis = -1;
            }

            // 所有的消息都被处理后,判断是否退出,并返回 null。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 的数量
            if (pendingIdleHandlerCount < 0
                    && (mMessages == null || now < mMessages.when)) {pendingIdleHandlerCount = mIdleHandlers.size();
            }
  
            // pendingIdleHandlerCount 的数量为 0 时,线程会继续堵塞
            if (pendingIdleHandlerCount <= 0) {
                // No idle handlers to run.  Loop and wait some more.
                mBlocked = true;
                continue;
            }

            // 判断当前空闲时处理任务的 handler 是否是为空,如果为空,就实例化出新的对象
            if (mPendingIdleHandlers == null) {mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)];
            }
            mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers);
        }

        // 运行 IdleHandler,只有第一次循环时才会运行
        for (int i = 0; i < pendingIdleHandlerCount; i++) {final IdleHandler idler = mPendingIdleHandlers[i];
            // 释放 IdleHandler 的引用
            mPendingIdleHandlers[i] = null;

            boolean keep = false;
            try {
                // 执行 IdleHandler 的方法
                keep = idler.queueIdle();} catch (Throwable t) {Log.wtf(TAG, "IdleHandler threw exception", t);
            }

            if (!keep) {synchronized (this) {mIdleHandlers.remove(idler);
                }
            }
        }

        // 重置 IdleHandler 的数量为 0,确保不会重复运行它们
        pendingIdleHandlerCount = 0;

        // 在执行 IdleHandler 时,一个新的消息可能插入或消息队列中的消息到了触发时间
        // 所以将 nextPollTimeoutMillis 设为 0,表示不需要阻塞,重新检查消息队列。nextPollTimeoutMillis = 0;
    }
}

至此,MessageQueue 的两个最重要的方法已经分析完了,下面来看 Looper 如何循环地从消息队列中取出消息。

6. Looper 工作原理

在讲 Looper 之前,需要先理解 ThreadLocal 的工作原理

6.1)ThreadLocal 的工作原理

ThreadLocal 是一个线程内存储数据的类,当不同的线程去访问同一个 ThreadLocal 对象时,获得的值都是不一样的,下面用一段代码来证明

private ThreadLocal<String> mThreadLocal = new ThreadLocal<>();

btn_1 = findViewById(R.id.btn_1);
btn_1.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {new Thread(){
            @Override
            public void run() {super.run();
                mThreadLocal.set("Thread_A");
                Log.d("ThreadLocalValue",mThreadLocal.get());
            }
        }.start();
        new Thread(){
            @Override
            public void run() {super.run();
                mThreadLocal.set("Thread_B");
                Log.d("ThreadLocalValue",mThreadLocal.get());
            }
        }.start();}
);

我在两个线程中分别存入在 mThreadLocal 中存入了不同的值,然后在控制台输出它们的内容

可见不同线程访问同一个 ThreadLocal 对象得到的值也是不一样的。

ThreadLocal 实现这种特性的原因也很简单,下面来看它内部的 set 方法:

public void set(T value) {
    // 获取当前线程 t
    Thread t = Thread.currentThread();
    // 根据当前线程 t,获取当前线程的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null)
        // map 不为空,调用 ThreadLocalMap 的 set() 方法。map.set(this, value);
    else
        // map 为空,则为当前线程创建一个新的 ThreadLocalMap 对象
        createMap(t, value);
}

在 set 方法中,先获取当前线程,然后获取当前线程的 ThreadLocalMap 对象。getMap() 的 和 createMap() 的实现如下:

ThreadLocalMap getMap(Thread t) {return t.threadLocals;}

void createMap(Thread t, T firstValue) {t.threadLocals = new ThreadLocalMap(this, firstValue);
}

那么 ThreadLocalMap 又是什么呢,这里是它的一部分源码:

static class ThreadLocalMap {
    static class Entry extends WeakReference<ThreadLocal<?>> {
        /** The value associated with this ThreadLocal. */
        Object value;

        Entry(ThreadLocal<?> k, Object v) {super(k);
            value = v;
        }
    }

    // 初始的 table 容量
    private static final int INITIAL_CAPACITY = 16;
  
    // Entry 数组用于存储数据
    private Entry[] table;

    // table 的大小
    private int size = 0;

    // 负载因子,用于扩容
    private int threshold; // Default to 0

    // 设置负载因子为当然容量大小的 2 / 3 
    private void setThreshold(int len) {threshold = len * 2 / 3;}
  
    // 初始化 Entry 数组
    ThreadLocalMap(ThreadLocal<?> firstKey, Object firstValue) {table = new Entry[INITIAL_CAPACITY];
        int i = firstKey.threadLocalHashCode & (INITIAL_CAPACITY - 1);
        table[i] = new Entry(firstKey, firstValue);
        size = 1;
        setThreshold(INITIAL_CAPACITY);
    }
}

可以将 ThreadLocalMap 当作一个哈希表,它的内部用 Entry 存储相应的数据。

在 Thread 的属性中有 ThreadLocal.ThreadLocalMap threadLocals = null;,所以每一个线程内部,都持有一个 ThreadLocalMap 对象,系统才可以通过 getMap() 方法获取当前线程的 ThreadLocalMap 对象。

在 ThreadLocal 中调用 set 方法,实际上会调用 ThreadLocalMap 中的 set 方法,源码如下:

// ThreadLocalMap 的 set 方法
private void set(ThreadLocal<?> key, Object value) {// We don't use a fast path as with get() because it is at
    // least as common to use set() to create new entries as
    // it is to replace existing ones, in which case, a fast
    // path would fail more often than not.

    // 首先获取当前 ThreadLocal 对象的 table 属性,table 一个 Entry 的数组
    // Entry 相当于一个 HashMap,存储了当前 ThreadLocal 对象和 Object 类型的 value 对象
    Entry[] tab = table;
    int len = tab.length;
    // 计算出存储的位置
    int i = key.threadLocalHashCode & (len-1);

    // 遍历 tab
    for (Entry e = tab[i];
        e != null;
        e = tab[i = nextIndex(i, len)]) {ThreadLocal<?> k = e.get();
        // 如果 tab 中已经存在了相同的 key 值,就覆盖它原有的 value
        if (k == key) {
            e.value = value;
            return;
        }
        // 如果 当前 entrt 的 key 为 null,调用 replaceStaleEntry 方法清楚所有 key 为 null 的数据
        if (k == null) {replaceStaleEntry(key, value, i);
            return;
        }
    }
        // 都不满足,就新建一个 Entry 对象
    tab[i] = new Entry(key, value);
    int sz = ++size;
    // ThreadLocalMap 的容量到达阀值后扩容
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();}

ThreadLocal 中的 get() 方法和 set() 方法一样,都是对 Thread 中对 ThreadLocalMap 进行操作

public T get() {
    // 获取当前线程
    Thread t = Thread.currentThread();
    // 获取当前线程的 ThreadLocalMap 对象
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        // 获取 ThreadLocalMap 中对应当前线程的 Entry 对象
        ThreadLocalMap.Entry e = map.getEntry(this);
        if (e != null) {@SuppressWarnings("unchecked")
            // 将 Entry 对象中的 value 取出来
            T result = (T)e.value;
            return result;
        }
    }
    return setInitialValue();}

private Entry getEntry(ThreadLocal<?> key) {int i = key.threadLocalHashCode & (table.length - 1);
    Entry e = table[i];
    if (e != null && e.get() == key)
        return e;
    else
        return getEntryAfterMiss(key, i, e);
}

6.2)Looper 中的 prepare() 方法

那么 ThreadLocal 和 Looper 有什么关系呢?我们知道每一个线程都有自己的 Looper,Looper 的作用域就是当前的线程,Android 系统中便通过 ThreadLocal 对象来存储不同线程中的 Looper。

Looper 中 prepare() 方法为当前线程创建一个 Looper 对象,我们看一下它的实现:

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");
    }
    // 将 Looper 对象保存到当前线程的 ThreadLocalMap 当中
    sThreadLocal.set(new Looper(quitAllowed));
}

这里再看一下 Looper 的构造方法

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

可以看到在一个 Looper 中创建了一个 MessageQueue,这里我们就可以搞清楚 Handler、Looper 和 MessageQueue 的对应关系了:

每个线程都有一个 Looper 对象,在 Looper 对象的初始化过程中,会为当前线程创建一个 MessageQueue,而一个线程中可以有多个 Handler。

6.3)Looper 中的 loop() 方法:

prepare() 调用后,就是调用 loop() 方法:

/**
  * Run the message queue in this thread. Be sure to call
  * {@link #quit()} to end the loop.
  */
public static void loop() {
    // 通过 Thread Local 获取当前线程的 Looper
    final Looper me = myLooper();
    if (me == null) {throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread.");
    }
    // 获取当前 Looper 对象的 MessageQueue
    final MessageQueue queue = me.mQueue;

    // 清空远程调用端进程的身份,确保此线程的身份是本地进程的身份,并跟踪该身份令牌
    // 这里主要用于保证消息处理是发生在当前 Looper 所在的线程
    Binder.clearCallingIdentity();
    final long ident = Binder.clearCallingIdentity();

    for (;;) {
        // 取出来下一条消息
        Message msg = queue.next(); // might block
        if (msg == null) {
            // No message indicates that the message queue is quitting.
            return;
        }
        
        // 用 logging 打印日志,默认为 null,可通过 setMessageLogging() 方法来指定
        final Printer logging = me.mLogging;
        if (logging != null) {
            logging.println(">>>>> Dispatching to" + msg.target + " " +
                    msg.callback + ":" + msg.what);
        }
        
        // 开始跟踪,并写入跟踪消息,用于 debug 功能
        final long traceTag = me.mTraceTag;
        if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
        }        
        ...
        ...
        try {
            // // 通过 Handler 分发消息
            msg.target.dispatchMessage(msg);
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;} finally {if (traceTag != 0) {
                // 停止跟踪
                Trace.traceEnd(traceTag);
            }
        }
        
        if (logSlowDispatch) {showSlowLog(slowDispatchThresholdMs, dispatchStart, dispatchEnd, "dispatch", msg);
        }

        if (logging != null) {logging.println("<<<<< Finished to" + msg.target + " " + msg.callback);
        }

        //  确保在分发消息的过程中线程的身份没有改变
        final long newIdent = Binder.clearCallingIdentity();
        if (ident != newIdent) {
            Log.wtf(TAG, "Thread identity changed from 0x"
                    + Long.toHexString(ident) + "to 0x"
                    + Long.toHexString(newIdent) + "while dispatching to"
                    + msg.target.getClass().getName() + " "
                    + msg.callback + "what=" + msg.what);
        }
        // 回收消息,并把消息放入消息池
        msg.recycleUnchecked();}
}

可以看到 loop() 方法就是不停的遍历消息队列中的消息,当发现有新的消息时,便调用 Handler 的 dispatchMessage() 方法。

6.4)getMainLooper()

public static void prepareMainLooper() {prepare(false);
    synchronized (Looper.class) {if (sMainLooper != null) {throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();}
}
  /**
    * Returns the application's main looper, which lives in the main thread of the application.
    */
public static Looper getMainLooper() {synchronized (Looper.class) {return sMainLooper;}
}

getMainLooper() 方法用于返回当前 UI 线程的 Looper,UI 线程的 Looper 在 ActivityThread 的建立时通过调用
prepareMainLooper() 方法创建。

6.5)quit() 和 quitSafely()

在子线程中,如果手动为其创建了 Looper,那么在所有消息处理完成之后应该调用 quit() 方法终止消息循环,不然 Looper 就会一直处于等待状态。

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

public void quit() {mQueue.quit(false);
}

可以看到这两个方法都调用了 MessageQueue 中都 quit(boolean safe) 方法,quitSafely 的参数为 true,quit 的参数为 false。

void quit(boolean safe) {
    // 主线程不退出消息循环
    if (!mQuitAllowed) {throw new IllegalStateException("Main thread not allowed to quit.");
    }
    synchronized (this) {
        // 如果已经退出了,直接 return
        if (mQuitting) {return;}

        // 标记为已经退出
        mQuitting = true;
        // 如果 safe 的值为 true,执行完当前的消息后退出消息循环
        if (safe) {removeAllFutureMessagesLocked();
        } else {
            // 直接退出消息循环
            removeAllMessagesLocked();}
        // We can assume mPtr != 0 because mQuitting was previously false.
        nativeWake(mPtr);
    }
}

quitSafely() 会等待当前消息执行完毕后退出消息循环,而 quit() 方法会直接退出消息循环。

private void removeAllMessagesLocked() {
    // 获取当前 MessageQueue 的头部消息
    Message p = mMessages;
    while (p != null) {
        // 循环遍历所有的 Message
        Message n = p.next;
        // 回收消息,并把消息放入消息池
        p.recycleUnchecked();
        p = n;
    }
    // 将头部消息置为空
    mMessages = null;
}

private void removeAllFutureMessagesLocked() {
    // 获取系统从开机到现在到时间
    final long now = SystemClock.uptimeMillis();
    // 将当前的头部消息赋值给 p
    Message p = mMessages;
    if (p != null) {if (p.when > now) {// 如果当前头部消息将要执行的时间大于系统开机到现在的时间,则执行 removeAllMessagesLocked() 方法
            // 清空 MessageQueue 队列
            removeAllMessagesLocked();} else {
            Message n;
            // 遍历当前的 MessageQueue,直到某个消息的执行时间小于 now 值(即这个消息正在执行)// 将这个消息的 next 赋值为 null
            for (;;) {
                n = p.next;
                if (n == null) {return;}
                if (n.when > now) {break;}
                p = n;
            }
            p.next = null;
            // 回收不会被执行的 Message
            do {
                p = n;
                n = p.next;
                p.recycleUnchecked();} while (n != null);
        }
    }
}

终于讲完了,希望大家能通过我的文章,彻底理解 Handler 的机制,但我的能力有限,如果存在错误的地方,还请指出。

零碎的东西很多,为了方便大家记忆,我把上面的内容做成了思维导图,需要的朋友可以保存下来,偶尔看一下,帮助自己记忆。

欢迎关注本文作者:

扫码关注并回复「干货」,获取我整理的千 G Android、iOS、JavaWeb、大数据、人工智能等学习资源。

正文完
 0