Handler机制 真的读懂了吗?

广西阳朔风景

广西阳朔风景

背景

聊到Handler,作为Android开发者肯定很熟悉,算是比较基础的知识点,基本初中高级的面试都有这货的身影,算是毒害Android开发者多年的。我也是自认为很理解Handler理论,不就是线程间通信吗?然后巴拉巴拉跟面试官讲了一通,然而结果总是不尽人意的。至此我觉得有必要对Handler从原理梳理起来,准备以两篇文章谈谈我对Handler的理解,首先先谈谈Handler的基本原理,后续编会针对Handler这货的坑深挖,如有描述不恰当,欢迎吐槽!

Handler机制是什么?

Handler作为线程间通信的一种通信机制,其出现核心原因是解决线程切换调度的问题,这里我们带着几个常见面试问题来解开handler的面纱。

1.子线程为什么不能 直接实例化Handler

2.Handler 是如何 切换线程 的?

3.多个Handler(处在不同线程中的Handle)往MessageQueue 中添加数据,如何确保内部安全?

4.Looper.loop()死循环 为什么不会导致主线程发生ANR

接下来我们先来简单看个常见的例子。

public class MainActivity extends AppCompatActivity {

    private Handler handler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            Toast.makeText(MainActivity.this, "来了老弟", Toast.LENGTH_SHORT).show();
        }
    };

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        new Thread(new Runnable() {
            @Override
            public void run() {
                // 子线程需要处理的逻辑...
                
                handler.sendMessage(handler.obtainMessage());
            }
        }).start();
    }
}

相信上面的例子很多Android小伙伴再熟悉不过,常见的子线程中做耗时操作后需要回到主线程更新UI。这里不知道有没小伙伴有疑问Android是怎么做到在子线程中简单用hander.sendMessage 就可以轻松在主线程的handler.handleMessage获取并且处理对应的操作的,带着这个疑问,我们来看看handler在源码中sendMessage到底做了什么。

以下所有源码均做简化,只做理解参考,详情自行查看源码。

Handler 源码分析
public class Handler {
...

public final boolean sendMessage(Message msg){
    return sendMessageDelayed(msg, 0);
}

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

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

public final boolean sendMessageDelayed(Message msg, long delay) {
    if (delay < 0) {
        delay = 0;
    }
    return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delay);
}
boolean sendMessageAtTime(Message msg, long uptime) {
    MessageQueue queue = mQueue;
    if (queue == null) {
        return false;
    }
    return enqueueMessage(queue, msg, uptimeMillis);
}

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

根据上述源码可以看出,Handler无论是sendMessage,还是post、sendMessageDelayed最终都会走向Handler#enqueueMessage(),然后转而调用MessageQueue#enqueueMessage(),其目的都是为了向MessageQueue添加一个Message。

MessageQueue 源码分析

接下来目光转向MessageQueue,手撕MessageQueue。

