前语

这两个类便是ActivityThread和ViewRootImpl,之所以说碰不到是由于咱们无法经过正常的方式引证这两个类或者其类的目标,调用办法或者直接拿他的特点。但他们其实又无处不在,使用开发中许多时分都和他们息息相关,阅读他们掌握其内部完成对咱们了解Android运行机理有醍醐灌顶之效果,码读百变其义自见,常读常新。本文就测验从几个咱们常常接触的方面先谈谈ViewRootImpl。

1.1 ViewRootImpl哪来的?

首先是ViewRootImpl,坐落android.view包下,从它所在的方位大概能猜到,跟View相关。其作用一句话总结,便是衔接Window和View的枢纽。

这个要从咱们最熟悉的Activity开端,咱们知道Activity的设置布局View是经过setContentView() 办法这个办法里边也大有文章,咱们简略的整理下。

  • Activity setcontentView()内部调用了getWindow().setContentView(layoutResID);也便是调用了Window的setContentView办法,Android里Window的唯一完成类便是PhoneWindow,PhoneWindow setContentView,初始化DecorView和把咱们设置的View作为其子类。
  • 目光转移到ActivityThread 没错是咱们提及的别的一个主角,先关注他的handleResumeActivity()办法,里边要害的部分代码,

public void handleResumeActivity(){
    r.window = r.activity.getWindow();
    View decor = r.window.getDecorView();
    ViewManager wm = a.getWindowManager();
    ViewManager wm = a.getWindowManager();
    WindowManager.LayoutParams l = r.window.getAttributes();
    wm.addView(decor, l);
}
  • WindowManager的完成类WindowManageImpl的addView办法里调用了mGlobal.updateViewLayout(view, params);
  • 终究咱们在WindowManagerGlobal的addView办法里找到了
public void addView(){
    root = new ViewRootImpl(view.getContext(), display);
    view.setLayoutParams(wparams);
    mViews.add(view);
    mRoots.add(root);
    mParams.add(wparams);
}

小结

  • 经过整理这个进程咱们知道,setContenview()其实仅仅在Window的下面挂了一个View链,View链的根便是ViewRootImpl。
  • Window经过把View和Activity联系在一起。
  • View链的真实增加操作终究交给了WindowManagerGlobal履行。
  • 补充一点:PopupWindow实质便是在当时Window下挂了一个View链,PopupWindow自身没有Window,就如雷锋塔没有雷锋相同;Dialog是有自己的window关于这点可自行查阅源码考证。

2 ViewRootImpl 一个View链烘托的中转站

View的烘托是自定而上层层向下建议的,大致阅历丈量布局和绘制,View链的管理者便是ViewRootImpl。经过

scheduleTraversals()办法建议烘托动作。交给Choreographer组织真实履行的时间关于Choreographer不熟悉的能够参考我的其他文章。终究履行performTraversals() 办法。

private void performTraversals(){
    performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
    performLayout(lp, mWidth, mHeight);
    performDraw();
}

3 不能在子线程操作View?

ViewRoot的RequestLayout中有这样一段代码:

@Override
public void requestLayout() {
    if (!mHandlingLayoutInLayoutRequest) {
        checkThread();
        mLayoutRequested = true;
        scheduleTraversals();
    }
}
void checkThread() {
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}
  • 咱们对View的操作,比方给TextView设置text,终究都会触发ViewRootImpl的requestLayout() 办法,该办法有如上的一个check逻辑。这便是咱们常说的不能在子线程中更新View。
  • 其实子线程中能够履行View的操作,但是有个条件是:View还未挂载时。 View未挂载不时不会触发requestLayout的,还仅仅一个普普通通的java目标。那挂载逻辑在哪?

4 View 挂载

  • 在ViewRootImpl的performTraversals() 里有这个代码
private void performTraversals(){
    host.dispatchAttachedToWindow(mAttachInfo, 0);//此处的host为ViewGroup
}
  • ViewGroup的dispatchAttachedToWindo()办法会把AttachInfo目标分配每一个View,终究完成咱们所谓的挂载。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
    for (int i = 0; i < count; i++) {
        final View child = children[i];
        child.dispatchAttachedToWindow(info,
                combineVisibility(visibility, child.getVisibility()));
    }
  • 完成挂载的View有任何风吹草动就会把事情传递到大bossViewRootImpl这儿了。

经过addView增加进的View也是会收到父View的mAttachInfo这儿不展开了。

5 View.post()的Runnable终究在哪履行了?

public boolean post(Runnable action) {
    final AttachInfo attachInfo = mAttachInfo;
    if (attachInfo != null) {
        return attachInfo.mHandler.post(action);
    }
    getRunQueue().post(action);
    return true;
}
  • 以上是View post()的代码,可见假如现已完成挂载的View,会直接把post进来的消息交给Hanlder处理了给履行,否则就post了HandlerActionQueue里。
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
  ..
    if (mRunQueue != null) {
        mRunQueue.executeActions(info.mHandler);//内部也是调用handler.post()
        mRunQueue = null;
    }
    ..
}
  • 终究这些Runnable会在View挂载的时分履行,也便是dispatchAttachedToWindow()办法里履行。

6 为什么View.post 能够获取宽高

  • 这个是是一个问题延伸,在Activity中直接获取宽高是获取不到的,咱们通常会使用view.post一个Runnable来获取。原因便是Activity onCreate时经过setContentView仅仅创建了View而未完成挂载,挂载是在onResume时,未挂载的View其实没有阅历丈量进程。。

  • 而经过post的方式,经过上一小节知道,未挂载的View上post之后,使命会在挂载之后,经过handler重新post,此时现已ViewRootImpl现已履行了performTraversals()完成了丈量自然能够得到宽高。

7 还有一点值得注意

ViewRootImpl 不单单是烘托的中转站,还是接触事情的中转站。

硬件传感器接收到接触事情经过层层传递分发到使用窗口的第一站便是ViewRootImpl。为什么这么说?由于我有依据~。这是ViewRoot里的代码

public void setView(){
    ..
    mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
        Looper.myLooper());
}
  • WindowInputEventReceiver是ViewRootImpl的一个内部类,其接收到input事情后,就会进行事情分发。
  • 这儿给咱们的启示是,并不是所有的主线程使命履行都是经过Handler机制, onTouch()事情是底层直接回调过来的,这就和咱们之前卡顿监控说的方案里有一项便是对onTouchEvent的监控。

  • ViewRoot的代码有一万多行,本文剖析的仅仅冰山一角,里边有很多细节直接研讨。
  • 经过ViewRootImpl相关几个点,简略的做了介绍剖析期望对你有帮助。

本文正在参加「金石计划」