Handler是Android消息机制的核心,但是要分析Handler的原理离不开MessageQueue和Looper。所以三者要一起分析。待解决问题:为什么在子线程中执行new Handler()会抛出异常?

先看图

这是描述Android消息机制的一张图,每个线程有一个Looper和一个MessageQueue,以及若干个Handler。其中MessageQueue存放了一系列Message的队列,而每个Handler通过sendMessage方法发送一个Message到MessageQueue中,并且这个Message持有发送它的Handler的引用。Looper通过Looper.loop()方法不断地从MessageQueue中取出Message,并将取出来的Message交给它持有的Handler对象,这个Handler对象通过handleMessage方法处理这个Message。

关键在于,Handler的handleMessage一定会在创建它的线程的上下文中被调用,但是它的sendMessage可以在其他线程中被调用,因此通过Handler可以实现跨线程通信。

首先举一个Android消息机制的例子来引入主题。

Android中常用handler更新UI,在主线程实例化handler,实现它的handleMessage()方法,为UI控件添加一个点击事件,在子线程中实例化一个Message对象,然后用handler发送它,handler在主线程中接收这个Message对象并更新UI。

public class HandlerActivity extends BaseActivity implements View.OnClickListener {    private Button mBtnChangeUI;    private Handler mHandler;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.module_activity_handler);        mHandler = new Handler(new Handler.Callback() {            @Override            public boolean handleMessage(Message msg) {                switch (msg.what) {                    case 0:                        mBtnChangeUI.setText(new StringBuilder(mBtnChangeUI.getText()).reverse());                        return true;                }                return false;            }        });        mBtnChangeUI = findViewById(R.id.btn_changeUI);        mBtnChangeUI.setOnClickListener(this);    }    @Override    public void onClick(View v) {        switch (v.getId()) {            case R.id.btn_changeUI:                new Thread(new Runnable() {                    @Override                    public void run() {                        mHandler.sendEmptyMessage(0);                    }                }).start();                break;        }    }}

让我们看看这里发生了什么。首先我们在UI线程里创建了mHandler,并实现了它的handleMessage()方法,然后实现点击事件,当Button被点击时,创建一个字线程,在子线程里用mHandler发送一个Message,随后倒置Button上的字符串。

结合上面的图,在主线程中创建mHandler,于是mHandler是主线程的,在子线程中发送一个Message,将其放入MessageQueue,这个MessageQueue应该和mHandler在同一个线程,所以也是主线程的,与mHandler同样位于主线程的Looper不断的从MessageQueue中取出Message,并将其交给Message持有的Handler也就是mHandler处理,mHandler在handleMessage中处理Message,将Button上的字符串倒置。

在代码中看不到Looper和MessageQueue,因此要查看源码(注:为了简洁,省略了部分源码)。

首先,看Handler的创建过程:

Handler的构造函数

Handler有多个构造函数,这里使用的是

public Handler(Callback callback) {    this(callback, false);}public Handler(Callback callback, boolean async) {    // ...    mLooper = Looper.myLooper();// ...    mQueue = mLooper.mQueue;    mCallback = callback;    mAsynchronous = async;}

可以看到这里出现了Looper,mLooper是Handler的一个实例变量,这里的mQueue也是实例变量,而且是MessageQueue类型的,因此Handler持有Looper和MessageQueue的引用。同时Looper也持有MessageQueue的引用。如图所示:

Handler一共有七个构造函数:

public Handler();public Handler(boolean async);public Handler(Callback callback);public Handler(Callback callback, boolean async);public Handler(Looper looper);public Handler(Looper looper, Callback callback);public Handler(Looper looper, Callback callback, boolean async);

但是它们之间会互相调用,因此最终起作用的只有两个构造函数:

public Handler(Callback callback, boolean async);public Handler(Looper looper, Callback, callback, boolean async);前者通过Looper.myLooper()获取Looper,后者手动指定Looper。MessageQueue和Looper是绑定的,制定了Looper也就指定了MessageQueue。

观察Handler的两类方法:发送消息处理消息

Handler发送消息的方法

发送消息又有七个方法:

public final boolean sendEmptyMessage(int what);public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis);public final boolean sendEmptyMessageDelayed(int what, long delayMillis)public final boolean sendMessage(Message msg);public final boolean sendMessageAtFrontOfQueue(Message msg);public boolean sendMessageAtTime(Message msg, long uptimeMillis);public final boolean sendMessageDelayed(Message msg, long delayMillis);

同样的,它们之间互相调用,最终起作用的是两个方法:

