Android的Handler机制解析

846 阅读12分钟

1.前言

在学习Android开发的过程中,想必许多小伙伴一开始都有过在子线程中修改UI导致报错的经历。现在我们明白了Android中规定UI的更新操作必须在主线程中完成,这主要由于是Android的UI控件是线程不安全的。相反的,一些耗时I/O操作如:读取文件、访问网络等,必须在子线程中执行,否则就有可能导致ANR。我们在实际开发当中,常常需要在子线程中进行一些耗时操作,当其完成以后再对UI进行一些改变,这就涉及到了线程切换操作,Android的消息机制——Handler就该派上用场了 。

2.Handler的使用

2.1与Handler相关的类

  • Message 用于储存消息。
  • MessageQueue 消息队列,用于储存Handler发送来的Message。
  • Looper 循环器,用于取出MessageQueue中的Message并进行分发,Handler的创建必须依赖于Looper,否则会创建失败。
  • ThreadLocal 线程内部的数据存储类,可在指定线程中存储数据,只有该线程可以获取其中数据,其他线程无法获取,在Handler中用于存储一个Looper。

2.2Handler常用的形式

//sendMessage
class MyHandler extends Handler {
    @Override
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 1:
                textView.setText("网络请求成功");//收到消息 更新UI
                break;
            default:
                break;
        }
    }
}

final Handler myHandler = new MyHandler();

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);//模拟网络请求等耗时任务
            Message message = Message.obtain();//从缓存池中获取Message
            message.what = 1;
            myHandler.sendMessage(message);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
}).start();

//sendEmptyMessage
new Thread(new Runnable() {
    @Override
    public void run() {
        myHandler.sendEmptyMessage(1);//内部获取了一个Message并设置了what为1
    }
}).start();

//post

final Handler myHandler = new Handler();

new Thread(new Runnable() {
    @Override
    public void run() {
        try {
            Thread.sleep(1000);//模拟网络请求等耗时任务
            myHandler.post(new Runnable() {
            //这里看似是在子线程中跟新UI,实际上他是在创建handler的线程中调用的,
            //通过之后的源码阅读可以发现他调用的是run方法而不是start方法
                @Override
                public void run() {
                    textView.setText("网络请求成功");
                }
            });
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

    }
});

//postDelayed
myHandler.postDelayed(new Runnable() {
    @Override
    public void run() {
        textView.setText("网络请求成功");
    }
},500);//延迟500ms

主线程中创建Handler十分简单,接下来我们来看如何在子线程中创建Handler

//这段代码会报错的
class MyThread extends Thread{

    public Looper mLooper;

    @Override
    public void run() {
        Looper.prepare();//创建一个Looper
        mLooper = Looper.myLooper();//得到当前线程中创建的Looper
        Looper.loop();//循环读取消息
    }
}

MyThread mThread = new MyThread();
mThread.start();
Handler handler = new Handler(mThread.mLooper){//子线程Handler的创建必须依赖于子线程的Looper
    public void handleMessage(@NonNull Message msg) {
        Log.d("MainActivity",Thread.currentThread().getName());
    }
};
handler.sendEmptyMessage(0);

我们可以看到在子线程中创建Handler比主线程要复杂很多,因为主线程中的Looper系统已经为我们创建好了,并启动了loop去循环读取消息,所以我们不需要自己来操作了,而子线程中的Looper需要我们自己来创建。这段代码虽然看似没有问题,但实际上会报空指针错误,原因是我们在创建Handler的时候,子线程的Looper不一定创建完毕,所以得到的mLooper可能为null。而Android为了解决这个问题,提供了HandlerThread类 。

HandlerThread handlerThread = new HandlerThread("Thread1");//传入线程名
handlerThread.start();//启动线程

Handler handler = new Handler(handlerThread.getLooper()){//得到子线程的Looper
    public void handleMessage(@NonNull Message msg) {
        Log.d("MainActivity",Thread.currentThread().getName());
    }
};
handler.sendEmptyMessage(0);//主线程发送消息到子线程

可以看到,handler成功收到了消息,并且handler的所在线程为子线程

D/MainActivity: Thread1

子线程中Handler的使用场景:

class MainHandler extends Handler{
    @Override
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 1001:
                textView.setText("网络请求成功");
                break;
            default:
                break;
        }
    }
}
final Handler mainHandler = new MainHandler();//主线程的Handler

HandlerThread handlerThread = new HandlerThread("Thread1");
handlerThread.start();//开启子线程

final Handler downloadHandler = new Handler(handlerThread.getLooper()){//子线程的Handler
    public void handleMessage(@NonNull Message msg) {
        switch (msg.what){
            case 1000:
                try {
                    Thread.sleep(1000);//模拟网络请求
                    //网络请求成功 子线程发消息给主线程更新UI
                    mainHandler.sendEmptyMessage(1001);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                break;
            default:
                break;
        }
    }
};

button.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        downloadHandler.sendEmptyMessage(1000);//主线程发送网络请求消息到子线程
    }
});

3.源码解析

3.1Message

public final class Message implements Parcelable {
    
    public int what;//标识

    public int arg1;//可以用来存放int内容

    public int arg2;//可以用来存放int内容

    public Object obj;//可存放任意类型的内容

    Handler target;//Message的接收者

    Runnable callback;//回调
    
    Message next;//链表的下一个Message

    private static Message sPool;//缓存池
    
    private static final int MAX_POOL_SIZE = 50;//缓存池最大容量
    
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {//缓存池不为null
                Message m = sPool;//得到缓存池表头的message
                sPool = m.next;//缓存池表头指向下一个meassge(若为空则obtain时将会新建一个)
                m.next = null;
                m.flags = 0; 
                sPoolSize--;//缓存池大小减一
                return m;
            }
        }
        return new Message();//第一次取的时候缓存池为null,所以会新建一个Message
    }
    
    public void recycle() {
        if (isInUse()) {
            if (gCheckRecycle) {
                throw new IllegalStateException("This message cannot be recycled because it "
                        + "is still in use.");
            }
            return;
        }
        //消息不再使用了,回收
        recycleUnchecked();
    }
    
    @UnsupportedAppUsage
    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++;//缓存池大小加一
            }
        }
    }
}

以上是Message类的主要内容,可以看到其内部维持了一个缓存池,当我们调用obtain方法的时候,会将message从缓存池中取出,而message使用完毕的时候会对其进行回收,重新加入缓存池。所以一般不会直接调用message的构造方法,这样会产生很多缓存池,造成资源的浪费。

3.2ThreadLocal

为了更直观的了解ThreadLocal的作用,我们先看一个小例子。

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

mThreadLocal.set("MainThread");//主线程设置mThreadLocal的值
Log.d(TAG,"MainThread: "+mThreadLocal.get());

new Thread(new Runnable() {//子线程1
    @Override
    public void run() {
        mThreadLocal.set("Thread1");//子线程1设置mThreadLocal的值
        Log.d(TAG,"Thread1: "+mThreadLocal.get());
    }
},"Thread1").start();

new Thread(new Runnable() {//子线程2
    @Override
    public void run() {
        Log.d(TAG,"Thread2: "+mThreadLocal.get());
    }
},"Thread2").start();

在这里,我们将主线程的mThreadLocal值设置成MainThread,子线程1中的值设置为Thread1,子线程2不设置任何内容。然后我们分别在这三个线程中打印mThreadLocal的值。

D/MainActivity: MainThread: MainThread
D/MainActivity: Thread1: Thread1
D/MainActivity: Thread2: null

可以看出在不同的线程中,虽然调用的是同一个mThreadLocal的get方法,却获得了不同的值,它可以在不同的线程中维护一组数据彼此不受干扰。接下来我们就来探究ThreadLocal的set和get方法,看看它内部是如何实现的。

public void set(T value) {
    Thread t = Thread.currentThread();//得到当先线程
    ThreadLocalMap map = getMap(t);//得到当线程所对应的map
    if (map != null)
        map.set(this, value);//设置map的值
    else
        createMap(t, value);//map为null就创建map
}

在set方法中首先通过getMap来获取当前线程所对应的ThreadLocalMap实例,再通过ThreadLocalMap的set方法把value值添加到map中。

static class Entry extends WeakReference<ThreadLocal<?>> {
    Object value;

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

private void set(ThreadLocal<?> key, Object value) {

    Entry[] tab = table;
    int len = tab.length;
    int i = key.threadLocalHashCode & (len-1);

    for (Entry e = tab[i];
         e != null;
         e = tab[i = nextIndex(i, len)]) {
        ThreadLocal<?> k = e.get();

        if (k == key) {//找到key对应的Entry
            e.value = value;//将value储存在相应的Entry中
            return;
        }

        if (k == null) {
            replaceStaleEntry(key, value, i);
            return;
        }
    }

    tab[i] = new Entry(key, value);
    int sz = ++size;
    if (!cleanSomeSlots(i, sz) && sz >= threshold)
        rehash();
}

可以看到最终会把数据储存到一个Entry类(一个ThreadLocalMap的内部类)当中,Entry继承了ThreadLocal的弱引用,并在内部储存一个value值。

public T get() {
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null) {
        ThreadLocalMap.Entry e = map.getEntry(this);//得到ThreadLocal(key)对应的Entry
        if (e != null) {
            @SuppressWarnings("unchecked")
            T result = (T)e.value;//取出Entry中的值
            return result;
        }
    }
    return setInitialValue();//返回null
}

