- Android每次刷新UI的时候,最终根布局ViewRootImpl.checkThread()来检验线程是否是View的创建线程。
- 由于Android是通过Handler消息机制的方式刷新UI的。所以Android 的UI控件是线程安全的,不会导致多线程访问使得UI处于不可预期的状态。
我们在(4.1.37.1)深入理解setContentView过程和View绘制过程, 讲到了 View绘制的起点:ViewRootImpl#requestLayout
public final class ViewRootImpl implements ViewParent,
View.AttachInfo.Callbacks, HardwareRenderer.HardwareDrawCallbacks {
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
// 检查发起布局请求的线程是否为主线程
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
}
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
}
}
final TraversalRunnable mTraversalRunnable = new TraversalRunnable();
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
void doTraversal() {
if (mTraversalScheduled) {
mTraversalScheduled = false;
mHandler.getLooper().removeSyncBarrier(mTraversalBarrier);
if (mProfile) {
Debug.startMethodTracing("ViewAncestor");
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "performTraversals");
try {
performTraversals();
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
if (mProfile) {
Debug.stopMethodTracing();
mProfile = false;
}
}
}
}
上面的方法中调用了scheduleTraversals()方法来调度一次完成的绘制流程,该方法会向主线程发送一个“遍历”消息,最终会导致ViewRootImpl#performTraversals() 方法被调用。
但是本章的重点在于checkThread();
一、checkThread();检查发起布局请求的线程是否为创建View的线程
final Thread mThread;
public ViewRootImpl(Context context, Display display) {
mThread = Thread.currentThread();
}
void checkThread() {
if (mThread != Thread.currentThread()) {
throw new CalledFromWrongThreadException(
"Only the original thread that created a view hierarchy can touch its views.");
}
}
- 校验ViewRootImpl构造时记录的mThread, 和当前线程是否一致
- ViewRootImpl的构造是在 onResume之后
- ActivityThread#handleResumeActivity
- ActivityClientRecord r = performResumeActivity(token, clearHide);
- windows获取
- r.window = r.activity.getWindow(); // 获得window对象
- View decor = r.window.getDecorView(); // 获得DecorView对象
decor.setVisibility(View.INVISIBLE); - ViewManager wm = a.getWindowManager(); // 获得windowManager对象
- WindowManager.LayoutParams l = r.window.getAttributes();
- a.mDecor = decor;
- wm.addView(decor, l); // 调用addView方法
- root = new ViewRootImpl(view.getContext(), display); // 【4.1】实例化了ViewRootImpl类
- view.setLayoutParams(wparams);
在做UI更新的时候,会执行当前线程是否是UI线程的检查,即通过检查ViewRoot的构建时线程和当前线程是否一致来判断是否是UI线程,一般来说,ViewRoot所在的线程就是UI线程
这也就意味着
- 我们如果在ViewRootImpl实例化之前,在 子线程中调用setText等函数是可以成功的
- 但是需要注意的是,由于ViewRootImpl没实例,就不会有measure layout draw等过程,所以页面不会显示
- 但是一般View作为一个普通Java对象,会记录setText的值到成员变量,因此在等到绘制时是会显示的
同时,如果我们想在子线程更新View:
- 可以通过hook ViewPootlmpl的方式
最后,Android Framework这样设置的目的是:
- 对于U操作I,一旦提交给了主线程执行,那么再允许子线程操作就会乱套,状态不能保证,所以安卓框架就加了这个限制
二、子线程创建VIew并更新
我在非主线程创建View,就可以在非主线程操作该view了咯!
于是:
private void addWindView(){
TextView tx = new TextView(MainActivity.this);
tx.setText("今天天气很好哦!");
tx.setTextColor(getResources().getColor(R.color.white));
tx.setBackgroundColor(getResources().getColor(R.color.colorPrimary));
tx.setGravity(Gravity.CENTER);
WindowManager wm = MainActivity.this.getWindowManager();
WindowManager.LayoutParams params = new WindowManager.LayoutParams(
250, 150, 200, 200, WindowManager.LayoutParams.FIRST_SUB_WINDOW,
WindowManager.LayoutParams.TYPE_TOAST, PixelFormat.OPAQUE);
wm.addView(tx, params);
}
new Thread(){
@Override
public void run() {
super.run();
addWindView();
}
}.start();
不幸的是,还是崩了。
崩溃信息:
not call Looper.perpare()
源码中:
void checkThread() 通过了,可是在scheduleTraversals()刷新UI的时候:
final ViewRootHandler mHandler = new ViewRootHandler();
.......
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
所以没有Looper实例化的异常。
于是加上Looper.perpare(),和Looper.loop().
new Thread(){
@Override
public void run() {
super.run();
Looper.perpare()
addWindView();
Looper.loop()
}
}.start();
这下没有报错并且成功加载显示UI.
所以,Android中非UI主线程能不能操作UI?答案是可以的。只不过只能在创建View的线程里操作view.