阅读 345

Android 源码分析(二)handler 机制

深入浅出 Handler

这次我自己彻彻底底弄懂 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则抛异常,不然就执行sThreadLocal.set(new Looper(quitAllowed));创建一个 Looper,并且赋值给sThreadLocal。

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 为什么会抛出异常的原因。得出了一个结论

  • 在子线程中创建 Handler 的时候必须先调用 Looper.prepare()方法。

但是?这个结论有什么卵用?别急,接着往下看。

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();
复制代码

然后,我们加上了Looper.prepare();,又执行了一遍代码,同学们思考一下这次能否正常执行并且弹出 Toast。

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 的主线程就是一个死循环,这里不扯远了。

我们可以找到 msg.target.dispatchMessage(msg);这样一行代码,我们看一下 handler 的这个方法:

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()方法里面msg.target = this;把 handler 本身赋值给了 Message,所以msg.target.dispatchMessage(msg) 实际上调用的就是handler.sendEmptyMessage(0);这个 handler 本身,所以这里也是没毛病的。

到这里,现在就只差 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

好了,不逗大家了,Looper.loop();开启了一个死循环,子线程执行到这行代码就在死循环,后面的代码就不会往下走了。把这行代码移到 handler.sendEmptyMessage(0)后面即可。

还没讲完

一直没用过标题,怕你们看着累,我加个标题吧。 上面的这些讲解,我们大概了解到了 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

好了,不给大家看运行错误日志了,看到这里,相信每次都认真思考过的同学应该知道报错原因了,没想出来也没关系,我们再来回顾一遍。

上面的分析中,主要牵涉到以下几个类。

Message

一个消息 bean。

MessageQueue

消息队列,可以当成是一个集合,但是数据结构是双向链表,只给 Looper 用,和 Looper 是一对一的关系。

Looper

线程可以没开启 Looper,但是最多只能开启一个,Looper 在构造方法里面创建一个 MessageQueue,在 loop()方法里面开启死循环不断从 MessageQueue 取Message,Message 消息在循环里面由Message 持有的 handler$handleMessage 方法处理。

Handler

在构造方法里面会绑定一个 Looper,默认绑定当前线程的 Looper,也可以指定一个 Looper 绑定。然后当 Handler 发送一个消息的时候,就把这个消息创封装成一个 Message,发送到绑定 Looper 的 MessageQueue 里面去,再被 Looper$loop 开启的死循环消费掉。

好像讲完了😰

上面的报错就是我们熟悉的“子线程不能修改 ui”的错,是由 ViewRootImpl 检测抛出的异常,这个不属于handler 的内容,所以我们在创建 Handler 的时候指定 handler 绑定主线程 Looper 即可。

好了,Handler 应该已经讲清楚了吧,有点像生产者消费者模型,哈哈哈哈哈~~

来,思考一下,谁是生产者,谁是消费者。

哦,对了,漏了几个知识点。

补充几个知识点

主线程Looper 问题

为什么在主线程创建的 handler,可以在子线程handleMessage 修改 ui,而子线程却不可以呢? 这个问题在上面的分析过程已经讲过了,handler 的创建默认是绑定当前线程的 Looper,你在子线程创建 handler 的时候指定 handler 绑定 主线程的 Looper 即可,代码是

然后主线程的Looper 是在哪里创建的呢?

我们都知道 Activity 的启动是从 ActivityThread 的 main 方法开始的(不知道别急,关注我,后面我会分析 Activity 的启动过程的),在 main 方法的结尾有这么几行代码。

Looper.prepareMainLooper();

ActivityThread thread = new ActivityThread();
thread.attach(false);

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

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

// End of event ActivityThreadMain.
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
Looper.loop();
复制代码

好了,我们的主线程要开启消息循环机制,也是需要调用 Looper.loop()的,在 ActivityThread 里面帮我们做了而已。

为什么主线程执行 looper 的死循环不会 ANR,而主线程做耗时操作,就会 ANR

先解释一下 ANR(Application not Response)应用无响应,单词应该没拼错。

刚刚我们已经知道主线程的循环是在不断死循环去处理 MessageQueue 里面的消息,但是 MessageQueue 不仅仅是我们手动创建的 Handler 去往里面生产消息,更多的是各种系统的消息,比如说?UI 的刷新,应该也是在这里面处理的(我猜测,后期研究 View 源码的时候再验证哦,但是那传说中的16ms 刷新一次的屏幕,肯定跟这个有关系)。所以,我们在 Activity 主线程的某个方法里面做了耗时操作,会影响 MessageQueue 里面下一个 Message 的执行,如果下一个 Message 正好是刷新View。其实 CPU 执行效率很高,一秒钟能处理很多很多 message,比如说有100个,那么耗时操作1秒钟,就会导致后面100个message 的处理被滞后,这就造成了界面卡顿。

Activity 的 runOnUiThread 方法怎样切换线程的

这个就简单了,点进去看 Activity 对这个方法的实现。

final Handler mHandler = new Handler();
public final void runOnUiThread(Runnable action) {
    if (Thread.currentThread() != mUiThread) {
        mHandler.post(action);
    } else {
        action.run();
    }
}
复制代码

没什么好说的了,过!下一题

View 的 post 方法

public boolean postDelayed(Runnable action, long delayMillis) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.postDelayed(action, delayMillis);
    }

    // Postpone the runnable until we know on which thread it needs to run.
    // Assume that the runnable will be successfully placed after attach.
    getRunQueue().postDelayed(action, delayMillis);
    return true;
}
复制代码

咳咳,这个,我暂时也解释不好,但是我们能看到,如果 View 已经被显示到 Window 之后,会调用 handler 来处理这个 Runnable。没办法,我只好通过 debug 的方式,来跟你们证明这里也是把消息放到了主线程的 MessageQueue 里面去了。attachInfo.mHandler.mLooper 是main looper,对应的是 main 线程。

好了,Handler 消息机制的讲解及源码分析就到这里咯。

关注下面的标签,发现更多相似文章
评论