学习Android之Handler

242 阅读10分钟

一、简介

Handler、Looper、MessageQueue、Message这四大金刚构成了Android的消息机制,Handler是暴露给开发人员的上层接口。通过Handler可以自由地进行线程切换,常被用于从子线程切换到UI线程,进而更新UI界面。例如:Retrofit获取网络数据后回调、EventBus消息跨线程传递以及LiveData的postValue都是使用Handler来将子线程切换到UI线程。

二、Handler的用法

下面举几个例子:

1、子线程处理任务结束后,切换到UI线程进行更新

2、执行定时任务

3、子线程创建Handler

1、子线程切换到UI线程

在子线程中进行耗时操作后,在UI线程更新控件

// Handler
private Handler mHandler = new Handler(getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 处理消息,UI线程,在这里进行更新处理
        ...
    }
}

// 子线程
new Thread(new Runnable() {
    @Override
    public void run() {
        // 执行耗时操作
        ...
        // 发送消息
        Message message = mHandler.obtainMessage(0);
        mHandler.sendMessage(nessage);
    }
}).start();

// 在onDestory 释放资源
protected void onDestory() {
    if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
    }
}

在UI线程中,创建了mHandler对象,在子线程中执行耗时操作后,通过mHandler发送消息,并会在mHandler的handleMessage中处理该消息,此时就由子线程切换到了主线程中了。需要注意下:在onDestory释放资源的时候,调用下mHandler.removeCallbacksAndMessages(null)。

2、定时任务

在创建Handler的线程中,执行一个定时任务

// 在UI线程创建 mHandler
private Handler mHandler = new Handler(getMainLooper());

// 在onCreate 做定时任务
protected void onCreate() {
    if (mHandler != null) {
        mLaunchHandler.postDelayed(new Runnable() {
            @Override
            public void run() {
                // 在UI线程,做某些操作
            }
        }, 500);
    }
}

// 在onDestory 释放资源
protected void onDestory() {
    if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
    }
}

3、子线程创建Handler

Handler的工作需要Looper。在UI线程时,ActivityThread已经为我们准备好了Loop。但是如果要在子线程自己创建Handler的话,需要手动为该线程添加Loop:

new Thread("sub Thread Name") {
    @Override
    public void run() {
        Looper.prepare();
        Handler handler = new Handler();
        Looper.loop();
    }
}.start();

三、原理

应用程序也是一个Java程序,有一个main函数。对于Java程序而言,执行完main函数所有内容之后,程序就退出了。而Android的应用程序从ActivityThread的main函数执行之后,会有一个死循环在进行,这样main函数执行的UI线程就不会退出了。

1、处理消息前的准备工作

且从ActivityThread的main函数看起:

public static void main(String[] args) {
    ...
    // 创建Loop对象,并将其存入到该线程的ThreadLocal中
    Looper.prepareMainLooper();
    ...
    // 创建ActivityThread对象
    ActivityThread thread = new ActivityThread();
    // 建立Binder通道(创建新线程)
    thread.attach(false, startSeq);
    ...
    // 在当前线程中死循环读取MessageQueue消息
    Looper.loop();

先看下执行Looper.prepareMainLooper()会做哪些操作:

public static void prepareMainLooper() {
    // 到这里执行prepare函数
    prepare(false);
    ...
}
// 在该函数下可以看到 将Loop对象设置 和该线程绑定的sThreadLocal 中
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));
}
// 继续看下 sThreadLocal :线程内部的数据存储类
static final ThreadLocal<Looper> sThreadLocal = new ThreadLocal<Looper>();

// 再继续看下 new Looper() 构造函数: 可以发现在Loop构造函数中创建了一个与其绑定的MessageQueue对象
private Looper(boolean quitAllowed) {
    mQueue = new MessageQueue(quitAllowed);
    mThread = Thread.currentThread();
}

然后在看下执行Looper.loop()会做哪些操作:

public static void loop() {
    // 这里的myLooper()方法就是:sThreadLocal.get(),获取当前线程的Looper对象
    final Looper me = myLooper();
    ...
    // 这里在UI线程开启了一个死循环(可能会有疑惑这里有死循环:UI线程会被阻塞吗?UI线程如何处理后续的逻辑呢?)
    for (;;) {
        if (!loopOnce(me, ident, thresholdOverride)) {
            return;
        }
    }
}

// 先继续往下看,看下loopOnce方法:
private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    // 这是个阻塞方法,取Loop的MessageQueue对象中的消息。
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    ...
    // 这里的msg.target实际就是指发消息的Handler:即谁发消息,谁处理
    // 因此通过该方法就将消息回到了Handler的dispacthMessage中来进行处理
    msg.target.dispatchMessage(msg);
    ...
}