get方法同样是取出当前线程对应的ThreadLocalMap实例,然后再通过getEntry得到当前ThreadLocal对应的Entry,然后取出Entry中的value值进行返回。如果当前线程没有ThreadLocalMap实例,则会调用setInitialValue方法,最终会返回null。

private T setInitialValue() {
    T value = initialValue();//设置value为null
    Thread t = Thread.currentThread();
    ThreadLocalMap map = getMap(t);
    if (map != null)
        map.set(this, value);
    else
        createMap(t, value);
    return value;
}

protected T initialValue() {
    return null;
}

3.3MessageQueue

MessageQueue中主要包含插入和读取两个操作分别对应着enqueueMessage和next。首先来看enqueueMessage的源码:

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

    synchronized (this) {
        ...

        msg.markInUse();//标记msg正在被使用
        msg.when = when;
        Message p = mMessages;
        boolean needWake;
        if (p == null || when == 0 || when < p.when) {//p为null代表MessageQueue没有消息
            // 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;
            for (;;) {//从头开始遍历队列,找到消息该插入的位置
                prev = p;
                p = p.next;
                if (p == null || when < p.when) {//对消息的时间进行比较
                    break;
                }
                if (needWake && p.isAsynchronous()) {
                    needWake = false;
                }
            }
            msg.next = p; //插入到队列中
            prev.next = msg;
        }

        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            nativeWake(mPtr);//唤醒
        }
    }
    return true;
}

enqueueMessage主要进行了队列的插入,插入完成后唤醒阻塞中的线程。接下来我们看next的源码:

Message next() {

    final long ptr = mPtr;
    if (ptr == 0) {
        return null;
    }

    int pendingIdleHandlerCount = -1; 
    int nextPollTimeoutMillis = 0;
    for (;;) {//死循环,直到获取到message后才退出
        if (nextPollTimeoutMillis != 0) {
            Binder.flushPendingCommands();
        }

        //阻塞操作(等待enqueueMessage唤醒)
        nativePollOnce(ptr, nextPollTimeoutMillis);

        synchronized (this) {

            final long now = SystemClock.uptimeMillis();
            Message prevMsg = null;
            Message msg = mMessages;
            if (msg != null && msg.target == null) {
                do {
                    prevMsg = msg;
                    msg = msg.next;
                } while (msg != null && !msg.isAsynchronous());
            }
            if (msg != null) {
                if (now < msg.when) {
                    nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE);
                } else {//成功获取到Message
                    
                    mBlocked = false;
                    if (prevMsg != null) {//从链表中移除
                        prevMsg.next = msg.next;
                    } else {
                        mMessages = msg.next;
                    }
                    msg.next = null;
                    if (DEBUG) Log.v(TAG, "Returning message: " + msg);
                    msg.markInUse();
                    return msg;
                }
            } else {
                
                nextPollTimeoutMillis = -1;
            }

          	...
        }

        ...
    }
}

可以看出next方法会出于阻塞状态,直到成功获取到下一条Message才会进行返回。

3.4Looper

Looper是一个循环器,当MessageQueue中有消息在的时候,它就会按顺序一个一个取出。一个线程最多只能有一个Looper,Handler的使用必须要有Looper,若使用Handler时该线程并未创建Looper则会报错。Looper主要有三个重要的方法,创建Looper(prepare)、获取当前线程Looper(myLooper)、开启Looper循环读取消息(loop)。首先我们看一下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));
}

prepare方法中对当前线程是否已经有Looper进行了判断,若已有Looper则抛出异常,否则会新建一个Looper并存入ThreadLocal。接下来看myLooper的源码:

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

十分简单,就是取出了ThreadLocal中的Looper。最后看一下loop的源码:

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(); // 获取队列中的消息(会阻塞)
        if (msg == null) {//取出消息为null,退出死循环
            // No message indicates that the message queue is quitting.
            return;
        }

        ...
            
        try {
            msg.target.dispatchMessage(msg);//分发消息
            if (observer != null) {
                observer.messageDispatched(token, msg);
            }
            dispatchEnd = needEndTime ? SystemClock.uptimeMillis() : 0;
        } catch (Exception exception) {
            if (observer != null) {
                observer.dispatchingThrewException(token, msg, exception);
            }
            throw exception;
        } finally {
            ThreadLocalWorkSource.restore(origWorkSource);
            if (traceTag != 0) {
                Trace.traceEnd(traceTag);
            }
        }
        ...

        msg.recycleUnchecked();//消息回收
    }
}

