Handler是什么
-
先来看官方文档对Handler的描述
A Handler allows you to send and process Message and Runnable objects associated with a thread's 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可以让我们发送和处理和线程的
MessageQueue
关联的Message
和Runnable
对象。每个Handler实例都与一个线程和该线程的消息队列相关联。当创建一个Handler时,Handler就会被绑定到创建它的线程/消息队列。创建之后,Handler就可以发送消息(messages) 和任务(runnables)到该消息队列,并且在消息出队的时候执行它们。所以Handler其实是Android为我们提供的一套消息机制。
Handler作用
-
同样看看官方文档中的描述
There are two main uses for a Handler: (1) to schedule messages and runnables to be executed at some point in the future; and (2) to enqueue an action to be performed on a different thread than your own.
可以看到Handler主要有两个作用:
- 让消息(messages) 和任务(runnables)在将来某个时刻执行
- 将要在不同于自己线程上执行的操作(messages和runnables)插入队列
第一个作用很好理解,也就是说可以通过Handler延时执行一些操作;第二个怎么来理解呢?前面有提过,每个Handler都会和一个线程/消息队列绑定起来。那么通过该Handler发送消息和任务就会到该线程的消息队列中并且执行。所以可以通过Handler将一些操作放到其它线程中去执行。举个例子,Android是不允许在子线程中更新UI的,所以在子线程中可以通过Handler将更新UI的操作放到主线程/UI线程中执行
-
下面这一段描述其实就为第二个作用进行了说明,并且我们知道了在主线程中会自动维护一个消息队列用于维护
When a process is created for your application, its main thread is dedicated to running a message queue that takes care of managing the top-level application objects (activities, broadcast receivers, etc) and any windows they create. You can create your own threads, and communicate back with the main application thread through a Handler.
为应用程序创建进程的时候,主线程也就是ActivityThread会专门启动一个消息队列对于管理顶级的应用程序对象,比如Activity、BroadcastReceiver等等以及它们创建的任何窗口。可以创建自己的线程,并通过Handler与主应用程序线程进行通信。
Handler的基本使用
-
Handler的最简单的使用方式如下:
- 创建Handler
//创建Handler private Handler mHandler = new Handler() { @Override public void handleMessage(Message msg) { switch (msg.what){ //do sth } } };
- 发送empty消息
int what = 1; mHandler.sendEmptyMessage(what);
- 携带对象
class Person { String name; int age; public Person(String name, int age) { this.name = name; this.age = age; } } Message message =mHandler.obtainMessage(); message.what = 1; message.obj = new Person("ygg",25); mHandler.sendMessage(message);
- 发送延时消息
mHandler.sendMessageDelayed(message,1000);
- 发送runnable
Runnable runnable = new Runnable() { @Override public void run() { //do sth } }; mHandler.postDelayed(runnable,1000);
- 移除message和runnable
int what = 1; mHandler.removeMessages(what); mHandler.removeCallbacks(runnable); //移除所有消息和任务 mHandler.removeCallbacksAndMessages(null);
- 拦截消息
private Handler mHandler = new Handler(new Handler.Callback() { @Override public boolean handleMessage(Message msg) { if (msg.what == 1){ //do sth return true; } return false; } }) { @Override public void handleMessage(Message msg) { switch (msg.what){ //do sth } } };
在创建的时候可以传入一个
Callback
对消息进行一个拦截,Callback
中有一个具有返回值的handleMessage
方法,返回true
表示需要拦截该消息,拦截后下面返回值为void
的handleMessage
方法不会再被调用- 指定Looper
private Handler mHandler = new Handler(Looper.getMainLooper());
关于
Looper
,会在后面进行讲解,在这我们先知道可以为Handler 指定Looper
,这也是在非UI线程中和UI线程交互的实现方式,将Looper
指定为UI线程的Looper
,发送的消息就会到UI线程中处理- 其它使用方式
在子线程中更新UI,我们常常使用runOnUiThread(new Runnable() { @Override public void run() { } }); //Activity.class public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }
runOnUiThread
,可以看到实际上它内部也是使用Handler实现的,如果不是在UI 线程就交给Handler处理,否则直接执行
我们常在private View view; view.post(new Runnable() { @Override public void run() { } }); //View.class public boolean post(Runnable action) { final AttachInfo attachInfo = mAttachInfo; if (attachInfo != null) { return attachInfo.mHandler.post(action); } // Postpone the runnable until we know on which thread it needs to run. // Assume that the runnable will be successfully placed after attach. getRunQueue().post(action); return true; }
Activity#onCreate
中用View.post
方式,在里面获取控件的宽高等操作,内部实际上也是利用Handler实现的,需要注意在Activity#onCreate
时这里的AttachInfo是为空的,所以走的是下面的getRunQueue().post(action);
,它会先把Runnable用数组存起来,等到第一次测量完成后才执行,所以我们可以用该方法获取到宽高。关于View.post
,想了解更多信息的话可以关注博主,之后会写一篇文章进行分析。
-
需要注意的点
- 对于Message的创建使用
Handler.obtainMessage()
而不是new Message()
先来看看obtainMessage
方法
//Handler.class public final Message obtainMessage() { return Message.obtain(this); } //Message .class public static Message obtain(Handler h) { Message m = obtain(); m.target = h; return m; } /** * Return a new Message instance from the global pool. Allows us to * avoid allocating new objects in many cases. */ public static Message obtain() { synchronized (sPoolSync) { if (sPool != null) { Message m = sPool; sPool = m.next; m.next = null; m.flags = 0; // clear in-use flag sPoolSize--; return m; } } return new Message(); }
可以看到
obtainMessage
方法会从一个公共缓存池返回一个Message实例,避免频繁的分配对象。所以使用该方法可以对Message对象进行复用,从而减少Message对象大量的创建。- Handler的创建应该改为静态内部类,在Activity关闭的时候应该调用``清空消息和任务,如果需要在Handler内部使用Activity,采用弱引用方式,避免内存泄漏
private Handler mHandler = new MyHandler(this); static class MyHandler extends Handler{ WeakReference<Activity> mWeakReference; public MyHandler(Activity activity) { mWeakReference = new WeakReference<Activity>(activity); } @Override public void handleMessage(Message msg){ final Activity activity = mWeakReference.get(); if(activity!=null){ // do sth } } } @Override protected void onDestroy() { super.onDestroy(); mHandler.removeCallbacksAndMessages(null); }
在Java 中,非静态的内部类和匿名内部类都会隐式地持有其外部类的引用,静态的内部类不会持有外部类的引用。如果发送了一个延时Message,此时MessageQueue就会持有一个Message对象。再来看发送消息是如何处理
//Handler.class public final boolean sendMessage(Message msg) { return sendMessageDelayed(msg, 0); } 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); } private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
可以看到最终会调用
enqueueMessage
方法入队,此时MessageQueue 便会持有一个Message对象,关注这行代码:msg.target = this;
,将Handler对象赋值给Message的target变量,所以Message会持有Handler对象,如果Handler是非静态内部类的话,它又持有Activity,最终就会形成MessageQueue ->Message->Handler->Activity
这样一条引用链,当Activity退出时还有消息没有被执行就会导致Activity不会被回收,最终导致Activity泄漏。- 在子线程中创建Handler时必须指定
Looper
或者调用Looper.prepare()
和Looper.loop()
为线程创建一个Looper并启动Looper
new Thread(new Runnable() { //指定Looper private Handler mHandler = new Handler(Looper.getMainLooper()); @Override public void run() { //先为线程创建一个Looper,并启动 Looper.prepare(); Looper.loop(); Handler mHandler1 = new Handler(); } }).start();
从下面的源码中可以看到在子线程中创建Handler,会对Looper进行一个检测,如果为空的话,会报错。至于为什么需要调用
Looper.prepare()
,在了解了Handler、Looper、MessageQueue、Message之间的关系之后就可以解释了。public Handler() { this(null, false); } public Handler(Callback callback, boolean async) { //建议创建成STATIC的 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()); } } //检测有没有设置Looper 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; }
- 对于Message的创建使用
Handler、Looper、MessageQueue、Message之间的关系
- Handler与Message之间的关系
首先Message
是一个Parcelable对象,作为消息的载体。主要注意target
、callback
、next
。
1.public final class Message implements Parcelable { public int what; public int arg1; public int arg2; public Object obj; Handler target; Runnable callback; Message next; //是否是异步消息 public boolean isAsynchronous() { return (flags & FLAG_ASYNCHRONOUS) != 0; } //设置消息是否是异步的,异步则意味着它不受 Looper同步障碍影响。 public void setAsynchronous(boolean async) { if (async) { flags |= FLAG_ASYNCHRONOUS; } else { flags &= ~FLAG_ASYNCHRONOUS; } } }
Handler target
注意到它是Handler类型,还记得前面Handler是如何发送一个消息的吗?最终会调用
关注private boolean enqueueMessage(MessageQueue queue, Message msg, long uptimeMillis) { msg.target = this; if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
msg.target = this;
,也就是说当Handler发送一个Message
的时候,Message
会将发送它的Handler记录下来。这也就是它们之间的关系:Message会持有发送它的Handler对象。最终是为了在该Handler的handleMessage
处理该消息
2.Runnable callback
callback是一个Runnable
对象,还记得Handler可以直接发送一个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; }
Runnable
对象包装成一个Message
,而Message
的callback就是发送的Runnable
对象。先记住这一点,在消息分发的时候会用到。-
Message next
next也是一个Message
类型,相信大家都有见到过这种写法——链表。实际上MessageQueue
是一个单链表形式的队列
-
-
ThreadLocal简介
为了便于理解线程与Looper
以及MessageQueue
之间的关系,在这里对ThreadLocal
做一个简要的介绍。ThreadLocal
可以提供一个线程的局部变量。每一个线程通过ThreadLocal#set
保存和ThreadLocal#get
访问的数据都有它们自己的副本。也就是说通过ThreadLocal
保存的数据是线程隔离的。举个例子static final ThreadLocal<Integer> sThreadLocal = new ThreadLocal<>(); new Thread(new Runnable() { @Override public void run() { sThreadLocal.set(1); try { Thread.sleep(2000); sThreadLocal.get();//1 } catch (InterruptedException e) { e.printStackTrace(); } } }).start(); new Thread(new Runnable() { @Override public void run() { sThreadLocal.set(2); new Thread(new Runnable() { @Override public void run() { sThreadLocal.set(3); sThreadLocal.get();//3 } }).start(); try { Thread.sleep(2000); sThreadLocal.get(); //2 } catch (InterruptedException e) { e.printStackTrace(); } } }).start();
事例非常简单。在每个线程中获取的都会是该线程本身保存的值。
-
接下来看一下
ThreadLocal#set
和ThreadLocal#get
方法,帮助我们理解这个过程//ThreadLocal.class public void set(T value) { Thread t = Thread.currentThread(); ThreadLocalMap map = getMap(t); if (map != null) map.set(this, value); else createMap(t, value); } void createMap(Thread t, T firstValue) { t.threadLocals = new ThreadLocalMap(this, firstValue); } 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(); } ThreadLocalMap getMap(Thread t) { return t.threadLocals; }
ThreadLocalMap
是ThreadLocal
的一个内部类,在这里我们不深入讲它是如何插入和获取值的。就当它是一个容器。比如HashMap之类的,可以用来保存插入的内容。我们可以看到整个流程其实非常的清晰- 调用
ThreadLocal.set
实际上会为当前线程创建一个容器(ThreadLocalMap),也就是t.threadLocals
,最终保存的内容是放在这个容器当中的 - 调用
ThreadLocal.get
实际上是从当前线程取出之前创建的容器(ThreadLocalMap),然后从这个容器中获取对应的内容。 - 也就是说实际上每一个线程都会有一个自己的容器(ThreadLocalMap),那么保存在里面的内容肯定是它自己独享的,和其它线程之间不会互相干扰。需要注意在线程退出(exit)的时候会自动释放该容器。
- 调用
-
Looper、MessageQueue之间的关系
- MessageQueue 简介
public final class MessageQueue { //Native消息机制中MessageQueue的地址 private long mPtr; // used by native code //处理的消息 Message mMessages; //可以添加一个IdleHandler,用于在每次空闲(没有消息处理)时回调,可以利用该特性进行一些优化处理一些需要在主线程执行又比较耗时操作,在系统空闲时才执行 private final ArrayList<IdleHandler> mIdleHandlers = new ArrayList<IdleHandler>(); private IdleHandler[] mPendingIdleHandlers; // Indicates whether next() is blocked waiting in pollOnce() with a non-zero timeout. //用来判断 next()方法是否因为调用pollOnce() 在阻塞 private boolean mBlocked; private native static long nativeInit(); //阻塞调用该方法的线程,类似于Object.wait,不过会在阻塞timeoutMillis之后唤醒线程 private native void nativePollOnce(long ptr, int timeoutMillis); //唤醒线程以继续执行 private native static void nativeWake(long ptr); MessageQueue(boolean quitAllowed) { //是否可以停止 mQuitAllowed = quitAllowed; mPtr = nativeInit(); } //消息入队 boolean enqueueMessage(Message msg, long when) { synchronized (this) { msg.when = when; Message p = mMessages; boolean needWake; if (p == null || when == 0 || when < p.when) { // 新消息入队,队列中没有消息或者新消息不需要延时,或者新消息的执行时间比当前队列中最早执行的消息执行时间还要早,插入头部 msg.next = p; mMessages = msg; //如果当前正在阻塞即mBlocked == true,则唤醒线程 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 next() { final long ptr = mPtr; if (ptr == 0) { return null; } ... int nextPollTimeoutMillis = 0; for (;;) { //阻塞nextPollTimeoutMillis时间,第一次是0,所以会往下执行 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { //获取系统开机到现在的时间 final long now = SystemClock.uptimeMillis(); Message prevMsg = null; Message msg = mMessages; //msg.target == null表示是屏障信息,则需要找到队列中下一个异步消息执行 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 && !msg.isAsynchronous()); } 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. // 不需要等待或者等待时间到了,直接返回该消息 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; if (DEBUG) Log.v(TAG, "Returning message: " + msg); msg.markInUse(); return msg; } } else { // No more messages. //没有更多的消息要处理,-1表示一直阻塞 nextPollTimeoutMillis = -1; } // Process the quit message now that all pending messages have been handled. //结束直接返回空 if (mQuitting) { dispose(); return null; } // If first time idle, then get the number of idlers to run. // Idle handles only run if the queue is empty or if the first message // in the queue (possibly a barrier) is due to be handled in the future. //没有消息的第一时间,获取IdleHandler的个数 if (pendingIdleHandlerCount < 0 && (mMessages == null || now < mMessages.when)) { pendingIdleHandlerCount = mIdleHandlers.size(); } //没有设置IdleHandler,继续阻塞 if (pendingIdleHandlerCount <= 0) // No idle handlers to run. Loop and wait some more. mBlocked = true; continue; } if (mPendingIdleHandlers == null) { mPendingIdleHandlers = new IdleHandler[Math.max(pendingIdleHandlerCount, 4)]; } mPendingIdleHandlers = mIdleHandlers.toArray(mPendingIdleHandlers); } // Run the idle handlers. // We only ever reach this code block during the first iteration. for (int i = 0; i < pendingIdleHandlerCount; i++) { final IdleHandler idler = mPendingIdleHandlers[i]; mPendingIdleHandlers[i] = null; // release the reference to the handler //执行IdleHandler boolean keep = false; try { keep = idler.queueIdle(); } catch (Throwable t) { Log.wtf(TAG, "IdleHandler threw exception", t); } if (!keep) { synchronized (this) { mIdleHandlers.remove(idler); } } } //重置个数,只执行一次 // Reset the idle handler count to 0 so we do not run them again. pendingIdleHandlerCount = 0; // While calling an idle handler, a new message could have been delivered // so go back and look again for a pending message without waiting. //执行完IdleHandler后,看看有没有消息需要执行 nextPollTimeoutMillis = 0; } } //向Looper的消息队列发布同步障碍。 //消息机制还是在执行,但是遇到屏障时,队列中的后续同步消息将被暂停(阻止执行),直到通过调用removeSyncBarrier并指定标识同步屏障的令牌来释放屏障 public int postSyncBarrier() { return postSyncBarrier(SystemClock.uptimeMillis()); } }
MessageQueue
是一个单项链表实现的队列。可以看到MessageQueue
在消息入队的时候,会为Message
选择一个合适的位置插入,延时时间越小,就排在越前面,当下一次被唤醒时。排在前面的就会先被取出执行。
需要注意消息出队是会阻塞的。获取下一个消息,如果下一个消息是可以执行的,也就是now >=msg.when,就直接返回,如果是延时消息,则阻塞起来,到达延时时间就会被唤醒,从而继续取出消息执行。因为入队时经过排序,如果队头消息都需要等待,后面的消息肯定也是需要等待的。这也是延时消息的实现原理。- 解决前面的问题:为什么需要在子线程中调用
Looper.prepare()
? 先来看看该方法做了什么
//Looper.class 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)); } public static @Nullable Looper myLooper() { return sThreadLocal.get(); }
我们可以看到有个抛出异常,也就是说一个线程只能创建一个Looper,也就是只能调用一次
Looper.prepare()
。该方法实际上就是创建了一个Looper
对象,并且保存到ThreadLocal
中。利用ThreadLocal
,实现Looper
的线程隔离。默认情况下,非UI线程的Looper
为空,所以在子线程中创建Handler时检测抛出异常,需要先调用Looper.prepare()
方法private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); }
在创建
Looper
的时候就会创建一个MessageQueue
与其绑定起来。- Looper和MessageQueue的关联
/** * Run the message queue in this thread. Be sure to call * {@link #quit()} to end the loop. */ public static void loop() { //返回sThreadLocal存储的Looper实例 final Looper me = myLooper(); //loop方法必须在prepare方法之后运行 if (me == null) { throw new RuntimeException("No Looper; Looper.prepare() wasn't called on this thread."); } //获取Looper实例中的消息队列mQueue final MessageQueue queue = me.mQueue; ... for (;;) { //next()方法用于取出消息队列中的消息,如果没有符合的消息或者没有消息,则线程阻塞 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.target.dispatchMessage(msg); //消息执行完后打印时间 if (logging != null) { logging.println("<<<<< Finished to " + msg.target + " " + msg.callback); } msg.recycleUnchecked(); } }
可以看到
loop()
方法就是开启一个循环,一直从消息队列中取出消息,并交给Handler#dispatchMessage
方法进行分发处理。在这额外说一下,在消息分发前后都调用了Looper 的Printer 打印了时间。而BlockCanary正是利用这一点监听卡顿的。下面看看Handler是如何分发消息的 -
Handler分发消息
//Handler.class public void dispatchMessage(Message msg) { if (msg.callback != null) { //如果msg.callback不为空,实际上就是调用的是post方法,传入了一个Runnable对象,直接执行 handleCallback(msg); } else { if (mCallback != null) { //如果创建Handler的时候传入了Callback ,则先调用Callback#handleMessage方法先处理消息 if (mCallback.handleMessage(msg)) { //如果Callback#handleMessage方法返回true,则拦截 return; } } //执行handleMessage方法 handleMessage(msg); } } private static void handleCallback(Message message) { //执行Runnable的run方法 message.callback.run(); }
Handler分发消息比较简单,先判断Message 的callback是不是为空,也就是判断是不是调用的post方法,注意post方法传入的Runnable也是包装成Message然后入队的,所以消息延时一样适用。如果是Runnable,直接执行。否则先判断Handler的Callback是否为空,先执行
Handler#Callback#handleMessage
方法,所以我们可以通过传入一个Callback对消息进行拦截,最后才执行Handler#handleMessage
方法。
Handler消息机制流程
-
Looper.prepare()
为每个线程创建了一个Looper,每个Looper会创建一个MessageQueue,用于存放Message -
Looper.loop()
开启一个循环,不断的从MessageQueue中取出Message并且交由Handler进行分发处理 -
new Handler()
会从线程中取出与线程绑定的Looper,再从Looper中拿到MessageQueue -
Handler.sendXXX()
一系列发送消息的方法最终会通过enqueueMessage
方法将Message插入MessageQueue中,此时Message的target属性绑定为发送它的Handler,最后消息出队时通过Message.target.dispatchMessage
方法将Message交回给发送该Message的Handler进行处理,最终回到handleMessage.handleMessage
方法
为什么非UI线程不能直接更新UI,只能通过Handler机制更新UI
-
为什么非UI线程直接更新UI会报错
//ViewRootImpl.class public ViewRootImpl(Context context, Display display) { ... mThread = Thread.currentThread(); } @Override public void invalidateChild(View child, Rect dirty) { invalidateChildInParent(null, dirty); } @Override public ViewParent invalidateChildInParent(int[] location, Rect dirty) { checkThread(); ... } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
View的绘制都是通过
ViewRootImpl
操作的,关于View的绘制流程,可以关注博主,后续也会写一篇文章。ViewRootImpl
是在UI线程创建的。我们可以看到每次更新UI都会检测是不是主线程/UI线程。当然在Activity#onCreate
之前直接在子线程/非UI线程直接更新UI也是可以的,因为此时ViewRootImpl并没有被创建,它是在Activity#onResume
中创建的。不过正常来说在这个时候更新UI并没有什么实际的作用。 -
为什么只能通过Handler机制更新UI
- 是因为考虑到多线程并发更新UI的问题。不知道大家有没有想过这样一个问题:如果非UI线程能够直接更新UI,那么在有多个非UI线程同时更新UI会发生什么问题?
- 答案是会导致界面UI更新错乱。我们都知道,多线程的情况下会存在一个并发的问题,在这种情况下更新UI就可能造成预期之外的结果。那么怎么处理这个问题呢?
- 处理多线程问题,一般我们会考虑加锁,如果对更新UI的操作加锁,会带来性能上的问题。更新UI的操作在Android中是非常频繁的操作。如果加锁了,线程对锁的获取和释放、线程的切换都会带来资源的消耗,而对于更新UI的操作,一旦多花了点时间,都有可能导致界面的卡顿等不好的结果。Android 16.6ms内要完成一次刷新操作想必都有听说过。所以加锁是不适合的。那么应该如何处理呢?
- 答案就是通过Handler机制更新UI,非UI线程将所有更新UI的操作都放到主线程/UI线程中,这些更新UI的操作就会被放到队列中一个接一个被执行,即解决了多线程并发的问题,也避免了加锁带来的性能上的消耗。