public class MessageQueue {
...

Message mMessages;
boolean needWake;

/**
 * 消息进入的方法。
 */
boolean enqueueMessage(Message msg, long when) {
    msg.when = when;
    Message p = mMessages;
    if (p == null || when == 0 || when < p.when) {
        msg.next = p;
        mMessages = msg;
    } else {
        Message prev;
        for (;;) {
            prev = p;
            p = p.next;
            if (p == null || when < p.when) {
                break;
            }
        }
        msg.next = p;
        prev.next = msg;
    }

    // 上面省去了对needWake的赋值逻辑
    if (needWake) {
        // 唤醒阻塞
        nativeWake(mPtr);
    }
    return true;
}

/**
 * 消息出去的方法。
 */
Message next() {
    int nextPollTimeMls = 0;
    for (;;) {
        // 尝试阻塞
        nativePollOnce(ptr, nextPollTimeMls);

        Message prevMsg = null;
        Message msg = mMessages;
        if (msg != null && msg.target == null) {
            do {
                prevMsg = msg;
                msg = msg.next;
            } while (msg != null);
        }
        if (msg != null) {
            if (now < msg.when) {
                nextPollTimeMls = Math.min(msg.when - now, MAX_VALUE);
            } else {
                if (prevMsg != null) {
                    prevMsg.next = msg.next;
                } else {
                    mMessages = msg.next;
                }
                msg.next = null;
                return msg;
            }
        } else {
            nextPollTimeMls = -1;
        }

        nextPollTimeMls = 0;
    }
}

上述代码可以看到MessageQueue#enqueueMessage原理实际是将Handler发过来的消息依据uptimeMillis与单向链表mMessages中的每个对象的when进行比较,从而确定合适的位置进行插入,同时维护needWake的逻辑,插入完成后判断needWake唤醒MessageQueue#next()中的阻塞,这块会在后面Looper时候详细说明。至此,Handler发送的消息已经放进到MessageQueue中。

这时候眼尖的同学可能会看到MessageQueue#next方法,没错这就是Message取出的地方。next()里面有一个for(;;)循环,循环体内调用了nativePollOnce(long, int)方法,这是一个Native方法,实际作用是通过Native层的MessageQueue阻塞当前调用栈线程nextPollTimeMls毫秒的时间。

下面是nextPollTimeMls取值的不同情况的阻塞表现:

小于0,一直阻塞,直到被唤醒
等于0,不会阻塞
大于0,最长阻塞nextPollTimeMls毫秒,期间如被唤醒会立即返回

MessageQueue中有一个 nativeWake(long) 的Native方法,可以唤醒 nativePollOnce() 的阻塞。

现在回到next()方法中,可以看到开始循环前nextPollTimeMls的值为0,那么
nativePollOnce()方法立刻返回不会阻塞,此时尝试取出下一个Message元素,如果没有下一个元素,nextPollTimeMls的值被修改为-1,此时nativePollOnce()进入阻塞状态,等待下一个Message的进入并唤醒阻塞,然后取出Message对象返回。

Looper 源码分析

上述分析完MessageQueue完后,我们来看看调用MessageQueue#next()方法的另外一个至关重要的角色Looper,这块关系到Message如何被消费的地方,而在描述Looper之前,需要先来聊聊另外一个也是非常重要的角色ThreadLocal。

第一眼看到ThreadLocal,就感觉这货跟Thread有关,事实的确如此。ThreadLocal用来存储指定线程的数据,当某些数据的作用域是该指定线程并且贯穿该线程的所有执行过程时就可以使用ThreadnLocal存储数据,当某线程使用ThreadnLocal存储数据后,只有该线程可以读取其存储的数据,其他线程无法读取该线程存储的数据。

可以上面这段有点绕,让我借用(抄下)严大的栗子再深刻理解下。

ThreadLocal<Boolean> local = new ThreadLocal<>();
// 设置初始值为true.
local.set(true);

Boolean bool = local.get();
Logger.i("MainThread读取的值为:" + bool);

new Thread() {
    @Override
    public void run() {
        Boolean bool = local.get();
        Logger.i("SubThread读取的值为:" + bool);

        // 设置值为false.
        local.set(false);
    }
}.start():

// 主线程睡1秒,确保上方子线程执行完毕再执行下面的代码。
Thread.sleep(1000);

Boolean newBool = local.get();
Logger.i("MainThread读取的新值为:" + newBool);

运行上面栗子,大胆猜测小心验证,第一个log打印的结果应该为true,这个应该没太大异议:

MainThread读取的值为:true

对于第二个log,这里需要开始引起注意了,根据上述描述的只有当前线程可以读取对应数据,明显第二log为子线程的结果,这里尚未赋值,所以应该为null

SubThread读取的值为:null

紧接着子线程中的local为false,然后第三条log打印,原理同上,不同线程间保持独立数据存储,子线程存储的local值并不会影响原有主线程的赋值,哪怕是延迟了的,所以第三个log的结果应该是:

MainThread读取的新值为:true

根据以上这个小栗子,我们可以得出结论同一个ThreadLocal对象针对不同的线程,所存储的数据应该是独立的,相互不受影响的。

有了ThreadLocal这个基础,回到Looper这个角色来,先来一波源码。

public class Looper {
...

static ThreadLocal<Looper> sThreadLocal = ...;
private static Looper sMainLooper;

MessageQueue mQueue;

// 获取当前线程的的Looper
public static Looper myLooper() {
    return sThreadLocal.get();
}

// 初始化当前线程的Looper
public static void prepare() {
    if (myLooper() == null) {
        sThreadLocal.set(new Looper());
    }
}

// 初始化主线程的Looper
public static void prepareMainLooper() {    
    if (sMainLooper == null) {
        prepare();
        sMainLooper = myLooper();
    }
}

// 获取主线程的Looper
public static Looper getMainLooper() {
    return sMainLooper;
}

// Looper的构造方法中初始化MessageQueue
private Looper() {
    mQueue = new MessageQueue();
}

// 循环处理当前线程的消息队列中的消息
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 nowQueue = nowLooper.mQueue;

