(4.1.15.3)子线程更新UI校验 viewRootImpl的checkThread

  1. Android每次刷新UI的时候,最终根布局ViewRootImpl.checkThread()来检验线程是否是View的创建线程。
  2. 由于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的线程

ViewRootImpl.java


  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线程

这也就意味着

  1. 我们如果在ViewRootImpl实例化之前,在 子线程中调用setText等函数是可以成功的
  2. 但是需要注意的是,由于ViewRootImpl没实例,就不会有measure layout draw等过程,所以页面不会显示
  3. 但是一般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.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值