ViewUI更新

本文深入解析子线程为何无法直接更新UI,探讨ViewRootImpl的线程检查机制,详解Activity Resume期间子线程更新UI的特殊时机,并提供三种正确的跨线程更新UI的方法:handler.post、view.post及activity.runOnUiThread,同时附带invalidate和postInvalidate的使用说明。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

子线程不能更新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

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值