public boolean sendMessageAtTime(Message msg, long uptimeMillis) {    MessageQueue queue = mQueue;    if (queue == null) {        return false;    }    return enqueueMessage(queue, msg, uptimeMillis);}public final boolean sendMessageAtFrontOfQueue(Message msg) {    MessageQueue queue = mQueue;    if (queue == null) {        return false;    }    return enqueueMessage(queue, msg, 0);}

我们发现发送消息最终就是调用了enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis)这个方法,顾名思义,这是一个Message入队的方法,它的代码如下:

private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {    msg.target = this;    if (mAsynchronous) {        msg.setAsynchronous(true);    }    return queue.enqueueMessage(msg, uptimeMillis);}

这里,Message获得了Handler的引用,message.target = handler。而queue是一个MessageQueue变量。这正是——Message被其持有的Handler发送,插入到MessageQueue。

MessageQueue的enqueueMessage方法后面再看。记录1⃣️

Handler处理消息的方法

/** * Handle system messages here. */public void dispatchMessage(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();}public interface Callback {    public boolean handleMessage(Message msg);}public void handleMessage(Message msg) {}

除了常用的覆写handleMessage接口的方法实现处理消息的方法之外,还有两种方法,使用Message自带的callback,或者传入一个Callback实例。代码很清晰。传入Callback实例的方法是采用Handler构造方法中的public Handler(Callback callback, boolean async);public Handler(Looper looper, Callback callback, boolean async);

Message的callback变量后面再看。记录2⃣️

综上,Handler的常用方法讲完了,现在解决记录1⃣️。分析MessageQueue的源码。

1⃣️MessageQueue的enqueueMessage方法。

MessageQueue

Android的每个线程可以持有一个Looper对象和一个MessageQueue对象,Looper不停地从MessageQueue里取出Message,并将Message交给Message持有的Handler对象来处理,当MessageQueue为空时Looper阻塞。而Message是由其持有的Handler对象插入MessageQueue的。Handler持有它被创建的线程的上下文,并且可以被多线程共享。

MessageQueue是由一个单链表的数据结构实现的,主要包含两个操作:插入和读取,对应的方法为enqueueMessage和next,enqueueMessage的作用是插入一条Message,next的作用是取出一条Message并将其从MessageQueue中移除,先发出的Message靠近队列的首部,现在看这两个方法的源码:

// 插入msg,指定其在when(ms)的延时后触发boolean enqueueMessage(Message msg, long when) {    //...    // 单链表插入,通过同步来防止多线程冲突    synchronized (this) {        //...        msg.when = when;        Message p = mMessages;        boolean needWake;//需要唤醒的标识         if (p == null || when == 0 || when < p.when) {        // 消息队列为空 || msg是立即触发的消息 || msg比队列首部的消息先触发,则将其插入到队列首部            msg.next = p;            mMessages = msg;            needWake = mBlocked;//        } else {            // 消息队列不为空 && msg比队列首部的消息晚触发 && p不持有Handler && msg是异步消息            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; // 根据触发的时间将msg插入到合适的位置            prev.next = msg;        }        // We can assume mPtr != 0 because mQuitting is false.        if (needWake) {            nativeWake(mPtr);        }    }    return true;}

needWake这个变量的用法还不明朗,在省略的源码里也有一些不清楚用法的变量,Message的同步/异步是什么,inUse表示是什么,等等。但是这个方法的主要功能是明显的,向队列插入Message,MessageQueue是单链表实现的,其中的Message按照触发的时间when排序,先触发的排在前面。有入队,就有出队,出队方法是next

