这两个类就是ActivityThread和ViewRootImpl,之所以说碰不到是因为我们无法通过正常的方式引用这两个类或者其类的对象,调用方法或者直接拿他的属性。但他们其实又无处不在,应用开发中很多时候都和他们息息相关,阅读他们掌握其内部实现对我们理解Android运行机理有醍醐灌顶之疗效,码读百变其义自见,常读常新。本文就尝试从几个我们经常接触的方面先谈谈ViewRootImpl。
首先是ViewRootImpl,位于android.view
包下,从它所处的位置大概能猜到,跟View相关。其作用一句话总结,就是连接Window和View的纽带。
这个要从我们最熟悉的Activity开始,我们知道Activity的设置布局View是通过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);
public void addView(){
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
小结
View的渲染是自顶而下、层层向下发起的,大致经历测量布局和绘制,View链的管理者也就是ViewRootImpl
。通过scheduleTraversals()
方法发起渲染动作。交给Choreographer安排真正执行的事件。关于Choreographer
不熟悉的可以参考我的其他文章。最终执行performTraversals()
方法。
private void performTraversals(){
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
performLayout(lp, mWidth, mHeight);
performDraw();
}
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.");
}
}
requestLayout()
方法,该方法有如上的一个check逻辑。这就是我们常说的不能在子线程中更新View。requestLayout
,只是一个普普通通的java对象。那挂载逻辑在哪?performTraversals()
里有这个代码private void performTraversals(){
host.dispatchAttachedToWindow(mAttachInfo, 0);//此处的host为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()));
}
通过addView添加进的View也是会收到父View的mAttachInfo这里不展开了。
public boolean post(Runnable action) {
final AttachInfo attachInfo = mAttachInfo;
if (attachInfo != null) {
return attachInfo.mHandler.post(action);
}
getRunQueue().post(action);
return true;
}
void dispatchAttachedToWindow(AttachInfo info, int visibility) {
..
if (mRunQueue != null) {
mRunQueue.executeActions(info.mHandler);//内部也是调用handler.post()
mRunQueue = null;
}
..
}
dispatchAttachedToWindow()
方法里执行。OnCreate()
方法中直接获取宽高是获取不到的,我们通常会使用view.post一个Runnable来获取。原因就是Activity onCreate
时通过setContentView只是创建了View而未实现挂载,挂载是在onResume时,未挂载的View没有经历测量过程。performTraversals()
完成了测量自然就可以得到宽高。ViewRootImpl
不单单是渲染的中转站,还是触摸事件的中转站。
硬件传感器接收到触摸事件经过层层传递分发到应用窗口的第一站就是ViewRootImpl。为什么这么说?因为我有证据~。这是ViewRoot里的代码
public void setView(){
..
mInputEventReceiver = new WindowInputEventReceiver(inputChannel,
Looper.myLooper());
}