这次我自己彻彻底底弄懂 handler 机制了,真的,不信我讲给你听。
从哪里讲起呢,我特意去翻了一下 Handler 的类注释说明,然而好像并没有 get 到我想讲的东西,粗略看一下类注释。
A Handler allows you to send and process {@link Message} and Runnable objects associated with a thread's {@link MessageQueue}. Each Handler instance is associated with a single thread and that thread's message queue. When you create a new Handler, it is bound to the thread / message queue of the thread that is creating it -- from that point on, it will deliver messages and runnables to that message queue and execute them as they come out of the message queue.
没看懂没事,反正我看了翻译也不想懂,我们换个角度来理解 handler。
Handler 我相信大家开发中肯定都用过。没用过的出门左拐~~
一般我们用 Handler 都是用来做线程切换,可能说到这里,有同学会想起一句话“子线程不能修改 ui,主线程不能做耗时操作”,没错,handler 的使用场景大多都是在异步任务中需要修改 ui。然并卵,这个我们都知道,但是并不能让我彻底理解 handler 的机制。
好了,不扯犊子了,耽误大家的时间。
先来看一个错误的示范。
- new Thread(new Runnable() {
- @Override
- public void run() {
- Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(MainActivity.this,"lalala",0).show();
- }
- };
- handler.sendEmptyMessage(0);
- }
- }).start();
根据大家的经验求解,以上代码能否运行通过?为什么
思考一分钟再看答案。
好了,思考结束,我贴运行结果了。
- java.lang.RuntimeException: Can 't create handler inside thread that has not called Looper.prepare()
- //报错行是 Handler handler = new Handler(){'
Why?Why?Why?稍后我再给大家解释。
这时有经验的同学会说,子线程在创建 Handler 之前,需要先调用
,
- Looper.prepare();
那么,我们来看一下Looper$prepare 方法吧。
- static final ThreadLocal < Looper > sThreadLocal = new ThreadLocal < Looper > ();
- 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));
- }
这个方法很简单,如果
则抛异常,不然就执行
- sThreadLocal.get() != null
创建一个 Looper,并且赋值给sThreadLocal。
- sThreadLocal.set(new Looper(quitAllowed));
Looper 的构造方法很简单,就保存了当前线程对象、然后创建了一个MessageQueue 对象,MessageQueue我们稍后再介绍。
可能有些同学不知道 ThreadLocal(我之前也不知道),偷懒的同学可以直接把这个类丢到百度上一搜就知道了,ThreadLocal 解决多线程程序的并发问题提供了一种新的思路。说的接地气一点,就是不同的线程从 ThreadLocal 能取出自己独有的数据,泛型 T 则是ThreadLocal里面取出来的数据类型。就是线程1调用ThreadLocal.set存了个对象 a,线程2再调用 ThreadLocal.get 方法是取不到数据的,只有线程1调用ThreadLocal.get方法才能取到这个数据。
ThreadLocal 在多线程篇好像没有讲,但是没关系,我们有扎实的 java 基础,如果让我们自己手动实现一个ThreadLocal,也不过就半个小时的事。我的实现思路:基于 HashMap 做实现,key 是线程 id,vaule 是线程对应的值,然后创建一个 MyThreadLocal 来管理这个HashMap即可。
好,扯远了。Looper.prepare()就是给当前线程创建了一个Looper对象,存在了静态变量sThreadLocal里面。
然后我们再来看看 Handler 的构造方法,看看为什么没调用Looper.prepare()的情况下直接new Handler 会报错。
- public Handler() {
- this(null, false);
- }
- public Handler(Callback callback, boolean async) {
- if (FIND_POTENTIAL_LEAKS) {
- final Class<? extends Handler> klass = getClass();
- if ((klass.isAnonymousClass() || klass.isMemberClass() || klass.isLocalClass())
- && (klass.getModifiers() & Modifier.STATIC) == 0) {
- Log.w(TAG, "The following Handler class should be static or leaks might occur: " + klass.getCanonicalName());
- }
- }
- mLooper = Looper.myLooper();
- if (mLooper == null) {
- throw new RuntimeException(
- "Can't create handler inside thread that has not called Looper.prepare()");
- }
- mQueue = mLooper.mQueue;
- mCallback = callback;
- mAsynchronous = async;
- }
敲黑板,注意了,这里我们找到了刚刚我们那个异常的抛出代码。代码结构也很简单,我们直接看Looper.myLooper()分析这个 mLooper为什么会为 null。
- public static@Nullable Looper myLooper() {
- return sThreadLocal.get();
- }
噢,不说了,大家都看得懂。到这里,我们解决了刚刚那个 demo 为什么会抛出异常的原因。得出了一个结论
但是?这个结论有什么卵用?别急,接着往下看。
- new Thread(new Runnable() {
- @Override
- public void run() {
- Looper.prepare();
- Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(MainActivity.this,"lalala",0).show();
- }
- };
- handler.sendEmptyMessage(0);
- }
- }).start();
然后,我们加上了
,又执行了一遍代码,同学们思考一下这次能否正常执行并且弹出 Toast。
- Looper.prepare();
333
22
1
好了,我来告诉大家执行结果,执行结果就是没有任何结果,不报错,也没有任何响应,debug 发现 handleMessage方法并没有被回调。我们只好去看handler.sendEmptyMessage(0);是否有将消息发出去。
通过阅读 Handler 的源码,我们发现,Handler 不管是调用postDelayed、sendEmptyMessage、post 等各种方法,最终都会调用enqueueMessage方法,我们来看看这个方法。
- private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) {
- msg.target = this;
- if (mAsynchronous) {
- msg.setAsynchronous(true);
- }
- return queue.enqueueMessage(msg, uptimeMillis);
- }
其中MessageQueue 是构造方法的时候new 的,Message 是根据传参创建的,uptimeMillis 则是一个消息处理时间戳,用于判断消息是立即处理还是稍后处理。
看到这里,还是没看到为什么 handler 发了消息没有回调 handleMessage 方法。
那就接着看 queue.enqueueMessage 吧
enqueueMessage ,顾名思义,就是信息入栈嘛,根据单一职能原则,这里大概不会找到为什么没有回调 handleMessage 的原因,但是我们还是来看一下吧。
- boolean enqueueMessage(Message msg, long when) {
- if (msg.target == null) {
- throw new IllegalArgumentException("Message must have a target.");
- }
- if (msg.isInUse()) {
- throw new IllegalStateException(msg + " This message is already in use.");
- }
- synchronized(this) {
- if (mQuitting) {
- IllegalStateException e = new IllegalStateException(msg.target + " sending message to a Handler on a dead thread");
- Log.w(TAG, e.getMessage(), e);
- msg.recycle();
- return false;
- }
- msg.markInUse();
- msg.when = when;
- Message p = mMessages;
- boolean needWake;
- if (p == null || when == 0 || when < p.when) {
- // New head, wake up the event queue if blocked.
- msg.next = p;
- mMessages = msg;
- needWake = mBlocked;
- } else {
- // Inserted within the middle of the queue. Usually we don't have to wake
- // up the event queue unless there is a barrier at the head of the queue
- // and the message is the earliest asynchronous message in the queue.
- 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; // invariant: p == prev.next
- prev.next = msg;
- }
- // We can assume mPtr != 0 because mQuitting is false.
- if (needWake) {
- nativeWake(mPtr);
- }
- }
- return true;
- }
这里是 Message 的入栈操作,也就是把 Message 存到 MessageQueue 里面,具体实现大家可以不用纠结细节,我给大家简单讲解一下:MessageQueue 里面维护的是一个双向链表,enqueueMessage 方法根据参数 when,决定 Message 查到链表的哪个位置。简单的说MessageQueue 就是一个集合,维护 Handler 消息的专属集合(虽然没有继承集合接口,但是数据结构是链表呀)。
到了这里,还是没找到为什么 handleMessage 方法没被回调的原因。 思考一下,很多同学肯定都知道 handler 是一个消息轮询机制,一条消息只有被处理的时候才会调用 handleMessage,而消息是保存在 Message 里面,Message 由MessageQueue 维护着,我们要处理消息,必须从 MessageQueue 去取。刚刚我们找到了MessageQueue 的添加信息的方法,那么肯定有消息被处理的时候需要出栈的操作,据此,我们在MessageQueue 里面找到了 next()方法,用于消息的出栈,那么只需要找到 next 在哪被调用就知道了。
于是,又是一番寻找。在 Looper.loop()方法里面找到了MessageQueue 的 next 方法调用。刚刚我们在创建 Looper 的时候,构造方法就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 queue = me.mQueue;
- // Make sure the identity of this thread is that of the local process,
- // and keep track of what that identity token actually is.
- Binder.clearCallingIdentity();
- final long ident = Binder.clearCallingIdentity();
- for (;;) {
- Message msg = queue.next(); // might block
- if (msg == null) {
- // No message indicates that the message queue is quitting.
- return;
- }
- // This must be in a local variable, in case a UI event sets the logger
- final Printer logging = me.mLogging;
- if (logging != null) {
- logging.println(">>>>> Dispatching to " + msg.target + " " +
- msg.callback + ": " + msg.what);
- }
- final long slowDispatchThresholdMs = me.mSlowDispatchThresholdMs;
- final long traceTag = me.mTraceTag;
- if (traceTag != 0 && Trace.isTagEnabled(traceTag)) {
- Trace.traceBegin(traceTag, msg.target.getTraceName(msg));
- }
- final long start = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
- final long end;
- try {
- msg.target.dispatchMessage(msg);
- end = (slowDispatchThresholdMs == 0) ? 0 : SystemClock.uptimeMillis();
- } finally {
- if (traceTag != 0) {
- Trace.traceEnd(traceTag);
- }
- }
- if (slowDispatchThresholdMs > 0) {
- final long time = end - start;
- if (time > slowDispatchThresholdMs) {
- Slog.w(TAG, "Dispatch took " + time + "ms on "
- + Thread.currentThread().getName() + ", h=" +
- msg.target + " cb=" + msg.callback + " msg=" + msg.what);
- }
- }
- if (logging != null) {
- logging.println("<<<<< Finished to " + msg.target + " " + msg.callback);
- }
- // Make sure that during the course of dispatching the
- // identity of the thread wasn't corrupted.
- final long newIdent = Binder.clearCallingIdentity();
- if (ident != newIdent) {
- Log.wtf(TAG, "Thread identity changed from 0x"
- + Long.toHexString(ident) + " to 0x"
- + Long.toHexString(newIdent) + " while dispatching to "
- + msg.target.getClass().getName() + " "
- + msg.callback + " what=" + msg.what);
- }
- msg.recycleUnchecked();
- }
- }
这个方法比较长,我给大家简单解释一下。 首先这是一个静态方法,通过静态方法 myLooper()获取当前线程的 Looper 对象,然后取出Looper 里面的 MessageQueue,然后就走了死循环,不断的取出 MessageQueue 里面的 Message 进行消费。很多同学都知道 Android 的主线程就是一个死循环,这里不扯远了。
我们可以找到
这样一行代码,我们看一下 handler 的这个方法:
- msg.target.dispatchMessage(msg);
- public void dispatchMessage(Message msg) {
- if (msg.callback != null) {
- handleCallback(msg);
- } else {
- if (mCallback != null) {
- if (mCallback.handleMessage(msg)) {
- return;
- }
- }
- handleMessage(msg);
- }
- }
这里就很简单了,根据状态,决定调用那个方法处理消息。我们可以轻易判断出这里调用的就是handleMessage。然后我们在思考一下,这个 handler 是谁,在那里创建的。
前面我们在 handler 的enqueueMessage()方法里面
把 handler 本身赋值给了 Message,所以msg.target.dispatchMessage(msg) 实际上调用的就是
- msg.target = this;
这个 handler 本身,所以这里也是没毛病的。
- handler.sendEmptyMessage(0);
到这里,现在就只差 Looper.loop()方法没被调用了,那么我们手动调用一下试试?
然后有了如下代码:
- new Thread(new Runnable() {
- @Override
- public void run() {
- Looper.prepare();
- Looper.loop();
- Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- Toast.makeText(MainActivity.this,"lalala",0).show();
- }
- };
- handler.sendEmptyMessage(0);
- }
- }).start();
然而,还是不行。同学们再思考一下原因?
333
22
1
好了,不逗大家了,
开启了一个死循环,子线程执行到这行代码就在死循环,后面的代码就不会往下走了。把这行代码移到 handler.sendEmptyMessage(0)后面即可。
- Looper.loop();
一直没用过标题,怕你们看着累,我加个标题吧。 上面的这些讲解,我们大概了解到了 handler 的工作机制。我给大家回顾一下。
1.调用 Looper.prepare();给当前线程创建一个 Looper,存在 Looper 的静态变量ThreadLocal里面。且这个方法在同一个线程只能调用一次,保证了一线程对应一个 Looper 。
2.Looper 的构造方法创建了MessageQueue 对象,所以Looper和 MessageQueue 也是一对一的关系。
3.Looper.loop()根据当前线程,获取到 Looper 对象,然后死循环MessageQueue的消息。
4.Handler 里面有个mLooper对象,默认赋值是 Looper.myLooper();
5.Handler 发生消息,只是将一个 Message 丢给 Handler 的成员变量mLooper里面的 MessageQueue 里面去。然后由Handler 里面的 mLooper 消费掉(前期是mLooper已经调用了loop 方法 开启死循环)。
大致就是酱紫吧。
再接着挖坑了,还是刚刚那个例子。
- new Thread(new Runnable() {
- @Override
- public void run() {
- Looper.prepare();
- Looper.loop();
- Handler handler = new Handler(){
- @Override
- public void handleMessage(Message msg) {
- mBt.setText("asasA");
- }
- };
- handler.sendEmptyMessage(0);
- }
- }).start();
同学们思考一下,这次代码能否正常执行。
333 22 1
好了,不给大家看运行错误日志了,看到这里,相信每次都认真思考过的同学应该知道报错原因了,没想出来也没关系,我们再来回顾一遍。
上面的分析中,主要牵涉到以下几个类。
一个消息 bean。
消息队列,可以当成是一个集合,但是数据结构是双向链表,只给 Looper 用,和 Looper 是一对一的关系。
线程可以没开启 Looper,但是最多只能开启一个,Looper 在构造方法里面创建一个 MessageQueue,在 loop()方法里面开启死循环不断从 MessageQueue 取Message,Message 消息在循环里面由Message 持有的 handler$handleMessage 方法处理。
在构造方法里面会绑定一个 Looper,默认绑定当前线程的 Looper,也可以指定一个 Looper 绑定。然后当 Handler 发送一个消息的时候,就把这个消息创封装成一个 Message,发送到绑定 Looper 的 MessageQueue 里面去,再被 Looper$loop 开启的死循环消费掉。
好像讲完了
来源: https://juejin.im/post/5a278d876fb9a0451238d1f9