Android 子线程更新 view

文章探讨了Android中子线程更新UI线程创建的View时的CalledFromWrongThreadException异常,解释了ViewRootImpl的线程检查机制,以及何时子线程创建的View可以在子线程安全更新。同时提到了在特定场景下如何避免问题和替代方案,如使用SurfaceView。最后强调了成为架构师所需的技能和学习资源。
摘要由CSDN通过智能技术生成

问题

  • CalledFromWrongThreadException 触发的判断逻辑是什么?
  • 子线程可以更新 ui 线程创建的 view 吗?注意这里说的 ui 线程创建的 view。
  • 如何在子线程创建 view 并能执行 view 的各种操作?

为什么不建议在子线程访问UI?

为了效率, UI 控件的实现是单线程的。正常情况下非UI线程访问会抛出 CalledFromWrongThreadException 异常。ViewRootImpl 在执行 view 的刷新操作时会进行线程的判断:

public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        //检查当前执行的线程是不是UI线程
        checkThread();
        scheduleTraversals();
    }
}

void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

注意 mThread != Thread.currentThread,这里的判断只是检测 mThread 是否是当前线程,并不是使用的 main 线程。那是不是说在子线程创建的 view 只要在本 view 访问就可以?

非UI线程真的不能更新UI吗?

override fun onResume() {
    super.onResume()
    val textView = findViewById(R.id.text_view)
    thread { textView.setTextColor(Color.RED) }
}

这里更新 ui 线程创建的 textView 并没有报 CalledFromWrongThreadException,什么原因哪?

view 的刷新操作(本质就是调用 invalidate/requestLayout)最终都会调用到 ViewRootImpl 的 peformTraversals。但 onResume 时,其实 ViewRootImpl 还没有创建,自然就不会进行实质上的 ui 更新。ActivityThread 在执行 handleResumeActivity 时,才会创建 ViewRootImpl

final void handleResumeActivity(IBinder token, boolean clearHide, boolean isForward, boolean reallyResume) {
    //处理Activity的onRestart onResume生命周期。
    ActivityClientRecord r = performResumeActivity(token, clearHide);

    if (r != null) {
        if (r.window == null && !a.mFinished) {
            r.window = r.activity.getWindow();
            View decor = r.window.getDecorView();
            //设置DecorView不可见
            decor.setVisibility(View.INVISIBLE);
           
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
          
            if (a.mVisibleFromClient) {
                a.mWindowAdded = true;
                //利用WindowManager添加DecorView。
                wm.addView(decor, l);
            }
        }
        ...
        //IPC调用,通知AMS Activity启动完成。
        ActivityManagerNative.getDefault().activityResumed(token);
    }
}

//上面的 addView 最终会调用到这里
public void addView(View view, ViewGroup.LayoutParams params, Display display, Window parentWindow) {
    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    //省略代码....
    root = new ViewRootImpl(view.getContext(), display); // 现在才创建 ViewRootImpl

    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
    
    root.setView(view, wparams, panelParentView); //关联 decorView
}

刷新调用过程中具体是在哪部分被中止的哪?

void invalidateInternal(int l, int t, int r, int b, boolean invalidateCache, boolean fullInvalidate) {
    final AttachInfo ai = mAttachInfo; // mAttachInfo 是在 ViewRootImpl 初始化时才创建
    final ViewParent p = mParent;
    if (p != null && ai != null && l < r && t < b) { // 此处条件不成立,不会触发invalidate
        final Rect damage = ai.mTmpInvalRect;
        damage.set(l, t, r, b);
        p.invalidateChild(this, damage);
    }
}

mAttachInfo 的创建参见 ViewRootImpl 的构造函数。

通过 WindowManager 创建子线程 View

线程检查只是判断当前线程与 view 创建的线程是否一致,如果在子线程创建 view,在子线程更新 view 哪。

thread {
    val textView = TextView(activity).apply { text = "Thread" }
    Looper.prepare()
    windowManager.addView(textView, WindowManager.LayoutParams())
    SystemClock.sleep(3000)
    textView.setBackgroundColor(Color.RED)
    Looper.loop()
}

在 activity 里执行上述代码,add 的 view 是主 window 的子窗口,这段代码就没有问题。为什么这里就可以触发 view 的正常绘制流程哪?