// 再继续看下 MessageQueue 的 next方法: mQueue.next()
```java
Message next() {
    ...
    for (;;) {
       // 该方法是一个阻塞方法,根据nextPollTimeoutMillis判断是否要阻塞
       nativePollOnce(ptr, nextPollTimeoutMillis);
    }
    ...
}

// 如果有消息msg,则将交有发送该消息的handler进行处理:
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

上述小结:

(1)ActivityTreahd的main函数中通过Looper.prepareMainLooper()创建了Looper对象,并将其设置到该UI线程的ThreadLocal当中,此Looper对象与当前UI线程进行了绑定。其中Looper对象中创建了MessageQueue对象。

(2)ActivityTreahd的main函数中的Looper.loop()开启了死循环:调用loopOnce方法

(3)在loopOnce方法中通过MessageQueue对象的next方法来获取需要处理的Message对象。但next方法是一个阻塞方法,由内部的nativePollOnce方法进行阻塞

(4)如果此时来一个消息message,则next方法将被唤醒,从而获取到message后交由其发送消息的handler进行处理。

2、发送消息

从构建Handler及sendMessage()方法看起:

// 构建Handler
private Handler mHandler = new Handler(getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        ...
    }
}

// 上述的构建Handler最终会调用到这里:将getMainLooper()赋值给当前mLooper对象
// getMainLooper方法实际就是前面的sThreadLocal.get(),从线程内部的数据存储类中获取已经创建好的Loop对象
// 这里的mQueue消息队列 也是Loop中创建好的MessageQueue对象赋值给mQueue的
public Handler(@NonNull Looper looper, @Nullable Callback callback, boolean async) {
    mLooper = looper;
    mQueue = looper.mQueue;
    mCallback = callback;
    mAsynchronous = async;
}

看下发送消息sendMessage(msg)方法

// 子线程
new Thread(new Runnable() {
    @Override
    public void run() {
        // 执行耗时操作
        ...
        // 发送消息
        Message message = mHandler.obtainMessage(0);
        mHandler.sendMessage(nessage);
    }
}).start();

// sendMessage 方法最后会调用到这里
public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) {
    MessageQueue queue = mQueue;
    // 将消息存储到消息队列中去(实际上数据结构是一个单链表)
    return enqueueMessage(queue, msg, uptimeMillis);
}

// 继续看下Handler的enqueueMessage方法
private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg,
        long uptimeMillis) {
    // 这里的Message的target属性是Handler类型,所以在这里将此msg和发送它的Handler进行了绑定
    msg.target = this;
    // 从Handler 切换到 MessageQueue 中的 enqueueMessage 执行
    return queue.enqueueMessage(msg, uptimeMillis);
}

// 继续看下 MessageQueue 中的 enqueueMessage 方法
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    synchronized (this) {
        // message 存入单链表中的操作...
        ...
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            // 这里有消息了,将唤醒next方法中的阻塞
            nativeWake(mPtr);
        }
    }
    return true;
}

// 最后看下Message 消息的载体结构
public final class Message implements Parcelable {
    /**
     * User-defined message code so that the recipient can identify
     * what this message is about. Each {@link Handler} has its own name-space
     * for message codes, so you do not need to worry about yours conflicting
     * with other handlers.
     */
     // what 常用于 同一个handler的哪个消息
    public int what;

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg1;

    /**
     * arg1 and arg2 are lower-cost alternatives to using
     * {@link #setData(Bundle) setData()} if you only need to store a
     * few integer values.
     */
    public int arg2;

    /**
     * An arbitrary object to send to the recipient.  When using
     * {@link Messenger} to send the message across processes this can only
     * be non-null if it contains a Parcelable of a framework class (not one
     * implemented by the application).   For other data transfer use
     * {@link #setData}.
     *
     * <p>Note that Parcelable objects here are not supported prior to
     * the {@link android.os.Build.VERSION_CODES#FROYO} release.
     */
    // obj 常用于 携带的参数
    public Object obj;

    /*package*/ Bundle data;

    // 这个是消息入队列时,将该处理该消息的Handler绑定在消息上,在处理消息时就是通过target来拿到对应的Handler,从而调用Handler的dispatchMessage方法的
    @UnsupportedAppUsage
    /*package*/ Handler target;
}

上述小结:

(1)在UI线程中构建Handler,并为其提供了ActivityThread创建的Looper对象及MessageQueue对象以供使用

(2)通过sendMessage发送消息,调用了Handler的enqueueMessage方法:将Handler绑定在该message消息上,然后调用了MessageQueue的enqueueMessage方法

(3)MessageQueue的enqueueMessage方法:将消息存入单链表中,并进行唤醒操作,此时会唤醒next方法

(4)唤醒next方法后,将读MessageQueue中的消息(并移除掉)给到Looper中的loopOnce方法,进而得到需要处理的消息

3、处理消息

Looper中的loopOnce方法拿到消息后,将进行消息的处理, 从Loop 中的 loopOnce 方法看起:

private static boolean loopOnce(final Looper me,
        final long ident, final int thresholdOverride) {
    // 这里已经拿到需要处理的msg
    Message msg = me.mQueue.next(); // might block
    if (msg == null) {
        // No message indicates that the message queue is quitting.
        return false;
    }
    ...
    // 这里的msg.target实际就是指发消息的Handler:即谁发消息,谁处理
    // 因此通过该方法就将消息回到了Handler的dispacthMessage中来进行处理
    msg.target.dispatchMessage(msg);
    ...
}

// 有消息msg,通过handler下的该方法进行处理,从而回调到handler中的handleMessage方法中
public void dispatchMessage(@NonNull Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

上述小结:

(1)Looper中死循环的loopOnce方法,拿到message信息后会立马进行处理

(2)通过message信息的target属性,找到发送该message的handler,然后调用handler的dispatchMessage方法

(3)通过handler的dispatchMessage方法,最终又回到了 handler 的回调中的 handleMessage 方法来完成处理消息工作

4、线程是如何从子线程切换到UI线程的

通过上面处理消息前的准备工作、发送消息、处理消息,基本上对整个消息处理有点整理轮廓的认知了,那在哪一个步骤是由子线程切换到UI线程的呢?

我个人理解是在:将消息入队列之后,执行nativeWake(mPtr)进行唤醒,此enqueueMessage方法在子线程中执行完毕。此时next方法被唤醒,开始继续执行,而此刻已经是在UI线程了。

// 消息入队列,此时还是在子线程中工作,但执行完nativeWake(mPtr)方法后,该子线程已经执行完毕了
boolean enqueueMessage(Message msg, long when) {
    if (msg.target == null) {
        throw new IllegalArgumentException("Message must have a target.");
    }
    synchronized (this) {
        // message 存入单链表中的操作...
        ...
        // We can assume mPtr != 0 because mQuitting is false.
        if (needWake) {
            // 这里有消息了,将唤醒next方法中的阻塞
            nativeWake(mPtr);
        }
    }
    return true;
}

// 此时,MessageQueue的next方法被唤醒,开始继续执行,而此刻已经是在UI线程了。

四、注意事项

1、在子线程中创建Handler时,记得要进行退出操作

在UI线程,ActivityThread已经创建好了Looper,并且一直在死循环处理消息,此时当应用程序退出时,该Looper的死循环才结束。

如果在子线程开启了Looper,在处理完任务后,一定要手动调用Looper的quit或quitSafely来退出Looper,否则该子线程会一直死循环,消耗资源,不能结束掉该线程。

2、使用Handler时,注意内存泄漏

如果在当前Activity创建的Handler是匿名内部类(或声明为非静态变量),很容易造成内存泄漏,原因是:当前Activity要销毁时,handler有任务还没处理完,handler持有Activity的引用,导致该Activity不能被GC

// 匿名内部类
new Handler().post(...)

// 非静态变量
private Handler mHandler = new Handler(getMainLooper()) {
    @Override
    public void handleMessage(Message msg) {
        // 处理消息,UI线程,在这里进行更新处理
        ...
    }
}

解决方法:

(1)在Activity销毁时,清空Handler中执行或未执行的Callback及Message:

// 在onDestory 释放资源
protected void onDestory() {
    if (mHandler != null) {
        mHandler.removeCallbacksAndMessages(null);
    }
}

(2)非静态内部类 + 弱引用:

private static class TaskHandler extend Handler {
    // 弱引用, T -> 你的Activity
    private WeakReference<T> mWeakReference;
    
    public TaskHandler(T t) {
        this.mWeakReference = new WeakReference<T>(t);
    }
    
    @override
    public void handlerMessage(Message msg) {
        T activity = mWeakReference.get();
        // 处理消息
    }
}

五、待学习

1、学习下 ThreadLocal

2、学习下 nativePollOnce和nativeWake 及背后的linux的pipe/epoll

3、学习下 EventBus、Retrofit、RxJava、LiveData 如何使用 Handler 进行线程切换的

4、学习下 ActivityThread、ApplicationThread 和 AMS 如何使用 Handler的

六、参考

1、《Android开发艺术探索》 - 第10章 Android的消息机制

2、Android:遇到Handler中有Loop死循环,还没有阻塞主线程,这是为什么呢?www.jianshu.com/p/56c7dd2ba…

3、Handler 源码解析:nativePollOnce阻塞和nativeWake唤醒 www.jianshu.com/p/10924b905…

4、android Handler避免内存泄露handler.removeCallbacksAndMessages(null)的使用 blog.csdn.net/qq_26317325…

5、Android --- Handler 内存泄漏原因及解决方案 blog.csdn.net/qq_43290288…

6、Handler内存泄漏原因及解决方案 blog.csdn.net/sqf25187754…

7、Handler中有Loop死循环,为什么没有阻塞主线程,原理是什么segmentfault.com/a/119000002…