前几篇文章讲了
从setContentView开始,了解view的加载过程
LayoutInflater 是怎么把xml添加到decorview?
今天来看一下
DecorView如何添加到Window
1,首先要了解Activity的启动过程
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
···省略若干行
Activity a = performLaunchActivity(r, customIntent);
···省略若干行
}
Activity是通过ActivityThead启动的,首先会走handleLaunchActivity方法,之后会走 performLaunchActivity(r, customIntent)方法,如下
performLaunchActivity(xxxxxx){
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
···省略若干行
if (activity != null) {
Context appContext = createBaseContextForActivity(r, activity);
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
if (DEBUG_CONFIGURATION) Slog.v(TAG, "Launching activity "
+ r.activityInfo.name + " with config " + config);
//调用attach方法
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor);
···省略若干行
}
看performLaunchActivity方法,在这个方法里会通过反射来拿到我们当前的Activity,然后会调用attach方法,之后再看attach方法;
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
//在这里初始化了PhoneWindow。。
mWindow = new PhoneWindow(this);
mWindow.setCallback(this);
再回到performLaunchActivity方法,当attch完成之后会走callActivityOnCreate方法。
performLaunchActivity
。。省略若干行
if (r.isPersistable()) {
//斤如callActivityOnCreate方法,它会执行activity.performCreate方法 mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
也就是说Activity的生命周期,执行顺序,调用流程都会在ActivityThread这个类中执行,例如callActivityOnRestoreInstanceState,callActivityOnPostCreate等。
我们再回到handleLaunchActivity,当它走完performLaunchActivity方法之后,会调用handleResumeActivity,callActivityOnPause等方法
如下,
{
//点进去最终会是callActivityOnResume这个方法来调用OnResume方法
handleResumeActivity(r.token, false, r.isForward,
!r.activity.mFinished && !r.startsNotResumed);
if (!r.activity.mFinished && r.startsNotResumed) {
try {
r.activity.mCalled = false;
mInstrumentation.callActivityOnPause(r.activity);
}
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
//获取到DecorView
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
a.mDecor = decor;
l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
l.softInputMode |= forwardBit;
if (a.mVisibleFromClient) {
a.mWindowAdded = true;
//把decor添加到WindowManager中
wm.addView(decor, l);
}
从上面可以看到当走完handleResumeActivity方法后,通过View decor = r.window.getDecorView();获取到DecorView并设置为隐藏。然后获取WindowManager,然后把decor添加到WindowManager中;
之后我们再进入WindowManager中,发现它是一个接口。通过搜索发现它的实现类为WindowManagerImpl,上面代码中的 wm.addView(decor, l) 这个方法就是走的WindowManagerImpl中的addView方法。
我们进WindowManagerImpl这个类看看者方法
@Override
public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
applyDefaultToken(params);
//这里其实是WindowManagerGlobal 实例的addView方法
//这里的view就是我们传进来的DecorView
mGlobal.addView(view, params, mDisplay, mParentWindow);
}
进入 mGlobal.addView方法
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
。。。省略
//ViewRootImpl 这个类很重要,绘制流程基本都是在这个类完成的。
ViewRootImpl root;
View panelParentView = null;
。。。省略
//这里new一个ViewRootImpl,绘制流程基本都是在这个类完成的
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
//这是三个List,把他们添加到一个list中去,也就是把他们都保存到这个类中,这个类是管理root ,和decorView的。
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
}
// do this last because it fires off messages to start doing things
try {
//这里调用setView把我们的decorView和一些参数传进去
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
}
//以上就是Activity的启动过程中生成phonewindow,并加载DecorView,之后发起绘制的基本过程。
进一步深入了解,我们再进入setView()方法,首先把传进去的DecorView赋值给mView,然后调用mWindowSession.addToDisplay这个方法把我们的DecorView显示出来。
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
//把传进去的DecorView赋值给mView
mView = view;
。。省略 者其中是一些动画的处理和输入法的处理等
// Schedule the first layout -before- adding to the window
// manager, to make sure we do the relayout before receiving
// any other events from the system.
requestLayout();//这个很常见
。。省略
try {
//下面是发起一个跨进程的通信,通过方法名可以猜测,是用来显示的
mOrigWindowType = mWindowAttributes.type;
mAttachInfo.mRecomputeGlobalAttributes = true;
collectViewAttributes();
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
getHostVisibility(), mDisplay.getDisplayId(),
mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
mAttachInfo.mOutsets, mInputChannel);
} catch (RemoteException e) {
}
}
}
...省略
//关键代码
//DecorView把WindowManagerImpl设置为顶层ViewGroup
view.assignParent(this);
我们再进入这个方法view.assignParent(this);这个方法是View中的方法
//就送给我们的View分配Parent
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");
}
}
讲一下requestLayout这个方法
首先,View.requestLayout—>其实是调用View类的mParent .requestLayout–>最终会到DecorView的requestLayout,在这个里面会调用ViewRootImpl的requestLayout。为什么会最终会调用DecorView的requestLayout,是因为在setView()方法的时候利用assignParent(ViewParent parent)设置了DecorView的根部局。所以这个mParent 其实就是ViewRootImpl。 其次,走到ViewRootImpl.requestLayout后就会重新进行绘制流程,代码如下
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
scheduleTraversals(); //遍历操作
}
}
scheduleTraversals()方法
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
这里有一个线程,看一下mTraversalRunnable,它会不断的走 doTraversal()方法
final class TraversalRunnable implements Runnable {
@Override
public void run() {
doTraversal();
}
}
再看一下 doTraversal()方法,里面重要的是 performTraversals()方法,可以猜测这个就是发起重绘的方法。
void doTraversal() {
。。省略
performTraversals();
。。省略
}
继续看 performTraversals()
private void performTraversals() {
// 这个就是我们的DecorView
final View host = mView;
//...省略一些操作,大家可以通过变量名来猜测它是干什么的
//顶层视图DecorView所需要窗口的宽度和高度,因为我们必须要知道父容器的宽高才能测量子View的宽高。
int desiredWindowWidth;
int desiredWindowHeight;
mDisplay.getRealSize(size);
desiredWindowWidth = size.x;
desiredWindowHeight = size.y;
else {
//这这里的宽高是是手机屏幕的宽高,见下面代码
DisplayMetrics packageMetrics =
mView.getContext().getResources().getDisplayMetrics();
desiredWindowWidth = packageMetrics.widthPixels;
desiredWindowHeight = packageMetrics.heightPixels;
}
。。。省略,大部分为初始化操作
// Ask host how big it wants to be
//重点方法,通过注释可以猜测,这里是测量布局所占用空间。
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
。。。省略
//
performLayout(lp, desiredWindowWidth, desiredWindowHeight);
。。。省略
//
performDraw();
}
进入 performMeasure方法看一看,它是会调用DecorView的measure方法,DecorView是继承View的,所以实际调用的是View的measure方法。所以这些测量,layout,draw都是再ViewRootImpl发起的。
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
//以上就是Activity的启动过程中生成phonewindow,并加载DecorView,之后发起绘制的过程。
总结:DecorView如何添加到Window?
看下面绘制流程图,可以发现最终调用了ViewRootImpl.setView,在setview方法里调用了view.assignParent(this);,将Decorview的mParent设置成ViewRootImpl
这也就是为什么View调用requestLayout方法的时候最终会走到ViewRootImpl的requestLayout
这篇博客也帮大家找到了UI绘制流程的起始点是在生什么地方,也就是下面三个方法:///测量–performMeasure()// 摆放布局–performLayout()
// 绘制–performDraw()