    for (;;) {
        Message msg = nowQueue.next(); // 从消息队列读取下一条消息
        if (msg == null) {
            // 如果读取到空消息,退出循环,退出该方法
            return;
        }

        ...

        // 通过Handler分发消息
        msg.target.dispatchMessage(msg);

        ...
    }
}

根据分析Looper的源码,我们可以得到Looper的构造被私有化,只能通过内部prepare方法进行初始化,并且整个Handler机制中的MessageQueue就在Loop构造中初始化的。prepare方法中我们还可以看到初始化的Looper被存储到sThreadLocal中,从而确保Looper在单线程内的唯一。

根据上述理解,可以得到另外一个常问的面试题的答案:

一个线程有几个Looper?

相信答案至此明了:因为Looper存储在sThreadLocal中,而sThreadLocal在线程中的数据具有相互独立,互不影响的特性,所以一个线程只有一个Looper,发散性来讲,一个线程也只有一个MessagQueue,因为MessageQueue是在Looper初始化时被同时初始化的。

关注点回到Looper#loop()方法,首先判断当前的线程的Looper是否为空,为空则抛出运行时异常中断当前操作,不为空则进入死循环读取MessagQueue的消息,
消息发回发送消息的Handler去分发。

到此,我们可以得出第一个问题的答案:

子线程为什么不能 直接实例化Handler

回到Handle,看看Handler初始化主要做了什么:

public class Handler {
...

final MessageQueue mQueue;
final Callback mCallback;

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 " 
            + Thread.currentThread() + " that has not called 
            Looper.prepare()");
    }

    mQueue = mLooper.mQueue;
    mCallback = callback;
}
// Looper中调用的分发方法
public void dispatchMessage(Message msg) {
    if (msg.callback != null) {
        handleCallback(msg);
    } else {
        if (mCallback != null) {
            if (mCallback.handleMessage(msg)) {
                return;
            }
        }
        handleMessage(msg);
    }
}

// 处理Runnable的Message
private static void handleCallback(Message message) {
    message.callback.run();
}

// 处理非Runnable的Message
public void handleMessage(Message msg) {
}

可以看到调用new Handler()时会判断Looper.myLooper()方法获取当前线程的Looper,如果为空则会抛出运行时异常中断当前线程,不为空则拿Looper队列中的MessageQueue对象,等待Handler#sendMessage等方法向消息队列中添加Message。

因此,在子线程中直接new Handler()时,当前子线程的Looper对象势必为空,为空则不能消费Handler所产生的Message,所以在这里直接抛异常。

假如真需要在子线程new Handler,需要先调用Looper.prepare()方法,才能创建Handler对象。

这里也许有小伙伴又有疑惑:为什么主线程new handler不需要先调用Looper.prepare()方法为当前线程创建一个looper,其实只是系统帮我们在底层帮我们调用而已,在程序启动时候帮我们自动调用,在
ActivityThread#mian方法中Looper.prepareMainLooper方法,具体如下:

public final class ActivityThread {

...

public static void main(String[] args) {
    SamplingProfilerIntegration.start();
    CloseGuard.setEnabled(false);
    Environment.initForCurrentUser();
    EventLogger.setReporter(new EventLoggingReporter());
    Process.setArgV0("<pre-initialized>");
    Looper.prepareMainLooper();
    ActivityThread thread = new ActivityThread();
    thread.attach(false);
    if (sMainThreadHandler == null) {
        sMainThreadHandler = thread.getHandler();
    }
    AsyncTask.init();
    if (false) {
        Looper.myLooper().setMessageLogging(new LogPrinter(Log.DEBUG, "ActivityThread"));
    }
    Looper.loop();
    throw new RuntimeException("Main thread loop unexpectedly exited");
}

因此我们应用程序的主线程中会始终存在一个Looper对象,从而不需要再手动去调用Looper.prepare()方法了。

回顾上述Handler、MessageQueue和Looper三者的源代码,不难发现Handler消息机制使用到了生产者消费者设计模式,当某个线程要使用Android的Handler消息机制时,首先要调用Looper#prepare()静态方法为当前线程生成一个Looper对象,紧接着调用Looper#loop()静态方法后,会拿出该线程的Looper对象的MessageQueue开始循环调用MessageQueue#next()方法获取消息队列的下一个Message并处理。

根据上面MessageQueue中分析,当MessageQueue中没有下一个Message时,next()方法会调用MessageQueue#nativePollOnce阻塞当前线程,直到下一个Message被加入并通过Message#nativeWake()唤醒阻塞,此时便可以拿出一个Message返回Looper,Looper通过msg.target.dispatchMessage(msg)分发消息。

接着看回Handler#dispatchMessage(msg)中,这里就可以看回开篇举那个例子handler的写法。在Handler#dispatchMessage(msg)中,首先判断该Message是否是Runnable,如果是则直接执行Runnable#run()方法,如果不是则看当前Handle是否有Callback对象,如果有的话就回调到Callback#handleMessage(Message)方法,没则调用Handler#handleMessage(Message)方法。

理解上述问题后,我们来看看问题2

Handler 是如何 切换线程 的?

上述聊到的某个线程要使用Handler消息机制,首先必须先调用Looper#prepare()方法为当前线程创建一个Looper对象,然后在该线程中调用Looper#loop()拿出该线程Looper对象的MessageQueue开始循环处理其中的消息。其他线程要向该线程发送消息时,只要获取到该线程的Looper对象并创建Handler,在其他线程中使用这个创建的Handler对象即可向该线程Looper的MessageQueue中添加一个消息,此时该线程的Looper#loop()方法即可取出该详细进行处理,从而达到线程切换的效果。

可能上述描述还有一些同学可能还不是很理解,来个栗子。

public class HandlerThread extends Thread {

