子线程不能更新UI的原因:
子线程不能更新UI的原因是ViewRootImpl会在View重绘时检查线程:
//ViewRootImpl
public void invalidateChild(View child, 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.");
}
}
而ViewRootImpl初始化的时机在Activity的Resume:
final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward) {
//WindowManagerGloble在addView时创建了ViewRootImpl并添加到一个列表中
wm.addView(decor, l);
}
所以在onResume执行之前都可以在子线程更新UI:
@Override
protected void onStart() { //这里替换成onCreate也不会崩溃
super.onStart();
new Thread(() -> ((TextView) findViewById(R.id.btn)).setText("text")).start();
}
如果添加一定的延时这种写法就会抛出异常
子线程更新UI的常用方法:
1.handler.post(Runnable)原理略
2.view.post(Runnable):
view.post本质上也是利用Handler机制来实现跨进程调用
类结构示意:
class ViewRootImpl {
//ViewRootImpl的构造函数中会初始化mAttachInfo
//在某个Activity中所有子View中都有一个mAttachInfo,它们指向的都是这个ViewRootImpl的mAttachInfo
AttachInfo mAttachInfo;
}
class AttachInfo {
//mHandler 绑定的是主线程的 Looper
//所有View.post的消息都会发送到这个Handler来处理
Handler mHandler;
}
View.post(Runnable):
public boolean post(Runnable action) {
//当一个View执行过attachedToWindow方法时,它的成员变量mAttachInfo就会被赋值
if(mAttachInfo != null){
return mAttachInfo.mHandler.post(action);
}
//没有执行过attachedToWindow方法时action会被缓存起来,等到performTraversals时一起post
getRunQueue().post(action);
return true;
}
ViewRootImpl.performTraversals方法:
private void performTraversals() {
getRunQueue().executeActions(attachInfo.mHandler); //这里把之前缓存的Runnable对象全部post出去
performMeasure();
performLayout();
performDraw();
}
可以看到在performTraversals方法中在measure,layout,draw之前就发送了缓存的Runnable,但是Runnable中的逻辑在执行时View的绘制是已经完成了的,即使用View.post是可以正确获取到View的宽和高的。
这是因为performTraversals也方法是作为一个Runnable被post到主线程执行的,这段代码整体的被Handler处理,在这个方法中post的消息只能在这个整体执行之后被Handler处理,所以view.post方法中的逻辑一定实在View绘制完成之后才执行。
view.attachedToWindow的执行时机:
ViewRootImol.performTraversals方法中会调用dispatchAttachedToWindow,这个方法会执行每个View的attachedToWindow方法,此后view调用post方法会直接发送消息到主线程
3.activity.runOnUiThread(Runnable)
public final void runOnUiThread(Runnable action) {
if (Thread.currentThread() != mUiThread) {
mHandler.post(action);
} else {
action.run();
}
}
invalidate方法和postInvalidate:
。。。
参考:
https://www.cnblogs.com/dasusu/p/8047172.html