addView 时创建 ViewRootImpl:

//WindowManagerGlobal
public void addView(View view, ViewGroup.LayoutParams params,
    Display display, Window parentWindow) {
	...

    ViewRootImpl root;
    View panelParentView = null;
	...
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
	...
    }
}

而 ViewRootImpl 已经具备了屏幕/view刷新各种响应条件:

public class ViewRootImpl{
    View mView; 
	final ViewRootHandler mHandler = new ViewRootHandler(); // 接收view、各种事件,

  	public ViewRootImpl(@UiContext Context context, Display display, IWindowSession session, WindowLayout windowLayout) {
        mContext = context;
        // 用于 view 刷新时线程检查,也就是说子线程也能创建并新view,只要view创建与更新是同一线程即可
        mThread = Thread.currentThread(); 
        // view 刷新时会判断 mAttachInfo 是否为空,如果为空其实就没走到检查线程阶段,所以也不会报子线程刷新ui问题
        mAttachInfo = new View.AttachInfo(mWindowSession, mWindow, display, this, mHandler, this, context);
        mChoreographer = Choreographer.getInstance();// 接收帧同步信号,触发 doTraversal
    }

    final class ViewRootHandler extends Handler {
        @Override
        public String getMessageName(Message message) {
            switch (message.what) {
                case MSG_INVALIDATE:
                    return "MSG_INVALIDATE";
                case MSG_INVALIDATE_RECT:
                    return "MSG_INVALIDATE_RECT";
                ......
            }
        }
    }
}

只不过这些实例现在是在子线程创建的而已。

使用这种方式在子线程创建 view 局限比较大,如果 View 刷新任务确实重,可以考虑使用 SurfaceView 来取代 View。

最后

如果想要成为架构师或想突破20~30K薪资范畴,那就不要局限在编码,业务,要会选型、扩展,提升编程思维。此外,良好的职业规划也很重要,学习的习惯很重要,但是最重要的还是要能持之以恒,任何不能坚持落实的计划都是空谈。

如果你没有方向,这里给大家分享一套由阿里高级架构师编写的《Android八大模块进阶笔记》,帮大家将杂乱、零散、碎片化的知识进行体系化的整理,让大家系统而高效地掌握Android开发的各个知识点。
img
相对于我们平时看的碎片化内容,这份笔记的知识点更系统化,更容易理解和记忆,是严格按照知识体系编排的。

欢迎大家一键三连支持,若需要文中资料,直接扫描文末CSDN官方认证微信卡片免费领取↓↓↓(文末还有ChatGPT机器人小福利哦,大家千万不要错过)

PS:群里还设有ChatGPT机器人,可以解答大家在工作上或者是技术上的问题
图片

  • 18
    点赞
  • 19
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
Android 中,主线程线程之间可以通过以下方式进行通信: 1. Handler:Handler 是 Android 中一种常用的线程间通信机制。主线程可以创建 Handler 对象,并将其传递给线程线程就可以通过 Handler 发送消息到主线程,主线程收到消息后,可以通过 Handler 处理消息。 2. AsyncTask:AsyncTask 是 Android 中一个方便的异步任务框架,它可以在线程中执行耗时操作,然后将执行结果传递给主线程。AsyncTask 可以通过重写其 onPostExecute() 方法,在主线程中处理执行结果。 3. runOnUiThread() 方法:runOnUiThread() 方法是 Activity 类提供的一个方法,它可以让线程中的代码在主线程中执行。线程可以通过 runOnUiThread() 方法将更新 UI 的任务发送到主线程。 4. View.post() 方法:View.post() 方法是 View 类提供的一个方法,它可以让线程中的代码在主线程中执行。线程可以通过 View.post() 方法将更新 UI 的任务发送到主线程。 5. Broadcast Receiver:Broadcast Receiver 是 Android 中一种广播机制,它可以让应用程序中的不同组件之间进行通信。线程可以通过发送广播的方式将数据传递给主线程。 6. Messenger:Messenger 是 Android 中一种轻量级的 IPC 机制,它可以让不同进程之间进行通信。主线程可以创建 Messenger 对象,并将其传递给线程线程就可以通过 Messenger 发送消息到主线程,主线程收到消息后,可以通过 Messenger 处理消息。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值