Android单线程模型是这样描述的:
1 |
Android UI操作并不是线程安全的,并且这些操作必须在UI线程执行 |
如果在其它线程访问UI线程,Android提供了以下的方式:
1 2 3 4 |
Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long) Handler |
为什么呢?在子线程中就不能操作UI么?
当一个程序第一次启动的时候,Android会同时启动一个对应的主线程,这个主线程就是UI线程,也就是ActivityThread。UI线程主要负责处理与UI相关的事件,如用户的按键点击、用户触摸屏幕以及屏幕绘图等。系统不会为每个组件单独创建一个线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。所以,响应系统回调的方法永远都是在UI线程里运行,如响应用户动作的onKeyDown()的回调。
那为什么选择一个主线程干这些活呢?换个说法,Android为什么使用单线程模型,它有什么好处?
先让我们看下单线程化的事件队列模型是怎么定义的:
1 |
采用一个专门的线程从队列中抽取事件,并把他们转发给应用程序定义的事件处理器 |
这看起来就是Android的消息队列、Looper和Handler嘛。类似知识请参考:深入理解Message, MessageQueue, Handler和Looper
其实现代GUI框架就是使用了类似这样的模型:模型创建一个专门的线程,事件派发线程来处理GUI事件。单线程化也不单单存在Android中,Qt、XWindows等都是单线程化。当然,也有人试图用多线程的GUI,最终由于竞争条件和死锁导致的稳定性问题等,又回到单线程化的事件队列模型老路上来。单线程化的GUI框架通过限制来达到线程安全:所有GUI中的对象,包括可视组件和数据模型,都只能被事件线程访问。
这就解释了Android为什么使用单线程模型。
那Android的UI操作并不是线程安全的又是怎么回事?
Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程中使用。换句话说,Android的UI操作不是线程安全可以表述为invalidate在子线程中调用会导致线程不安全。作一个假设,现在我用invalidate在子线程中刷新界面,同时UI线程也在用invalidate刷新界面,这样会不会导致界面的刷新不能同步?既然刷新不同步,那么invalidate就不能在子线程中使用。这就是invalidate不能在子线程中使用的原因。
postInvalidate可以在子线程中使用,它是怎么做到的?
看看源码是怎么实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void postInvalidate() { postInvalidateDelayed(0); } public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window if (mAttachInfo != null) { Message msg = Message.obtain(); msg.what = AttachInfo.INVALIDATE_MSG; msg.obj = this; mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); } } |
说到底还是通过Handler的sendMessageDelayed啊,还是逃不过消息队列,最终还是交给UI线程处理。所以View的更新只能由UI线程处理。
如果我非要在子线程中更新UI,那会出现什么情况呢?
1 |
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. |
抛了一个CalledFromWrongThreadException异常。
相信很多人遇到这个异常后,就会通过前面的四种方式中的其中一种解决:
1 2 3 4 |
Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long) Handler |
说到底还没触发到根本,为什么会出现这个异常呢?这个异常在哪里抛出来的呢?
1 2 3 4 5 6 |
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } |
该代码出自 framework/base/core/java/android/view/ViewRootImpl.java
再看下ViewRootImpl的构造函数,mThread就是在这初始化的:
如果在其它线程访问UI线程,Android提供了以下的方式:
1 2 3 4 |
Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long) Handler |
为什么呢?在子线程中就不能操作UI么?
当一个程序第一次启动的时候,Android会同时启动一个对应的主线程,这个主线程就是UI线程,也就是ActivityThread。UI线程主要负责处理与UI相关的事件,如用户的按键点击、用户触摸屏幕以及屏幕绘图等。系统不会为每个组件单独创建一个线程,在同一个进程里的UI组件都会在UI线程里实例化,系统对每一个组件的调用都从UI线程分发出去。所以,响应系统回调的方法永远都是在UI线程里运行,如响应用户动作的onKeyDown()的回调。
那为什么选择一个主线程干这些活呢?换个说法,Android为什么使用单线程模型,它有什么好处?
先让我们看下单线程化的事件队列模型是怎么定义的:
1 |
采用一个专门的线程从队列中抽取事件,并把他们转发给应用程序定义的事件处理器 |
这看起来就是Android的消息队列、Looper和Handler嘛。类似知识请参考:深入理解Message, MessageQueue, Handler和Looper
其实现代GUI框架就是使用了类似这样的模型:模型创建一个专门的线程,事件派发线程来处理GUI事件。单线程化也不单单存在Android中,Qt、XWindows等都是单线程化。当然,也有人试图用多线程的GUI,最终由于竞争条件和死锁导致的稳定性问题等,又回到单线程化的事件队列模型老路上来。单线程化的GUI框架通过限制来达到线程安全:所有GUI中的对象,包括可视组件和数据模型,都只能被事件线程访问。
这就解释了Android为什么使用单线程模型。
那Android的UI操作并不是线程安全的又是怎么回事?
Android实现View更新有两组方法,分别是invalidate和postInvalidate。前者在UI线程中使用,后者在非UI线程中使用。换句话说,Android的UI操作不是线程安全可以表述为invalidate在子线程中调用会导致线程不安全。作一个假设,现在我用invalidate在子线程中刷新界面,同时UI线程也在用invalidate刷新界面,这样会不会导致界面的刷新不能同步?既然刷新不同步,那么invalidate就不能在子线程中使用。这就是invalidate不能在子线程中使用的原因。
postInvalidate可以在子线程中使用,它是怎么做到的?
看看源码是怎么实现的:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 |
public void postInvalidate() { postInvalidateDelayed(0); } public void postInvalidateDelayed(long delayMilliseconds) { // We try only with the AttachInfo because there's no point in invalidating // if we are not attached to our window if (mAttachInfo != null) { Message msg = Message.obtain(); msg.what = AttachInfo.INVALIDATE_MSG; msg.obj = this; mAttachInfo.mHandler.sendMessageDelayed(msg, delayMilliseconds); } } |
说到底还是通过Handler的sendMessageDelayed啊,还是逃不过消息队列,最终还是交给UI线程处理。所以View的更新只能由UI线程处理。
如果我非要在子线程中更新UI,那会出现什么情况呢?
1 |
android.view.ViewRoot$CalledFromWrongThreadException: Only the original thread that created a view hierarchy can touch its views. |
抛了一个CalledFromWrongThreadException异常。
相信很多人遇到这个异常后,就会通过前面的四种方式中的其中一种解决:
1 2 3 4 |
Activity.runOnUiThread(Runnable) View.post(Runnable) View.postDelayed(Runnable, long) Handler |
说到底还没触发到根本,为什么会出现这个异常呢?这个异常在哪里抛出来的呢?
1 2 3 4 5 6 |
void checkThread() { if (mThread != Thread.currentThread()) { throw new CalledFromWrongThreadException( "Only the original thread that created a view hierarchy can touch its views."); } } |
该代码出自 framework/base/core/java/android/view/ViewRootImpl.java
再看下ViewRootImpl的构造函数,mThread就是在这初始化的: