Android只能主线程中更新UI吗?

    许多人都是人云亦云,自己却从来没有实践过,然而纸上得来终觉浅,绝知此事要躬行。我们先来简单的看下这个代码:

public class MainActivity extends AppCompatActivity {
	private TextView textView;
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
		textView = findViewById(R.id.hello);
		new Thread(new Runnable() {
			@Override
			public void run() {
				textView.setText("Just do it !");
			}
		}).start();
    }
}

大家觉得这个会崩溃掉吗? 马上动手打开AndroidStudio新建一个项目,打开手机模拟器,跑一遍就可以发现运行时没有任何问题的,也成功更新了UI,为啥它在子线程也能成功的更新UI呢,我们看一下textView.setText()的源码(按住Ctrl+鼠标左键点进去),可以发现:

    // 第一层setText
    public final void setText(CharSequence text) {
        setText(text, mBufferType);
    }

    // 第二层setText
    public void setText(CharSequence text, BufferType type) {
        setText(text, type, true, 0);

        if (mCharWrapper != null) {
            mCharWrapper.mChars = null;
        }
    }
   // 第三层setText
   private void setText(CharSequence text, BufferType type,
                         boolean notifyBefore, int oldlen) {
        mTextSetFromXmlOrResourceId = false;
        if (text == null) {
            text = "";
        }
        ...此处省略部分代码

         if (mLayout != null) {
            checkForRelayout();
        }

        ... 此处省略部分代码
}

可以看到第三层的setText调用了一个checkForRelayout()方法,那么再点进去看一下:

   private void checkForRelayout() {
        ... 此处省略

        requestLayout();
        invalidate();

        ... 此处省略
    }
   
  // 贴一下 requestLayout()的实现
    public void requestLayout() {
        if (mMeasureCache != null) mMeasureCache.clear();
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == null) {
            ViewRootImpl viewRoot = getViewRootImpl();
            if (viewRoot != null && viewRoot.isInLayout()) {
                if (!viewRoot.requestLayoutDuringLayout(this)) {
                    return;
                }
            }
            mAttachInfo.mViewRequestingLayout = this;
        }

        mPrivateFlags |= PFLAG_FORCE_LAYOUT;
        mPrivateFlags |= PFLAG_INVALIDATED;

        if (mParent != null && !mParent.isLayoutRequested()) {
            mParent.requestLayout();  // 调用这这个mParent的requestLayout
        }
        if (mAttachInfo != null && mAttachInfo.mViewRequestingLayout == this) {
            mAttachInfo.mViewRequestingLayout = null;
        }
    }

 可以看到checkForRelayout调用的requestLayout,里面又调用了mParent的requestLayout, 那么这个mParent又是什么呢?通过定义可以发现它是一个ViewParent接口,那么是哪里赋值实例化的呢?

     * @see #getParent()
     */
    protected ViewParent mParent;   // mParent的定义,其中ViewParent是一个接口

     
    // 查找View.java源码可以发现,是通过assignParent赋值的
    void assignParent(ViewParent parent) {
        if (mParent == null) {
            mParent = parent;
        } else if (parent == null) {
            mParent = null;
        } else {
            throw new RuntimeException("view " + this + " being added, but"
                    + " it already has a parent");
        }
    } 
   

   // ViewRootImpl中的setView函数中调用了这个assignParent函数
     public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {    
    ... 此处省略
     
    view.assignParent(this);
    
    ... 此处省略
  }

所以View.java中的mParent.requestLayout() 调用的是ViewRootImpl.java中的requestLayout函数,如下:

    @Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            checkThread();  
            mLayoutRequested = true;
            scheduleTraversals();
        }
    }

  // 贴一下chekcThread这个函数的代码

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

可以看到,最终是调用了checkThread这个函数,如果当前线程不是主线程就会抛出一个异常,那为啥我最开头举的例子没有报错呢? 回到第三层的setText中会有一个 if 判断条件 , if(mParent != nul )才会执行requestLayout ,上面也分析了mParent的具体实现是ViewRootImpl,  而ViewRootImpl具体实例化是通过WindowManagerGlobal.addView()添加进去的,之前还有PhoneWindow以及DecorView的加载, 所以我们的setView是在ViewRootImpl还没有初始化完成的时候就已经调用了,所以不会调用到checkThread, 自然也不会报错。

    假如我们在textView.setText() 的前面加一个延时函数,例如延时200ms ,Thread.sleep(200), 等待ViewRootImpl加载完毕,这个时候你就会发现,再次启动就会崩掉了。(没崩掉可能启动的太慢了,加长一些延时时间)

   

猜你喜欢

转载自blog.csdn.net/Chen_dSir/article/details/107153445