    private Looper mLooper;
    private Handler mHandler;

    public HandlerThread() {
    }

    @Override
    public void run() {
        Looper.prepare();
        mLooper = Looper.myLooper();
        Looper.loop();
    }
    
    public Looper getLooper() {
        return mLooper;
    }

    public Handler getHandler() {
        if (mHandler == null) {
            mHandler = new Handler(getLooper());
        }
        return mHandler;
    }

    public void quit() {
        if (mLooper != null) {
            mLooper.quit();
        }
    }
}

上述类中,参考了Android SDK提供的HandlerThread,只是这里做了主体的使用,意在让同学能更好理解Handler的线程切换,详细写法自行参考源码,这里不过多解析。
这个类中在线程启动时,初始化该线程的Looper,并作为成员变量存储起来,然后调用Looper#loop()静态方法,让该Looper去处理发送到该线程的消息。
简单粗暴来个调用,让HandlerThread起来干活。

public class AbcThread implements Runnable {
...

HandlerThread thread = new HandlerThread();
thread.start();

此时该线程应该阻塞在Looper#loop()处,如果要往这个线程中发送一个Runnable,那么可以这样做:

public class AbcThread implements Runnable {
...

thread.getHandler().post(new Runnable() {
    @Ovvride
    public void run() {
        // 该处代码执行在 HandleThread#run() 方法中
    }
});

另外一种方式:

public class AbcThread implements Runnable {
...

Looper looper = thread.getLooper();
Handler mHandler = new Handler(looper) {
    @Override
    public void handleMessage(Message msg) {
        // 该处代码执行在 HandlerThread#run() 方法中
    }
};

Message message = new Message();
...
mHandler.sendMessage(message);

细心的同学体会下这两种写法,就能体会出Looper这个对象在线程切换中重要角色。

至此,带着上述一些认识,我们可以来聊聊问题3:

多个Handler(处在不同线程中的Handle)往MessageQueue 中添加数据,如何确保内部安全?

其实相信很多同学心里已经有了答案了,根据上述描述多个Handler实际也是持有同一线程的Looper,从而确保多个Handler发送的消息都是往同一个MessageQuote中添加数据,而且因为Message中都持有对应发送消息的Handler,从而在Message被MessageQueue取出时候能够切回原有handler进行处理,从而保证其内部安全。

最后我们来看看最后一个问题:

Looper.loop()死循环 为什么不会导致主线程发生ANR

要回答这个问题前,我们需要先来了解下ANR是如何发生的?
其实Android所有的UI操作都通过Handler来发消息操作的,包括屏幕刷新,各种点击事件,Activity的生命周期等。因此当Looper.loop()取到任一消息后,处理该消息的时间过长,影响到屏幕刷新速率,此时造成UI卡顿现象,乃至发生ANR。

理解了ANR,我们再看回ActivityThread#mian方法,在main方法中Android系统帮我们做了Looper.prepareMainLooper()以及Looper.loop()循环处理主线程的消息。

假如ActivityThread#mian执行完,也就意味着主线程生命周期结束,应用即将退出,应用程序即将结束。设想下我们不可能点击icon后没啥反应,所以得保证主线程活着。

那么如何保证主线程一直活着呢?就是main方法中的Looper.loop()死循环保证代码一直执行,在MessageQuote中没有消息的时候该线程被MessageQueue#nativePollOnce()阻塞,但是该线程还是活着的,所以我们的主线程活着意味着我们的应用程序是正在运行的。

因此,Looper.loop()中的死循环和阻塞保证了主线程一直在运行,而不是挂掉,它运行过程就是主线程的运行过程,因此Looper.loop()中的死循环和MessageQueue#nativePollOnce()不会导致主线程发生ANR。

主线程内MessageQueue#nativePollOnce()一直阻塞,是否会特别消耗CPU资源呢?这里其实是利用了Linux pipe/epoll机制,当MessageQueue#nativePollOnce()阻塞时,此时主线程会释放CPU资源进入休眠状态,直到下一条消息被加入消息队列,并调用MessageQueue#nativeWake()后,通过往pipe管道写端写入数据来唤醒主线程工作。因此主线程在阻塞时,其实是处于休眠状态,并不会消耗大量CPU资源。

先扒拉这么多内容吧,最后附上一张图方便大家理解,后面还有一篇对Handler机制内部一些不常见的原理以及坑位进行深挖,留意后续。

Handler消息机制

本文借鉴参考以下文章:

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值