Message next() {//...    for (;;) {        //...        synchronized (this) {            // Try to retrieve the next message.  Return if found.            final long now = SystemClock.uptimeMillis();            Message prevMsg = null;            Message msg = mMessages;            // MessageQueue不为空 && 队首的Message不持有Handler            if (msg != null && msg.target == null) {                // 找到MessageQueue中第一个不为空 && 同步的Message                do {                    prevMsg = msg;                    msg = msg.next;                } while (msg != null && !msg.isAsynchronous());            }            //            if (msg != null) {                // 还没到Message触发的时间                if (now < msg.when) {                    //...                } else {//...                    // 从队列中移除Message                    if (prevMsg != null) {                        prevMsg.next = msg.next;                    } else {                        mMessages = msg.next;                    }                    msg.next = null;    //...                    return msg;                }            } else {                // No more messages.                //...            }        }    }}

next是一个循环函数,在MessageQueue里找到符合条件的Message(条件很复杂),将其从MessageQueue中移除并返回。

接下来分析Looper。

Looper

Looper不停地查看MessageQueue中是否有Message,这是在它的loop方法中完成的,因此loop是它的核心方法,从这个方法开始看

public static void loop() {    final Looper me = myLooper();    //...    final MessageQueue queue = me.mQueue;    //...    for (;;) {        // 从MessageQueue中取出Message        Message msg = queue.next(); // might block        if (msg == null) {            // No message indicates that the message queue is quitting.            return;        }//...        try {            msg.target.dispatchMessage(msg);// 使用Message持有的Handler处理事件。            //...        } finally {            //...        }        //...    }}

可以看到,loop是一个循环函数,从MessageQueue中不断地取出Message并处理,当MessageQueue为空时退出循环。照例,MessageQueue是与Looper绑定的,通过myLooper方法获取Looper。查看myLooper方法:

public static @Nullable Looper myLooper() {    return sThreadLocal.get();}static final ThreadLocal sThreadLocal = new ThreadLocal();

我们发现这里用到了ThreadLocal变量,也就是说每个线程都有一个线程独立的Looper,而他是在Looper.prepare()方法里获取到的。

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

可以看到每个线程只能有一个Looper。

到这里,我们可以解决开头的问题了——为什么在子线程中执行new Handler()会抛出异常?

在子线程中执行new Handler(),Handler()中需要调用myLooper()获取Looper,而此时Looper是空值,因此抛出异常。

public Handler() {    this(null, false);}public Handler(Callback callback, boolean async) {    mLooper = Looper.myLooper();    if (mLooper == null) {        throw new RuntimeException(            "Can't create handler inside thread that has not called Looper.prepare()");    }    //...}

那么,为什么在主线程中执行new Handler()不会抛出异常呢?回到Looper的源码,可以发现prepare方法被prepareMainLooper方法调用了,而prepareMainLooper方法又被ActivityThread.main方法调用了。

public static void prepareMainLooper() {    prepare(false);    synchronized (Looper.class) {        if (sMainLooper != null) {            throw new IllegalStateException("The main Looper has already been prepared.");        }        sMainLooper = myLooper();    }}public static void main(String[] args) {    //...    Looper.prepareMainLooper();    //...    Looper.loop();    //...}

因此,在主线程初始化的时候就已经创建了Looper,所以执行new Handler()不会抛出异常,在子线程中就需要手动调用Looper.prepare()来创建了。主线程的Looper名为sMainLooper,是一个静态私有变量,使用共有静态方法getMainLooper就可以获取到它,因此在子线程中也可以手动获取主线程的Looper。

顺便看看Looper的构造函数

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

Looper在实例化的时候会创建自己的MessageQueue,并获取当前线程的引用。因此每个Looper都有一个MessageQueue。Looper使用单例设计,每个线程只能有一个Looper。子线程中的Looper使用ThreadLocal存储,对其处理时不需要做同步处理,主线程中的Looper是一个类变量,对其处理时需要做同步处理。

Looper不停地查看MessageQueue中是否有Message,如果有新消息就处理,否则阻塞。通过Looper.prepare()创建Looper。

综上所述,Handler-MessageQueue-Looper的关系就讲完了。再回顾一下Android消息机制是如何实现的:

  1. 每个线程中都至多只能持有一个Looper,每个Looper持有一个MessageQueue实例和当前线程的引用。主线程在初始化的时候自动调用Looper.prepareMainLooper创建了一个MainLooper,对于子线程,需要手动调用Looper.prepare创建Looper。

  2. 每个线程可以持有多个Handler,Handler持有一个Looper的引用,可以为Handler指定Looper,也可以让Handler自行获取当前线程的Looper。

  3. 手动创建Message,使用Handler发送Message,Handler会调用MessageQueue的方法,将Message插入MessageQueue。

  4. Looper.loop,循环从MessageQueue取出Message,然后调用Handler.dispatchMessage()来处理Message。

 

参考资料:

《Android开发艺术探索》

更多相关文章

  1. 浅谈Java中Collections.sort对List排序的两种方法
  2. Python list sort方法的具体使用
  3. python list.sort()根据多个关键字排序的方法实现
  4. E/错误(3907): android.view.ViewRootImpl$CalledFromWrongThrea
  5. Android(安卓)CheckBox中设置padding无效问题解决方法
  6. android的Handler
  7. 安卓入门笔记之Activity
  8. Android四大基本组件介绍与生命周期
  9. Android(安卓)WiFiDirect 学习(二)——Service Discovery

随机推荐

  1. Android(安卓)官方数据库Room --- 配置
  2. Android(安卓)获取缩略图,网络视频,或者
  3. Android实验九之天气预报
  4. Android带参数链接请求服务器
  5. 源码网站推荐
  6. android 文件搜索
  7. Android传感器API:近距离感应Proximity
  8. Android(安卓)自定义View (一)
  9. ffmpeg compile with neon support for a
  10. android bitmap(图片)旋转90度