博客主页java
在分析Handler源码以前,咱们先来看下下面这条异常android
android.view.ViewRootImpl$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. at android.view.ViewRootImpl.checkThread(ViewRootImpl.java:6094) at android.view.ViewRootImpl.requestLayout(ViewRootImpl.java:824) at android.view.View.requestLayout(View.java:16431)
做为Android开发人员,这样异常的信息应该并不陌生,产生的缘由就是在子线程操做UI控件了。那么为何在子线程操做UI控件,就会抛出异常呢?segmentfault
咱们再来看另外一条异常信息安全
java.lang.RuntimeException: Can't toast on a thread that has not called Looper.prepare() at android.widget.Toast$TN.<init>(Toast.java:435) at android.widget.Toast.<init>(Toast.java:150) at android.widget.Toast.makeText(Toast.java:313) at android.widget.Toast.makeText(Toast.java:303)
抛出这条异常信息是由于在子线程中弹toast致使的。为何跟第一条产生的异常信息不同呢?多线程
让咱们带着疑问开始探索源码之旅吧~~~并发
一、其实第一条异常信息咱们都知道是在哪里抛出的,跟View的绘制机制有关,也就是为何不容许在子线程中操做UI控件?
这是由于Android的UI控件不是线程安全的,若是在多线程中并发访问可能会致使UI控件不可预期的状态,那么为何系统不对UI控件的访问加上锁机制呢?app
因此最简单且高效的方法就是采用单线程模型来处理UI操做,那么Android中子线程必定不能更新UI控件吗?async
其实Android系统在更新UI控件时,会调用ViewRootImpl类的checkThread()来检测当前线程是不是建立UI控件的线程,若是不在同一个线程就会抛出异常。ide
// ViewRootImpl.java public ViewRootImpl(Context context, Display display) { mThread = Thread.currentThread(); // ... 省略无关代码 } @Override public void requestLayout() { checkThread(); // ... } void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } }
二、只要咱们执行下面这段代码就会抛出第二条崩溃日志oop
new Thread(new Runnable() { @Override public void run() { Toast.makeText(MainActivity.this, "handler", Toast.LENGTH_LONG).show(); } }).start(); // 抛出:java.lang.RuntimeException: Can't toast on a thread that has not called
看到这里不少人感到奇怪,子线程中更新UI控件应该是第一条崩溃日志啊。
来看下Toast源码:
Toast.java public Toast(@NonNull Context context, @Nullable Looper looper) { // 建立TN mTN = new TN(context.getPackageName(), looper); // ... } private static class TN extends ITransientNotification.Stub { final Handler mHandler; TN(String packageName, @Nullable Looper looper) { // ... // 看到这里,我相信你们知道为何缘由了。 if (looper == null) { // Use Looper.myLooper() if looper is not specified. looper = Looper.myLooper(); if (looper == null) { throw new RuntimeException( "Can't toast on a thread that has not called Looper.prepare()"); } } mHandler = new Handler(looper, null) { @Override public void handleMessage(Message msg) { } }; } }
阅读Toast源码后发现,弹toast时须要获取当前线程的Looper,若是当前线程没有Looper,就会抛出异常。看到这里会有一个疑问,为何在main线程中弹toast不会报错呢?
咱们能够猜测,既然在main线程没有报错,那么确定main线程中已经建立过Looper对象。是谁建立的呢?
在Android系统中,App启动入口是在ActivityThread类的main方法。
ActivityThread.java public static void main(String[] args) { // 建立Looper对象和MessageQueue对象,用于处理主线程的消息 Looper.prepareMainLooper(); // 建立ActivityThread对象 ActivityThread thread = new ActivityThread(); // 建立Binder通道 thread.attach(false, startSeq); // 主线程的Handler if (sMainThreadHandler == null) { sMainThreadHandler = thread.getHandler(); } // End of event ActivityThreadMain. Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER); Looper.loop(); // 消息循环运行 throw new RuntimeException("Main thread loop unexpectedly exited"); }
阅读ActivityThread源码后,咱们知道main线程的Looper在App启动时就帮咱们建立了,因此咱们能够直接在main线程中弹toast了。 Binder 线程会向 H(就是main线程的Handler) 发送消息,H 收到消息后处理Activity的生命周期,因此在主线程中能够直接更新UI控件了。
主线程的消息循环模型:
ActivityThread 经过 ApplicationThread 和 AMS 进行进程间通信,AMS 以进程间通讯的方式完成 ActivityThread 的请求后会回调 ApplicationThread 中的 Binder 方法,而后 ApplicationThread 会向 H 发送消息,H 收到消息后会将 ApplicationThread 中的逻辑切换到 ActivityThread 中去执行,即切换到主线程中去执行。
先来看这段代码,在子线程中建立Handler,运行后抛出异常了
new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler(); } }).start();
出现崩溃日志以下,提示说明:调用线程中没有调用过Looper.prepare(),就不能建立Handler
java.lang.RuntimeException: Can't create handler inside thread that has not called Looper.prepare() at android.os.Handler.<init>(Handler.java:200) at android.os.Handler.<init>(Handler.java:114)
为何Looper.prepare()会影响Handler的建立呢?走进Handler的源码分析:
//Handler.java final Looper mLooper; final MessageQueue mQueue; public Handler(@Nullable Callback callback, boolean async) { 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; // ... } --------------------------------- // Looper.java // 返回与调用线程相关联的Looper对象 // 若是调用的线程没有关联Looper(也就是没调用过Looper.prepare())返回null public static @Nullable Looper myLooper() { // Looper对象怎么与调用线程关联?设计到ThreadLocal知识 return sThreadLocal.get(); }
从Handler构造方法中可知:在建立Handler对象时,会检查调用线程中是否有Looper关联,若是没有就抛出异常。
Looper源码:
//Looper.java final MessageQueue mQueue; private Looper(boolean quitAllowed) { mQueue = new MessageQueue(quitAllowed); mThread = Thread.currentThread(); } 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)); } // 在ActivityThread中的main方法调用 public static void prepareMainLooper() { prepare(false); synchronized (Looper.class) { if (sMainLooper != null) { throw new IllegalStateException("The main Looper has already been prepared."); } sMainLooper = myLooper(); } }
Looper类中主要做用:
其它的线程怎么拥有本身的Looper呢?Android系统为咱们提供了一个很是方便的类:HandlerThread
// HandlerThread.java public class HandlerThread extends Thread { @Override public void run() { Looper.prepare(); synchronized (this) { mLooper = Looper.myLooper(); notifyAll(); } onLooperPrepared(); Looper.loop(); } }
HandlerThread继承Thread类,当Thread.start()线程,调用Looper.prepare()后,就会建立一个Looper与这个Thread关联。
Handler、Looper、MessageQueue、Message之间的关系:一个Thread持有一个Looper,一个Looper持有一个MessageQueue和多个与之关联的Handler,一个Handler持有一个Looper,一个MessageQueue维护Message单项链表。
简单描述上图:使用Handler发送消息到MessageQueue,Looper不断轮询MessageQueue中消息,分发消息到Handler中处理。
Looper的loop()源码:
// Looper.java 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; // ... for (;;) { // 堵塞调用,从MessageQueue中获取消息 Message msg = queue.next(); // might block if (msg == null) { // No message indicates that the message queue is quitting. return; } // ... // 消息的分发及处理 msg.target.dispatchMessage(msg); // ... } }
Looper不断从MessageQueue中轮询到消息后,分发给Handler处理。
// Handler.java public void dispatchMessage(@NonNull Message msg) { if (msg.callback != null) { handleCallback(msg); } else { if (mCallback != null) { if (mCallback.handleMessage(msg)) { return; } } handleMessage(msg); } }
分发消息Message处理的顺序:
总结:若是想拦截Handler中callback或者Handler中handleMessage方法,能够给Message设置callback,这样Handler中Message处理就不会被执行。
接下来分析关键的MessageQueue中的next():
Message next() { // ... int nextPollTimeoutMillis = 0; for (;;) { // 阻塞在native层 nativePollOnce(ptr, nextPollTimeoutMillis); synchronized (this) { Message prevMsg = null; Message 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 && !msg.isAsynchronous()); } if (msg != null) { if (now < msg.when) { // msg定义的when尚未到,让native继续等nextPollTimeoutMillis时长 nextPollTimeoutMillis = (int) Math.min(msg.when - now, Integer.MAX_VALUE); } else { // 有到点的msg,返回给Looper的loop处理 mBlocked = false; if (prevMsg != null) { prevMsg.next = msg.next; } else { mMessages = msg.next; } msg.next = null; return msg; } } else { // 没有消息,在native层阻塞 nextPollTimeoutMillis = -1; } // 若是调用了quit(),Looper的loop()就会退出无限循环 if (mQuitting) { dispose(); return null; } } // ... // 当在处理idle handler的时,能够发送一个新的Message // nextPollTimeoutMillis设置为0,当即查询挂起的消息,无需等待 nextPollTimeoutMillis = 0; } }
一、 Looper.loop() 死循环为何不会致使应用卡死?
这里就涉及到 Linux pipe/epoll机制,简单说就是在主线程的 MessageQueue 没有消息时,便阻塞在 loop 的 queue.next() 中的 nativePollOnce() 方法里,此时主线程会释放 CPU 资源进入休眠状态,直到下个消息到达或者有事务发生,经过往 pipe 管道写端写入数据来唤醒主线程工做。这里采用的 epoll 机制,是一种IO多路复用机制,能够同时监控多个描述符,当某个描述符就绪(读或写就绪),则马上通知相应程序进行读或写操做,本质同步I/O,即读写是阻塞的。 因此说,主线程大多数时候都是处于休眠状态,并不会消耗大量CPU资源。
一、直接在main线程建立Handler,那么Looper就会与main线程绑定,子线程就能够经过该Handler更新UI
// 使用方式 private static Handler mHandler = new Handler() { @Override public void handleMessage(@NonNull Message msg) { super.handleMessage(msg); } };
二、用Activity的runOnUiThread方法,判断调用线程是不是main线程,若是不是,使用Handler发送到main线程。原理也是使用Handler机制
// Activity.java源码 final Handler mHandler = new Handler(); public final void runOnUiThread(Runnable action) { if (Thread.currentThread() != mUiThread) { mHandler.post(action); } else { action.run(); } }
三、建立Handler时,在Handler构造方法中传入main Looper。能够在子线程建立该Handler,而后更新UI。
// 使用方式 new Thread(new Runnable() { @Override public void run() { Handler handler = new Handler(Looper.getMainLooper()); handler.post(new Runnable() { @Override public void run() { // TODO 更新UI Log.d(TAG, "run: "+Thread.currentThread().getName()); // run: main } }); } }).start();
四、View.post(Runnable action),先看下源码API 29:
// View.java源码 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; } private HandlerActionQueue getRunQueue() { if (mRunQueue == null) { mRunQueue = new HandlerActionQueue(); } return mRunQueue; }
在来看下HandlerActionQueue的源码:
// HandlerActionQueue.java源码 private HandlerAction[] mActions; private int mCount; public void post(Runnable action) { postDelayed(action, 0); } public void postDelayed(Runnable action, long delayMillis) { final HandlerAction handlerAction = new HandlerAction(action, delayMillis); synchronized (this) { if (mActions == null) { mActions = new HandlerAction[4]; } mActions = GrowingArrayUtils.append(mActions, mCount, handlerAction); mCount++; } } // 关键:executeActions调用时机不一样,可能致使不会被执行 public void executeActions(Handler handler) { synchronized (this) { final HandlerAction[] actions = mActions; for (int i = 0, count = mCount; i < count; i++) { final HandlerAction handlerAction = actions[i]; handler.postDelayed(handlerAction.action, handlerAction.delay); } mActions = null; mCount = 0; } }
这种方式有不靠谱的地方,区别在于API 24(Android7.0)以上,可能post()的Runnable,永远不能运行。
根本缘由在于,API 24(Android7.0)以上 executeActions() 方法的调用时机不一样,致使 View 在没有 mAttachInfo 对象的时候,表现不同了。而executeActions()执行只会在View.dispatchAttachedToWindow()方法中调用
若是你只是经过 new 或者使用 LayoutInflater 建立了一个 View ,而没有将它经过 addView() 加入到 布局视图中去,你经过这个 View.post() 出去的 Runnable ,将永远不会被执行到。
举一个例子说明问题:
private ViewGroup mRootLayout; private Handler handler = new Handler(Looper.getMainLooper()); @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); mRootLayout = findViewById(R.id.rootLayout); final View view = new View(this); Log.e("View.post()>>>>", "当前设备的SDK的版本:" + Build.VERSION.SDK_INT); view.post(new Runnable() { @Override public void run() { Log.e("View.post()>>>>", "直接new一个View,而后post的Runnable被执行了"); } }); handler.postDelayed(new Runnable() { @Override public void run() { Log.e("View.post()>>>>", "delay addView被执行了"); mRootLayout.addView(view); } }, 1000); } }
在API 24(Android7.0)如下运行结果。从执行结果中的时间能够看出,new出来的View,post的Runnable当即被执行了。
11-04 14:40:20.614 View.post()>>>>: 当前设备的SDK的版本:19 11-04 14:40:20.664 View.post()>>>>: 直接new一个View,而后post的Runnable被执行了 11-04 14:40:22.614 View.post()>>>>: delay addView被执行了
而在API 24(Android7.0)以上(包括7.0)运行的结果。从执行时间上能够看出,post的Runnable没有当即被执行,而是addView后才被执行。
2019-11-04 14:44:36.240 View.post()>>>>: 当前设备的SDK的版本:28 2019-11-04 14:44:38.243 View.post()>>>>: delay addView被执行了 2019-11-04 14:44:38.262 View.post()>>>>: 直接new一个View,而后post的Runnable被执行了
经过Handler能够发送的Message的方法以下:
public final boolean sendEmptyMessage(int what) public final boolean sendEmptyMessageDelayed(int what, long delayMillis) public final boolean sendEmptyMessageAtTime(int what, long uptimeMillis) public final boolean sendMessage(@NonNull Message msg) public final boolean sendMessageDelayed(@NonNull Message msg, long delayMillis) public boolean sendMessageAtTime(@NonNull Message msg, long uptimeMillis) public final boolean sendMessageAtFrontOfQueue(@NonNull Message msg) public final boolean post(@NonNull Runnable r) public final boolean postAtTime(@NonNull Runnable r, long uptimeMillis) public final boolean postDelayed(@NonNull Runnable r, long delayMillis) public final boolean postAtFrontOfQueue(@NonNull Runnable r)
这些方法最终调用的是enqueueMessage方法
// Handler.java源码 private boolean enqueueMessage(@NonNull MessageQueue queue, @NonNull Message msg, long uptimeMillis) { msg.target = this; msg.workSourceUid = ThreadLocalWorkSource.getUid(); if (mAsynchronous) { msg.setAsynchronous(true); } return queue.enqueueMessage(msg, uptimeMillis); }
Handler源码分析到这了,可是它的精髓远远不止这些,还有不少鲜为人知的秘密,须要咱们去探索。
下一篇:Handler扩展知识探索~~~
若是个人文章对您有帮助,不妨点个赞鼓励一下(^_^)