Android基础 -- 子线程可以修改UI吗?

子线程可以修改UI吗?为什么会产生这样的问题,可能是因为在开发过程中遇到了

"Only the original thread that created a view hierarchy can touch its views."

这个异常信息,又或者是常用Handler将子线程中的数据更新到UI上,又或者是其他的一些原因,如果你思考到了为什么要在主线程中更新UI或者子线程中可以更新UI吗这样的问题,说明你有一颗探寻问题本质的心,对于技术从业者来讲,是极为有益的,知其然更要知其所以然,接下来,就一起看看其中的逻辑。

先说结论,子线程是可以修改的UI的,例如下面这行代码

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.yourlayoutid);
        new Thread(new Runnable() {
            @Override
            public void run() {
                mContentTv.setText("我是来自子线程的更新信息");
            }
        }).start();
    }

但是如果给这个线程做个延迟,或者使用按钮点击来执行,就会看到上面熟悉的异常信息了,那么我们从这个异常信息入手,这个异常信息是在ViewRootImpl中抛出的

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

如果你不熟悉ViewRootImpl,先不要跳出去看ViewRootImpl,对于刚开始看源码的同学而言,切记不要一看到陌生的类,就跳出去看,在跳出去看的过程中,可能又会遇到不熟悉的类,所以,相关的类的知识点,只需要知道它的大致功能就好,等理解了当前的知识点后,在去看这些陌生的类的细节,刚开始可能会比较痛苦,但是一段时间之后,看源码的效率会大大提高。

ViewRootImpl可以先暂时理解为一个View的管理类,它在执行一些View相关操作的时候,会去检查线程是否为主线程,如果不是,就抛出这个异常。

那么接下来的关注点就是ViewRootImpl何时被创建?

既然是创建,我们就从构造方法入手

public ViewRootImpl(Context context, Display display) {
        ...        
}

可以看到在WindowManagerGlobal的addView方法里,有着ViewRootImpl的初始化操作

public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {
        
        ViewRootImpl root;
        
        synchronized (mLock) {

            root = new ViewRootImpl(view.getContext(), display);

            view.setLayoutParams(wparams);

            mViews.add(view);
            mRoots.add(root);
            mParams.add(wparams);

        }
    }

而WindowManagerGlobal的addView方法会在WindowMangerImpl被调用

    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
    }

WindowManagerImpl是WindowManager的实现类,而WindowsManager继承自ViewManager,在Activity里的makeVisible方法里有着WindowMangerImpl的addView方法的调用

void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }

而makeVisible方法在ActivityThread的handleResumeActivity里进行了调用

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
       
            if (r.activity.mVisibleFromClient) {
                r.activity.makeVisible();
            }
        }

    }

在handleResumeActivity里,调用了perfromResumeActivity方法

    @Override
    public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
            String reason) {
        
        final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
      
        }

在perfromResumeActivity方法里,调用了activity的performResume方法

    @VisibleForTesting
    public ActivityClientRecord performResumeActivity(IBinder token, boolean finalStateRequest,
            String reason) {
            r.activity.performResume(r.startsNotResumed, reason);
        } 

在performResume方法里,调用了Instrument的callActivityOnResume方法

final void performResume(boolean followedByPause, String reason) {
        mInstrumentation.callActivityOnResume(this);
    }

callActivityOnResume方法里,调用了onResume

public void callActivityOnResume(Activity activity) {
        activity.mResumed = true;
        activity.onResume();
    }

至此,我们就可以明白,ViewRootImpl是在onResume的时候创建的,而上面的示例代码,是在onCreate里修改UI的,这个时候还没有ViewRootImpl,所以不会检查线程。

上面的源码分析是从入口一步一步找上来了,现在顺着逻辑在来做一个总结。

在ActivityThread里的handleResumeActivity方法里,调用了onResume,并且创建了ViewRootImpl,有了ViewRootImpl后,就无法在子线程中直接修改UI了。

这两条线,小伙伴可以自己顺着源码找一找具体的调用关系,调用到的函数在文章里都提到了,希望会对小伙伴有帮助~

你可能感兴趣的:(你好,Android)