在“Activity中view的加载”文章中可知,当用户在Activity中提供的view添加到DecorView后,view的加载就完成,再经过绘制就能显示出来了。那么DecorView又是如何添加到窗口中的或者说Activity关联的view又是如何被添加到窗口中的?
Activitythread的scheduleLaunchActivity方法负责启动Activity,经过Handerl处理执行到handleLaunchActivity
Activity a = performLaunchActivity(r, customIntent);
handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);
performLaunchActivity会创建待启动Activity类的一个实例,并调用其attach方法进行初始化,然后调用Activity的onCreate,onStart方法,在onCreate中会调用用户实现的setContentView,在setContentView中会创建Activity关联的DecorView。handleResumeActivity会调用onResume方法,让Activity处于可见状态并把其关联的DecorView添加到WindowManager中。attach方法做了很多工作,重点是创建了关联的window对象即PhoneWindow对象,获得关联的WindowManager对象,主要代码:
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
mWindowManager = mWindow.getWindowManager();
makeNewWindow的过程创建了PhoneWindow对象,setCallback把Activity对象传递到关联的Window对象中便于窗口的状态发生改变时能够通知Activity,getWindowManager得到WindowManager对象。继续看如何添加view的
if (r.window == null && !a.mFinished && willBeVisible) {
r.window = r.activity.getWindow();
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;
wm.addView(decor, l);
}
首先获得关联的window对象和根view并把view的属性设为不可见,因为此时还没有加入到窗口中,所以设为不可见。获得关联的WindowManager对象,获得窗口的属性并设窗口类型为“应用程序窗口”,最后调用WindowManager的addView方法把Activity的view添加到窗口中,并置标志位mWindowAdded为true,表示view添加成功。有的文章中提到在Activity的makeVisible中添加view,这个不是完全正确
void makeVisible() {
if (!mWindowAdded) {
ViewManager wm = getWindowManager();
wm.addView(mDecor, getWindow().getAttributes());
mWindowAdded = true;
}
mDecor.setVisibility(View.VISIBLE);
}
mWindowAdded已经为true了,所以不会在执行添加动作,makeVisible的重要工作让decorview可见。读到此处可知,先是让Activity可见,然后view才能可见。再看WindowManager的addView的具体实现,WindowManager的实现为WindowManagerImpl,添加、删除、更新窗口都是在这个类中进行的,这个类的实际的操作还是由WindowManagerGlobal进行,相关代码:
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
root.setView(view, wparams, panelParentView);
创建view关联的ViewRootImpl对象并初始化,设置view的布局属性,把view添加到mViews列表中保存起来,ViewRootImpl对象添加到mRoots列表中,布局参数存储到mParams列表中,再把view传递到ViewRootImpl的setView中,看setView做了什么
mAttachInfo.mRootView = view;
mAdded = true;
requestLayout();
把view放在AttachInfo对象中保存起来,AttachInfo是View的内部类,其作用是保存即将被添加到窗口中的view的信息的。mAdded表示添加成功的标志,requestLayout方法很重要,会刷新view树,对view进行测量、布局、绘制,经过这个步骤后,用户提供的视图显示出来。
res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes, getHostVisibility(), mDisplay.getDisplayId(), mAttachInfo.mContentInsets, mInputChannel);
addToDisplay的实现在Session中,这里是一个binder通信过程,Session的addToDisplay会传递到WindowManagerService的addWindow,添加窗口真实在这里发生的。从WindowManagerGlobal到WindowManagerService这几个过程可以看到,添加view变成了添加窗口,view都被保存在列表中,在创建每一个ViewRootImpl对象时都会创建对应的window对象
mWindow = new W(this);
最终在WindowManagerService的addWindow中完成窗口的添加
win = new WindowState(this, session, client, token,attachedWindow, appOp[0], seq, attrs, viewVisibility, displayContent);
mWindowMap.put(client.asBinder(), win);
用传递过来的窗口对象client作为参数创建了一个WindowState对象,然后把该对象作为值,把传递过来的窗口IBinder句柄作为键,作为一个键值对放在HashMap中保存起来,等下次使用的时候就可以从HashMap中根据窗口IBinder句柄取出对应的窗口对象。从这里可以看出,添加窗口的过程实际上变成了添加WindowState,WindowState实际上就表示一个窗口,这个窗口代表了被添加的窗口,也就是说,添加view最后变成添加窗口。window只是一个抽象的概念,其存在形式为view,添加view变成了添加窗口。窗口添加好后,相关事件就可以通过窗口传递给viewRootIml,再由viewRootIml传递给view。比如窗口焦点事件,如果焦点发生了变化,事件就会通过WindowState回调给上面参数client,client就是IWindow对象,IWindow对象传递给viewRootIml,viewRootIml又会传递给view。
本文的标题是“Activity的view添加到window中”,可以理解为添加Activity关联的窗口到窗口管理器中。在这个过程中会把decorview显示出来并把用户提供的视图绘制出来。
回答艺术开发探索的问题,一个应用有多少个window?
一个窗口对应一个view,一个view对应一个viewRootImpl,每个Activity启动的时候会关联一个窗口,那么每个Activity都会对应一个窗口,如果有N个Activity就会有N个窗口,但是窗口并不都是同时存在的,只有当Activity处于活动或者可见状态时窗口才会存在。一个窗口对应一个view,这里的view不是开发者写的textview或者button,而是根decorview。除了Activity,还有各种dialog,toast,弹出式菜单等,这些UI都会有对应的窗口,只要能想到和用户交互的地方都会存在窗口的概念。所以一个应用包含多个窗口,具体数目看这个应用有多少个Activity、dialog、toast等,而且这些视图必须处于活动或可见状态。
备注:可以验证这个说法,写一个小Demo,然后在创建WindowState和Decorview地方加上打印,每次启动Activity会不会打印到。