一、概述
Handler是Android中解决异步音讯的机制。Looper、Handler、MessageQueue、Message概括来说就是:Looper负责的就是创立一个MessageQueue,而后进入一个有限循环体一直从该MessageQueue中读取音讯Message,而后回调相应的音讯处理函数,而音讯的创建者就是一个或多个Handler,执行实现一个音讯后则持续循环。
二、MessageQueue详解
音讯队列MessageQueue就是寄存音讯的队列。那队列中存储的音讯是什么呢?假如咱们在UI界面上单击了某个按钮,而此时程序又恰好收到了某个播送事件,那咱们如何解决这两件事呢? 因为一个线程在某一时刻只能解决一件事件,不能同时解决多件事件,所以咱们不能同时解决按钮的单击事件和播送事件,咱们只能挨个对其进行解决,只有挨个解决就要有解决的先后顺序。为此Android把UI界面上单击按钮的事件封装成了一个Message,将其放入到MessageQueue外面去,行将单击按钮事件的Message入栈到音讯队列中,而后再将播送事件的封装成以Message,也将其入栈到音讯队列中。也就是说一个Message对象示意的是线程须要解决的一件事件,音讯队列就是一堆须要解决的Message的池。线程Thread会顺次取出音讯队列中的音讯,顺次对其进行解决。
MessageQueue中有两个比拟重要的办法,一个是enqueueMessage办法,一个是next办法。enqueueMessage办法用于将一个Message放入到音讯队列MessageQueue中,next办法是从音讯队列MessageQueue中阻塞式地取出一个Message。
三、Looper详解
音讯队列MessageQueue只是存储Message的中央,真正让音讯队列循环起来的是Looper,这就好比音讯队列MessageQueue是个水车,那么Looper就是让水车转动起来的河水,如果没有河水,那么水车就是个静止的陈设,没有任何用途,Looper让MessageQueue动了起来。
Looper是用来使线程中的音讯循环起来的。默认状况下当咱们创立一个新的线程的时候,这个线程外面是没有音讯队列MessageQueue的。为了可能让线程可能绑定一个音讯队列,咱们须要借助于Looper:首先咱们要调用Looper的prepare办法,而后调用Looper的loop办法。
(一)prepare()办法
public static final void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new Looper(true));
sThreadLocal是一个ThreadLocal对象,能够在一个线程中存储变量。能够看到,将一个Looper的实例放入了ThreadLocal,并且先判断了sThreadLocal.get是否为null,否则抛出异样。这也就阐明了Looper.prepare()办法不能被调用两次,同时也保障了一个线程中只有一个Looper实例。
(二)构造函数
下面的代码执行了Looper的构造函数,咱们看一下其代码:
private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mRun = true; mThread = Thread.currentThread();}
在构造函数中,创立了一个音讯队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。
(三)loop()办法
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 Printer logging = me.mLogging; if (logging != null) { logging.println(">>>>> Dispatching to " + msg.target + " " + msg.callback + ": " + msg.what); } msg.target.dispatchMessage(msg); 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.recycle(); }}
下面有几行代码是要害代码:
1. final Looper me = myLooper();
myLooper()办法间接返回了sThreadLocal存储的Looper实例,如果me为null则抛出异样,也就是说looper办法必须在prepare办法之后运行。
final Looper me = myLooper();if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); }
public static Looper myLooper() { return sThreadLocal.get();}
2. final MessageQueue queue = me.mQueue;
拿到该looper实例中的音讯队列mQueue。变量me是通过静态方法myLooper()取得的以后线程所绑定的Looper,me.mQueue是以后线程所关联的音讯队列。
3. for (;;)
进入了循环。咱们发现for循环没有设置循环终止的条件,所以这个for循环是个有限循环。
4. Message msg = queue.next(); // might block
取出一条音讯,如果没有音讯则阻塞。咱们通过音讯队列MessageQueue的next办法从音讯队列中取出一条音讯,如果此时音讯队列中有Message,那么next办法会立刻返回该Message,如果此时音讯队列中没有Message,那么next办法就会阻塞式地期待获取Message。
5. msg.target.dispatchMessage(msg);
msg的target属性是Handler,该代码的意思是让Message所关联的Handler通过dispatchMessage办法让Handler解决该Message。
6. msg.recycle();
开释音讯占据的资源。
(四)Looper次要作用
1.与以后线程绑定,保障一个线程只会有一个Looper实例,同时一个Looper实例也只有一个MessageQueue。
2.loop()办法,一直从MessageQueue中去取音讯,交给音讯Message的target属性,即Handler的dispatchMessage去解决。
二、Handler详解
(一)构造函数
应用Handler之前,咱们都是初始化一个实例,比方用于更新UI线程,咱们会在申明的时候间接初始化,或者在onCreate中初始化Handler实例。所以咱们首先看Handler的构造方法,看其如何与MessageQueue分割上的,它在子线程中发送的音讯(个别发送音讯都在非UI线程)怎么发送到MessageQueue中的。
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; }
下面有几行代码是要害代码:
1. public Handler(Callback callback, boolean async)
Handler.Callback是用来解决Message的一种伎俩,如果没有传递该参数,那么就应该重写Handler的handleMessage办法,也就是说为了使得Handler可能解决Message,咱们有两种方法:
(1)向Hanlder的构造函数传入一个Handler.Callback对象,并实现Handler.Callback的handleMessage办法。
(2)无需向Hanlder的构造函数传入Handler.Callback对象,然而须要重写Handler自身的handleMessage办法。
//在主线程中创立mHandler,所以主动绑定主线程 private Handler mHandler = new Handler(){ @Override public void handleMessage(@NonNull Message msg) { switch (msg.what){ case 1: System.out.println("handleMessage thread id " + Thread.currentThread().getId()); System.out.println("msg.arg1:" + msg.arg1); System.out.println("msg.arg2:" + msg.arg2); System.out.println("msg.obj:" + msg.obj.toString()); System.out.println("msg.setDate:" + msg.getData().get("QQ")); textview.setText("success"); break; } } }; private Handler mHandler2 = new Handler(new Handler.Callback() { @Override public boolean handleMessage(@NonNull Message msg) { switch (msg.what){ case 1: System.out.println("handleMessage thread id " + Thread.currentThread().getId()); System.out.println("msg.arg1:" + msg.arg1); System.out.println("msg.arg2:" + msg.arg2); System.out.println("msg.obj:" + msg.obj.toString()); System.out.println("msg.setDate:" + msg.getData().get("QQ")); textview.setText("success"); break; } return false; } });
也就是说无论哪种形式,咱们都得通过某种形式实现handleMessage办法,这点与Java中对Thread的设计有殊途同归之处。
在Java中,如果咱们想应用多线程,有两种方法:
(1)向Thread的构造函数传入一个Runnable对象,并实现Runnable的run办法。
(2)无需向Thread的构造函数传入Runnable对象,然而要重写Thread自身的run办法。
2. mLooper = Looper.myLooper();
首先通过Looper.myLooper()获取了以后线程保留的Looper实例。
3.mQueue = mLooper.mQueue;
而后再获取该Looper实例中保留的音讯队列MessageQueue,这样就保障了Handler的实例与Looper实例中MessageQueue关联上了。
(二)sendMessage()办法
public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); }
public final boolean sendEmptyMessageDelayed(int what, long delayMillis) { Message msg = Message.obtain(); msg.what = what; return sendMessageDelayed(msg, delayMillis); }
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
辗转反则最初调用了sendMessageAtTime,在此办法外部有间接获取MessageQueue,而后调用了enqueueMessage办法,咱们再来看看此办法:
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
下面有几行代码是要害代码:
1. msg.target = this;
该代码将Message的target绑定为以后的Handler。
2. queue.enqueueMessage;
变量queue示意的是Handler所绑定的音讯队列MessageQueue,通过调用queue.enqueueMessage(msg, uptimeMillis)咱们将Message放入到音讯队列中。
当初曾经很分明了Looper会调用prepare()和loop()办法,在以后执行的线程中保留一个Looper实例,这个实例会保留一个MessageQueue对象,而后以后线程进入一个有限循环中去,一直从MessageQueue中读取Handler发来的音讯。而后再回调创立该音讯的Handler中的dispathMessage办法。Handler的dispatchMessage的源码如下:
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); }
下面有几行代码是要害代码:
1. if (msg.callback != null) { handleCallback(msg); }
首先会判断msg.callback存不存在,msg.callback是Runnable类型,如果msg.callback存在,那么阐明该Message是通过执行Handler的postXXX系列办法将Message放入到音讯队列中的,这种状况下会执行handleCallback(msg), handleCallback源码如下:
private static void handleCallback(Message message) { message.callback.run();}
这样咱们咱们就分明地看到咱们执行了msg.callback的run办法,也就是执行了postXXX所传递的Runnable对象的run办法。
2. else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } }
如果咱们不是通过postXXX系列办法将Message放入到音讯队列中的,那么msg.callback就是null,代码持续往下执行,接着咱们会判断Handler的成员字段mCallback存不存在。mCallback是Hanlder.Callback类型的,咱们在下面的Handler构造函数提到过,在Handler的构造函数中咱们能够传递Hanlder.Callback类型的对象,该对象须要实现handleMessage办法,如果咱们在构造函数中传递了该Callback对象,那么咱们就会让Callback的handleMessage办法来解决Message。
3.handleMessage(msg);
如果咱们在构造函数中没有传入Callback类型的对象,那么mCallback就为null,那么咱们会调用Handler本身的hanldeMessage办法,该办法默认是个空办法,咱们须要本人是重写实现该办法。
综上,咱们能够看到Handler提供了三种路径解决Message,而且解决有前后优先级之分:首先尝试让postXXX中传递的Runnable执行,其次尝试让Handler构造函数中传入的Callback的handleMessage办法解决,最初才是让Handler本身的handleMessage办法解决Message。
让咱们看一下handleMessage(msg)
/** * Subclasses must implement this to receive messages. */ public void handleMessage(Message msg) { }
能够看到这是一个空办法,为什么呢,因为音讯的最终回调是由咱们管制的,咱们在创立handler的时候都是复写handleMessage办法,而后依据msg.what进行音讯解决。
private Handler mHandler = new Handler(){ public void handleMessage(android.os.Message msg){ switch (msg.what){ case value: break; default: break; } }; };
到此,sendMessage形式流程曾经解释结束,接下来看下post形式。
(三)post()办法
mHandler.post(new Runnable() { @Override public void run() { Log.e("TAG", Thread.currentThread().getName()); mTxt.setText("yoxi"); } });
而后run办法中能够写更新UI的代码,其实这个Runnable并没有创立什么线程,而是发送了一条音讯,上面看源码:
public final boolean post(Runnable r){ return sendMessageDelayed(getPostMessage(r), 0); }
private static Message getPostMessage(Runnable r) { Message m = Message.obtain(); m.callback = r; return m; }
能够看到,在getPostMessage中,失去了一个Message对象,而后将咱们创立的Runable对象作为callback属性,赋值给了此message。 注:产生一个Message对象,能够new也能够应用Message.obtain()办法;两者都能够,然而更倡议应用obtain办法,因为Message外部保护了一个Message池用于Message的复用,防止应用new从新分配内存。
public final boolean sendMessageDelayed(Message msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, SystemClock.uptimeMillis() + delayMillis); }
public boolean sendMessageAtTime(Message msg, long uptimeMillis) { MessageQueue queue = mQueue; if (queue == null) { RuntimeException e = new RuntimeException( this + " sendMessageAtTime() called with no mQueue"); Log.w("Looper", e.getMessage(), e); return false; } return enqueueMessage(queue, msg, uptimeMillis); }
最终和handler.sendMessage一样,调用了sendMessageAtTime,而后调用了enqueueMessage办法,给msg.target赋值为handler,最终退出MessagQueue。
public void dispatchMessage(Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
能够看到,这里msg的callback和target都有值,那么会执行msg.callback != null的判断,则执行handleCallback回调,也就是咱们的Runnable对象。
三、总结
到此,这个流程曾经解释结束,让咱们首先总结一下:
- 首先Looper.prepare()在本线程中保留一个Looper实例,而后该实例中保留一个MessageQueue对象;因为Looper.prepare()在一个线程中只能调用一次,所以MessageQueue在一个线程中只会存在一个。
- 在Lopper构造函数中,创立了一个音讯队列MessageQueue,并将其赋值给其成员字段mQueue,这样Looper也就与MessageQueue通过成员字段mQueue进行了关联。
- Looper.loop()会让以后线程进入一个有限循环,不端从MessageQueue的实例中读取音讯,而后回调msg.target.dispatchMessage(msg)办法。
- Handler的构造方法,会首先失去以后线程中保留的Looper实例,进而与Looper实例中的MessageQueue想关联。
- Handler的sendMessage办法,会给msg的target赋值为handler本身,而后退出MessageQueue中。
- 在结构Handler实例时,咱们会重写handleMessage办法,也就是msg.target.dispatchMessage(msg)最终调用的办法。
四、一图胜千言
咱们在本文探讨了Thread、MessageQueue、Looper以及Hanlder的之间的关系,咱们能够通过如下一张传送带的图来更形象的了解他们之间的关系。
在现实生活的生产生存中,存在着各种各样的传送带,传送带下面洒满了各种货物,传送带在发动机滚轮的带动下始终在向前滚动,一直有新的货物搁置在传送带的一端,货物在传送带的带动下送到另一端进行收集解决。
咱们能够把传送带上的货物看做是一个个的Message,而承载这些货物的传送带就是装载Message的音讯队列MessageQueue。传送带是靠发送机滚轮带动起来转动的,咱们能够把发送机滚轮看做是Looper,而发动机的转动是须要电源的,咱们能够把电源看做是线程Thread,所有的音讯循环的所有操作都是基于某个线程的。所有准备就绪,咱们只须要按下电源开关发动机就会转动起来,这个开关就是Looper的loop办法,当咱们按下开关的时候,咱们就相当于执行了Looper的loop办法,此时Looper就会驱动着音讯队列循环起来。
那Hanlder在传送带模型中相当于什么呢?咱们能够将Handler看做是放入货物以及取走货物的管道:货物从一端顺着管道划入传送带,货物又从另一端顺着管道划出传送带。咱们在传送带的一端放入货物的操作就相当于咱们调用了Handler的sendMessageXXX、sendEmptyMessageXXX或postXXX办法,这就把Message对象放入到了音讯队列MessageQueue中了。当货物从传送带的另一端顺着管道划出时,咱们就相当于调用了Hanlder的dispatchMessage办法,最终执行handleMessage办法,在该办法中咱们实现对Message的解决。
相干视频举荐:
【Android handle面试】MessageQueue如何放弃各线程增加音讯的线程平安?
【Android handle面试】子线程中的looper在无音讯的时候应该怎么解决缩小性能问题?
【Android handle面试】一个线程有几个hander和几个looper?
本文转自 https://juejin.cn/post/6844904195653386248,如有侵权,请分割删除。