可以看到loop方法中首先获取到当前Looper中的消息队列,然后进入一个死循环,唯一跳出死循环的方式是MessageQueue的next方法返回了null,而只有当Looper的quit或quitSafely方法被调用时,会通知消息队列退出,此时会返回null,其他时候一直处于一个死循环中。当成功获取到下一条Message时,会调用msg.target.dispatchMessage(msg)来分发信息,msg.target及目标Handler,而我们的目标Handler是在我们想要接收消息的线程中创建的,这样就达到了线程的切换。dispatchMessage方法将在Handler当中具体讲解。

3.5Handler

首先我们看一下我们调用new Handle()时所用到的构造方法。

public Handler() {
    this(null, false);
}

public Handler(@Nullable Callback callback, boolean async) {
    ...
    //获得当前线程的Looper
    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;
    mCallback = callback;
    mAsynchronous = async;
}

可以看到其中调用了Looper的myLooper方法来获取当前线程的Looper。那么问题来了,在Android主线程中,我们不需要自己创建Looper,那么这个Looper是在哪里set到ThreadLocal当中的呢?我们的Handler是在主线中创建的,Android中的主线程其实就对应着ActivityThread,主线程的入口即为main方法。下面是ActivityThread的main方法:

public static void main(String[] args) {
    ...
    Looper.prepareMainLooper();//创建主线程Looper
    ActivityThread thread = new ActivityThread();
    thread.attach(false, startSeq);

    if (sMainThreadHandler == null) {
   	    sMainThreadHandler = thread.getHandler();
    }

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

    Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
    Looper.loop();//开启消息循环,并进行分发
    ....
}

Looper的prepareMainLooper方法

public static void prepareMainLooper() {
    prepare(false);//调用prepare方法
    synchronized (Looper.class) {
        if (sMainLooper != null) {
            throw new IllegalStateException("The main Looper has already been prepared.");
        }
        sMainLooper = myLooper();//获取当前Looper
    }
}

可以看到prepareMainLooper也时调用到了之前所说的prepare方法,这样就在程序启动的时候自动为我们创建了一个Looper,因此我们不需再手动调用prepare方法去创建Looper。而我们自己新建的子线程中系统并没有为我们创建Looper,所以我们要手动进行创建,并开启Looper循环。

我们再来看看Handler的消息是如何发送的,首先看看sendMessage方法:

public final boolean sendMessage(@NonNull Message msg) {
    return sendMessageDelayed(msg, 0);
}
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为自己(Handler),用于loop中dispatchMessage
    msg.target = this;
    msg.workSourceUid = ThreadLocalWorkSource.getUid();

    if (mAsynchronous) {
        msg.setAsynchronous(true);
    }
    return queue.enqueueMessage(msg, uptimeMillis);//将消息放入消息队列
}

再来看看Handler的post方法:

public final boolean post(@NonNull Runnable r) {
   //同样是调用了sendMessageDelayed方法
   return  sendMessageDelayed(getPostMessage(r), 0);
}
private static Message getPostMessage(Runnable r) {
    Message m = Message.obtain();//获取一个message
    m.callback = r;//设置message的callback
    return m;
}

可以看到无论是sendMessage方法还是post方法,都调用了sendMessageDelayed方法。sendMessage是依据传入的message,把他放入消息队列中,而post是重新获取了一个message,并设置其callback,最后把他放入消息队列中。看完了消息是如何发送的,接下来我们来看看loop方法中调用的Handler的dispatchMessage方法:

public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {//msg存在callbcak
        handleCallback(msg);//1
    } else {
        if (mCallback != null) {//handler存在callback
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);//2
    }
}
private static void handleCallback(Message message) {//1处调用
    message.callback.run();
}

public void handleMessage(@NonNull Message msg) {//2处调用
}
    

这里的2就是我们继承Handler时重写的handleMessage方法,1处调用的就是post方法传入的Runnable的run方法。这样我们的Handler就成功得到了message。

4.Looper阻塞

我们通过阅读Looper和MessageQueue的源码发现,当Looper调用loop方法的时候若没有消息得到会进行阻塞,那么为什么Android的主线程并没有被阻塞呢?

实际上阻塞确实存在,但是Android的所有UI操作都是通过Handler来完成的,一旦收到收到各种点击事件之类的消息,就会把线程进行唤醒,所以主线程就不会有卡顿的现象了。

参考资料《Android开发艺术探索》