Handler消息机制几乎是现在android面试中出现概率很高的一个问题,当然不建议大家去把这个过程背下来,有经验的面试官,很可能随便一问就问出来,所以连接handler机制的原理是非常重要的,这篇文章从handler的源码分析开始,顺便着手写一个handler简易框架,也顺便复习下,因为公司搬迁,我也要准备出去找工作了。(强调一句,不要背面试题,一定要知道原理,这样你才能走的更远)
我们先看一下经典的handler的使用方法,在子线程发送消息到主线程,在主线程更新UI。
//uiHandler在主线程中创建,所以自动绑定主线程 private Handler uiHandler = new Handler(){ @Override public void handleMessage(Message msg) { switch (msg.what){ case 1: System.out.println("handleMessage thread id " + Thread.currentThread().getId()); break; } } };发送消息
new Thread(new Runnable() { @Override public void run() { // 发送 Message1 Message message1 = new Message(); uiHandler.sendMessageDelayed(message1, 1000); // 发送 Message2 Message message2 = new Message(); uiHandler.sendMessageDelayed(message2,1000); } }).start();
先过一遍简单的流程。从发送消息开始
uiHandler.sendEmptyMessage(1);进去
public final boolean sendEmptyMessage(int what) { return sendEmptyMessageDelayed(what, 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); }
enqueueMessage(queue, msg, uptimeMillis);)
继续走
private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }这里我们就要进入了messageQueue里边,我们找到enqueueMessage方法
boolean enqueueMessage(Message msg, long when) { if (msg.target == null) { //判断target 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; //mMessages此时为空,因为之前是没有被赋值的 boolean needWake; if (p == null || when == 0 || when < p.when) //第一次添加消息进入队列,此时不会走下面的else里边方法 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; }
上面是真个的代码,我们一行行的分析
当发送第一条消息的时候,是把消息添加到队列中,到了这里我们先不管了,此时在消息队列中的消息是如下。
此时我们发送第二条消息,此时仍然走到了上面将消息添加到消息队列中。
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; }此时走的是这里里面,因为上一次的p不等于null,这段代码主要是利用链表结构,每一条消息指向下一条消息,便于快速的删除,这上面的when主要是发送消息的时间,是否延迟,这里是计算数据插入的地方,根据时间来判断。
此时如果继续发送消息,handler.sendMessage(),其实是将我们的message发送的消息,采用链表的方式,按照时间的方式(这个时间是根据延时还有系统时间来判断的),添加到了messageQueue中。
这里有个经常问的面试题:
为什么MessageQueue中的消息队列采用链表的模式去存储消息?为什么不用数组?
回答这个问题的时候,我们首先要回答链表跟数组的优缺点。
比如说,链表查询比较慢,但是增删比较快,数组查询快,增删慢,在android系统中使用消息机制,为了让消息能够按时发送,必须要保证消息能够快速的到达,由于我们的消息是不断的去轮寻取出的,是按照时间顺序一条条的读取,所以对于删除或者添加消息速度要求比较高,刚好链表的数据结构符合我们的要求,使用数组这种结构会导致所有的数据跟着改变,时效性太慢。这个语言组织起来还是需要自己去处理的
接下来,我从另外一条路开始分析,我们看下handler的初始化。
new Handler()
点进去
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; }在handler构造方法中获取了一个
mLooper对象,然后根据mLooper去获取mQueue对象。
看下
mLooper = Looper.myLooper();继续
public static @Nullable Looper myLooper() { return sThreadLocal.get(); }这里我们用一个sThreadLocal对象去get一个什么东西,点进去看下
public T get() { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) { ThreadLocalMap.Entry e = map.getEntry(this); if (e != null) { @SuppressWarnings("unchecked") T result = (T)e.value; return result; } } return setInitialValue(); }1.上面获取当前线程。
2.根据线程去获取ThreadLocalMap
ThreadLocalMap getMap(Thread t) {
return t.threadLocals;
}
看下ThreadLocalMap是什么
/** * ThreadLocalMap is a customized hash map suitable only for * maintaining thread local values. No operations are exported * outside of the ThreadLocal class. The class is package private to * allow declaration of fields in class Thread. To help deal with * very large and long-lived usages, the hash table entries use * WeakReferences for keys. However, since reference queues are not * used, stale entries are guaranteed to be removed only when * the table starts running out of space. */ static class ThreadLocalMap {。。。}百度翻译:
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;看下threadLocals
/* ThreadLocal values pertaining to this thread. This map is maintained * by the ThreadLocal class. */ ThreadLocal.ThreadLocalMap threadLocals = null;这个线程ThreadLocal值有关。这个集合是由ThreadLocal类保持
上面的翻译看的还不是很明白这个东西到底是什么,实际上翻译起来就是线程本地集合,这个东西到底有设么用呢?
我们必须要从整个的ThreadLocal看起,我们看下set的方法
public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); }
这里看出来,在这个ThreadLocalMap集合中存储的是ThreadLocal与value(其实是looper)
ThreadLocal提供了set和get访问器用来访问与当前线程相关联的线程局部变量。
可以从ThreadLocal的get函数中看出来,其中getmap函数是用t作为参数,这里t就是当前执行的线程。
从而得知,get函数就是从当前线程的threadlocalmap中取出当前线程对应的变量的副本【注意,变量是保存在线程中的,而不是保存在ThreadLocal变量中】。当前线程中,有一个变量引用名字是threadLocals,这个引用是在ThreadLocal类中createmap函数内初始化的。每个线程都有一个这样的threadLocals引用的ThreadLocalMap,以ThreadLocal和ThreadLocal对象声明的变量类型作为参数。这样,我们所使用的ThreadLocal变量的实际数据,通过get函数取值的时候,就是通过取出Thread中threadLocals引用的map,然后从这个map中根据当前threadLocal作为参数,取出数据。
其实也就是说,我们使用ThreadLocal保证一个线程的数据,还有对应的Looper,当我们需要使用这个线程的时候,可以直接获取到这个线程对应的数据。这样能够保证一个线程数据一一对应,往前看,因为一个线程的数据对应一个Looper,因为我们是在主线程中创建的handler,所以我们在handler中获取的这个looper也是我们在主线程中存储的Looper,因为mQueue是Looper中的一个引用,在Loop方法里面
public static void loop() {;;;}
不断的去轮寻队列里边的消息,使用
msg.target.dispatchMessage(msg);去分发消息,我们创建handler重写的的那个handlerMessage方法就能收到消息了
===================================分割线=============================================
上面就是简单的分析handler的大概过程,接下来,我们自己去试下简单的handler机制
首先说下需求:
我们要自己实现handler,使用自己的handler能够处理消息机制,并且在子线程不能更新UI。
我们使用HandlerText来作为程序的入口,代码只是模拟这部分功能。
需要达到如下效果:
上面的代码能够看到,在子线程更新UI会报错,但是在主线程中更新UI是可以的,如果想要在子线程中更新UI就必须使用android提供的消息机制,下面代码是使用我们自定义的消息机制来实现在子线程中更新UI。
代码,就不做过多的注释,我们只是截取了部分功能,比如实际消息机制中消息的插入是需要计算时间的,在我们的demo中是没有的,在很多地方需要进行判断,在代码中我们也是省略了,下面的代码只是为你了解handler机制提供了参考。
程序入口:
* @项目名: Zhifubao * @包名: com.huhai.zhifubao * @文件名: HandlerText * @创建者: huhai * @创建时间: 2017/11/10 14:59 * @描述: 详细代码可见ActivityThread */ import android.os.Looper; import android.os.Trace; import android.util.Log; import android.util.LogPrinter; public class HandlerText { public static void main(String[] args){ MyLooper.prepare(); MyActivityThread thread = new MyActivityThread(); thread.attach(false); MyLooper.loop(); } }
MyActivityThread
* @项目名: Zhifubao * @包名: com.huhai.zhifubao.HandlerText * @文件名: MyActivityThread * @创建者: huhai * @创建时间: 2017/11/10 15:05 * @描述: TODO */ import java.util.logging.Handler; import static android.R.id.message; public class MyActivityThread { public void attach(boolean b) { MyActivity myActivity=new MyActivity(); myActivity.onCreat(); } }
MyActivity:(模拟了实际真是的activity,写了一个oncreat方法,一个onResume方法)
* @项目名: Zhifubao * @包名: com.huhai.zhifubao.HandlerText * @文件名: MyActivity * @创建者: huhai * @创建时间: 2017/11/10 15:05 * @描述: TODO */ import android.util.Log; import android.widget.TextView; public class MyActivity { private static final String TAG = "MyActivity"; private MyTextView mytextview=new MyTextView(); private H mH=new H(); public void onCreat(){ //主线程更新UI System.out.println("执行onCreat"); mytextview.setText(); new Thread(new Runnable() { @Override public void run() { try { Thread.sleep(5000); //子线程更新UI // mytextview.setText(); MyMessage message=new MyMessage(); message.obj="消息"; mH.sendMessage(message); } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); } public void onResume(){ System.out.println("onResume"); } private class H extends MyHandler { public void handlerMessage(MyMessage message){ String data= (String) message.obj; System.out.println("handler中更新了"+data); mytextview.setText(); } } }MyTextView:
import android.os.Looper; import static android.os.Looper.getMainLooper; public class MyTextView { private static final String TAG = "MyTextView"; private Thread mThread; public MyTextView() { mThread = Thread.currentThread(); } public void setText(){ //先检查是否在主线程 try { checkThread(); System.out.println("更新了UI"); } catch (Exception e) { e.printStackTrace(); } } private void checkThread() throws Exception{ if (mThread!=Thread.currentThread()){ throw new Exception("Only the original thread that created a view hierarchy can touch its views."); } } }
MyLooper:
* @项目名: Zhifubao * @包名: com.huhai.zhifubao.HandlerText * @文件名: MyLooper * @创建者: huhai * @创建时间: 2017/11/10 15:04 * @描述: TODO */ public class MyLooper { static final ThreadLocalMyHandler:sThreadLocal = new ThreadLocal (); public MyMessageQueue mQueue; public MyLooper() { mQueue = new MyMessageQueue(); } public static void prepare() { if (sThreadLocal.get() != null) { throw new RuntimeException("Only one Looper may be created per thread"); } sThreadLocal.set(new MyLooper()); } public static void loop() { MyLooper myLooper=getLooper(); for (;;){ MyMessageQueue queue= myLooper.mQueue; MyMessage message= queue.next(); if (message==null){ return; } message.target.handlerMessage(message); } } private static MyLooper getLooper() { return sThreadLocal.get(); } public static MyLooper myLooper() { return sThreadLocal.get(); } }
import android.os.Looper; import android.os.Message; import android.os.MessageQueue; import android.os.SystemClock; import android.util.Log; import static android.R.attr.y; public class MyHandler { public MyMessageQueue mQueue; public MyHandler() { MyLooper looper=MyLooper.myLooper(); mQueue=looper.mQueue; } public void handlerMessage(MyMessage message){ }; public void sendMessage(MyMessage message) { sendMessageDelayed(message,0); } public final boolean sendMessageDelayed(MyMessage msg, long delayMillis) { if (delayMillis < 0) { delayMillis = 0; } return sendMessageAtTime(msg, delayMillis); } public boolean sendMessageAtTime(MyMessage msg, long uptimeMillis) { MyMessageQueue 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); } private boolean enqueueMessage(MyMessageQueue queue, MyMessage msg, long uptimeMillis) { msg.target = this; return queue.enqueueMessage(msg, uptimeMillis); } }
MyMessageQueue:(在消息队列里边消息的插入及后面的取出,都是直接拿的实际的源码,截取了关键的部分,省略了)
* @项目名: Zhifubao * @包名: com.huhai.zhifubao.HandlerText * @文件名: MyMessageQueue * @创建者: huhai * @创建时间: 2017/11/10 16:34 * @描述: TODO */ import android.os.Binder; import android.os.Message; import android.os.SystemClock; import android.util.Log; import static android.content.ContentValues.TAG; public class MyMessageQueue { private MyMessage mMessages; boolean enqueueMessage(MyMessage msg, long when) { synchronized (this) { msg.when = when; MyMessage 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; } 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. MyMessage prev; for (; ; ) { prev = p; p = p.next; if (p == null || when < p.when) { break; } } msg.next = p; // invariant: p == prev.next prev.next = msg; } return true; } } public MyMessage next() { // Return here if the message loop has already quit and been disposed. // This can happen if the application tries to restart a looper after quit // which is not supported. int pendingIdleHandlerCount = -1; // -1 only during first iteration int nextPollTimeoutMillis = 0; for (;;) { if (nextPollTimeoutMillis != 0) { Binder.flushPendingCommands(); } synchronized (this) { // Try to retrieve the next message. Return if found. final long now = 0; MyMessage prevMsg = null; MyMessage msg = mMessages; if (msg != null && msg.target == null) { // Stalled by a barrier. Find the next asynchronous message in the queue. do { prevMsg = msg; msg = msg.next; } while (msg != null); } if (msg != null) { if (now < msg.when) { // Next message is not ready. Set a timeout to wake up when it is ready. nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // Got a message. if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; return msg; } } else { // No more messages. nextPollTimeoutMillis = -1; } } nextPollTimeoutMillis = 0; } }
MyMessage:
* @项目名: Zhifubao * @包名: com.huhai.zhifubao.HandlerText * @文件名: MyMessage * @创建者: huhai * @创建时间: 2017/11/10 15:04 * @描述: TODO */ public class MyMessage { public Object obj; public MyHandler target; public long when; public MyMessage next; }大概消息机制就这么一个流程,大家想要了解其过程,把代码复制进去,跑一边,走一遍,消息机制其实就差不多了。