Android消息机制的Handler

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<Looper> sThreadLocal = new ThreadLocal<Looper>();

我们发现这里用到了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开发艺术探索》

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值