工作中,我们可能直接使用我们需要的工具方法,但是不曾了解其中的原理内涵,这样并不能很好的让我们理解其运行机制,在复杂项目和疑难问题时无从入手。作为开发想要提高并设计架构,一是要先学习优秀的设计理念,再就是了解其内部原理,为自己在复杂使用场景和发生疑难问题时能够透过表象看到本质。
Handler,Message,looper 和 MessageQueue 构成了安卓的消息机制,handler创建后可以通过 sendMessage 将消息加入消息队列,然后 looper不断的将消息从 MessageQueue 中取出来,回调到 Hander 的 handleMessage方法,从而实现线程的通信。
从两种情况来说,第一在UI线程创建Handler,此时我们不需要手动开启looper,因为在应用启动时,在ActivityThread的main方法中就创建了一个当前主线程的looper,并开启了消息队列,消息队列是一个无限循环,为什么无限循环不会ANR?因为可以说,应用的整个生命周期就是运行在这个消息循环中的,安卓是由事件驱动的,Looper.loop不断的接收处理事件,每一个点击触摸或者Activity每一个生命周期都是在Looper.loop的控制之下的,looper.loop一旦结束,应用程序的生命周期也就结束了。我们可以想想什么情况下会发生ANR,第一,事件没有得到处理,第二,事件正在处理,但是没有及时完成,而对事件进行处理的就是looper,所以只能说事件的处理如果阻塞会导致ANR,而不能说looper的无限循环会ANR
另一种情况就是在子线程创建Handler,此时由于这个线程中没有默认开启的消息队列,所以我们需要手动调用looper.prepare(),并通过looper.loop开启消息
主线程Looper从消息队列读取消息,当读完所有消息时,主线程阻塞。子线程往消息队列发送消息,并且往管道文件写数据,主线程即被唤醒,从管道文件读取数据,主线程被唤醒只是为了读取消息,当消息读取完毕,再次睡眠。因此loop的循环并不会对CPU性能有过多的消耗。
Handler源码部分解读:
初级程序员使用handler时有时会出现异常。Can’t create handler inside thread that has not called Looper.prepare()
为什么呢。
在core/java/android/os/Handler.java 中
@hide
*/
public Handler(@Nullable 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 " + Thread.currentThread()
+ " that has not called Looper.prepare()");
}
mQueue = mLooper.mQueue;
mCallback = callback;
mAsynchronous = async;
}
在其初始化的结构体中,获取mLooper = Looper.myLooper();通过检测mLooper是否为空,为空则报上述异常。如果不为空, 再通过mQueue = mLooper.mQueue;获取消息队列,可以看到messageQuene对象是在Looper中获取的。
再往下看,Handler中提供获取主线程Handler的方法。
/** @hide */
@UnsupportedAppUsage
@NonNull
public static Handler getMain() {
if (MAIN_THREAD_HANDLER == null) {
MAIN_THREAD_HANDLER = new Handler(Looper.getMainLooper());
}
return MAIN_THREAD_HANDLER;
}
之后,我们我们发现了我们常用的obtainMessage方法。为何建议使用obtainMessage而不是new Message()呢,从obtainMessage的注释中也可以看出来,其会从一个全局message pool中返回一个新的message,比新建一个实例更有效率。另外的比如obtainMessage(int what)和obtainMessage(int what, @Nullable Object obj)则同步设置了message.what和obj消息。
/**
* Returns a new {@link android.os.Message Message} from the global message pool. More efficient than
* creating and allocating new instances. The retrieved message has its handler set to this instance (Message.target == this).
* If you don't want that facility, just call Message.obtain() instead.
*/
@NonNull
public final Message obtainMessage()
{
return Message.obtain(this);
}
然后是各种发送消息的方法,包括发送message消息的和post(runnable)的方法。可以看到无论是send还是post还是带时间参数的,最终都走到了queue.enqueueMessage。这个messagequeue在下边的小节会解读。
Looper源码解读:
在Handler源码中我们看到其Looper是用Looper.myLooper获取的,这里looper相当于消息的传送带,不断的循环检测是否有消息发过来。
在Looper类文件的开头我们就看到了谷歌给我们的注释和一个例子,清楚的说明了普通现场默认是没有looper的,需要手动创建,然后创建的方法就是Looper.prepare(),然后下面的例子也清楚的解释了上节中报错信息如何防止,Can’t create handler inside thread that has not called Looper.prepare()。然后启动looper循环用Looper.loop(),以启动消息循环,直到loop被停止。
/**
* Class used to run a message loop for a thread. Threads by default do
* not have a message loop associated with them; to create one, call
* {@link #prepare} in the thread that is to run the loop, and then
* {@link #loop} to have it process messages until the loop is stopped.
*
* <p>Most interaction with a message loop is through the
* {@link Handler} class.
*
* <p>This is a typical example of the implementation of a Looper thread,
* using the separation of {@link #prepare} and {@link #loop} to create an
* initial Handler to communicate with the Looper.
*
* <pre>
* class LooperThread extends Thread {
* public Handler mHandler;
*
* public void run() {
* Looper.prepare();
*
* mHandler = new Handler() {
* public void handleMessage(Message msg) {
* // process incoming messages here
* }
* };
*
* Looper.loop();
* }
* }</pre>
*/
然后prepare的方法定义,可以看到sThreadLocal.get() != null的判断,这里可以看出,一个线程只能创建一个looper,但是如何保证的呢,代码如何实现一个线程一个looper的呢。这里就需要看代码中的sThreadLocal的get和set方法了。
/** Initialize the current thread as a looper.
* This gives you a chance to create handlers that then reference
* this looper, before actually starting the