我之所以第一小节没有将窗口描述成Window是怕大家将二者混淆,因为应用进程的Window/PhoneWindow和真正的窗口根本就是两个概念,作者也曾在阅读源码时就这个问题困惑了很久。在此非常感谢一只修仙的猿在 Android全面解析之Window机制 一文中给了我答案
Android SDK中的Window是一个抽象类,它有一个唯一实现类PhoneWindow,PhoneWindow内部会持有一个DecorView(根View),它的职责就是对DecorView做一些标准化的处理,比如标题、背景、导航栏、事件中转等,很显然与我们前面所说的窗口概念不符合
那PhoneWindow何时被创建?
2.1小结我提到可以通过ActivityThread#performLaunchActivity()创建Activity,来看下其代码:
#ActivityThread
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
…
Activity activity = null;
//注释1
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
…
if (activity != null) {
…
//注释2.
activity.attach(…);
…
//注释3.
if (r.isPersistable()) {
mInstrumentation.callActivityonCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityonCreate(activity, r.state);
}
}
…
return activity;
}
首先通过注释1处创建一个Activity对象,然后在注释2处执行其attach(..)方法,最后在通过callActivityonCreate()执行Activity的onCreate()方法
先来看attach做了什么事情:
#Activity
final void attach(…){
…
mWindow = new PhoneWindow(this, window, activityConfigCallback);
…
mWindow.setWindowManager(…);
mWindowManager = mWindow.getWindowManager();
…
}
Activity会在attach()方法中创建一个PhoneWindow对象并复制给成员变量mWindow,随后执行WindowManager的setter、getter。来重点看一下setter方法:
#Window
public void setWindowManager(…) {
…
if (wm == null) {
//注释1
wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
}
//注释2
mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}
注释1处会通过系统服务获取一个WindowManager类型对象,用来管理Window。
注释2会通过WindowManager创建一个WindowManagerImpl对象,实际上WindowManager是一个接口,它继承自ViewManager接口,而WindowManagerImpl是它的一个实现类
绕来绕去原来是通过WindowManager创建了另一个WindowManager,看起来多此一举,那Android为什么要这样设计呢?
首先WindowManager具备两个职责,管理Window和创建WindowManager。系统服务获取的WindowManager具备创建Window功能,但此时并未与任何Window关联。而通过createLocalWindowManager创建的WindowManager会与对应的Window一对一绑定。所以前者用于创建WindowManager,后者用于与Window一对一绑定,二者职责明确,但让作者费解的是为什么不基于单一设计原则把创建过程抽取至另一个类?如果有知道的同学可以评论区留言,事先谢过~
关于WindowManagerImpl如何管理Window先暂且不提,下面文章会说到
PhoneWindow已经创建完毕,但还没有跟Activity/View做任何关联。扒一扒PhoneWindow的源码你会发现,它内部只是设置了标题、背景以及事件的中转等工作,与窗口完全不搭嘎,所以切勿将二者混淆
2.3 DecorView的创建时机通过2.2可知 Activity的attach()运行完毕后会执行onCreate(),通常我们需要在onCreate()中执行stContentView()才能显示的XML Layout。关于stContentView() 顾名思义就是设置我们的Content View嘛,内部代码如下:
#Activity
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
…
}
public Window getWindow() {
return mWindow;
}
首先通过getWindow()获取到attach()阶段创建的PhoneWindow,随后将layoutResID(XML Layout)传递进去,继续跟:
#PhoneWindow
ViewGroup mContentParent;
public void setContentView(int layoutResID) {
//注释1
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
…
} else {
//注释2
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
注释1处会判断mContentParent是否为空,如果为空会通过installDecor()对其实例化,否则移除所有子View。
注释2处会将layoutResID对应的XML加载到mContentParent。到此为止唯一的疑问是mContentParent如何被创建的,跟一下installDecor():
#PhoneWindow
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor(-1);
…
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
…
}
}
首先创建DecorView类型对象并赋值给引用mDecor。那什么是DecorView?
DecorView继承自frameLayout,内部有一个垂直布局的LinearLayout用来摆放状态栏、TitleBar、ContentView、导航栏,其中ContentView就是用来存放由Activity#setContentView传入的Layout。之所以设计出DecorView是因为状态栏、导航栏等需要做到系统统一,并将其管控 *** 作屏蔽在内部,只暴露出ContentView由开发者填充,符合迪米特法则
再回到mDecor的创建过程,跟一下generateDecor(-1)代码:
#PhoneWindow
protected DecorView generateDecor(int featureId) {
…
return new DecorView(context, featureId, this, getAttributes());
}
直接new出来了一个DecorView。再回到我们最初的疑问,mContentParent从何而来?installDecor()创建出DecorView会通过generateLayout(mDecor)创建mContentParent。generateLayout(mDecor)代码很长就不贴了,内部会通过mDecor获取到mContentParent并为其设置主题、背景等。
到此阶段DecorView创建完毕并与XML Layout建立了关联,但此时根View(DecorView)还未与窗口建立关联,所以是看不到的。
为什么要在onCreate执行setContentView?
2.4 ViewRootImpl如何协调View和Window的关系?通过setContentView可以创建DecorView,而一个Activity通常只有一个DecorView(撇去Dialog等),如若将setContentView放在start、resume可能会创建多个DecorView,进而会造成浪费。所以onCreate是创建DecorView的最佳时机
Activity启动后会在不同时机通过ActivityThread调用对应的生命周期方法,onResume是一个特殊的时机它通过ActivityThread#handleResumeActivity被调用,代码如下:
#PhoneWindow
public void handleResumeActivity(…) {
//注释1
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
…
final Activity a = r.activity;
…
//注释2
r.window = r.activity.getWindow();
View decor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();
…
//注释3
wm.addView(decor, l);
…
}
注释1处 会间接调用Activity的onResume方法
注释2处 通过Activity获取PhoneWindow、DecorView、WindowManager,它们的创建时机前面小结有写,忘记的可以回翻阅读。
注释3处 调用了WindowManager的addView方法,顾名思义就是将DecorView添加至Window当中,这一步非常关键
关于WindowManager的概念2.2小结提到过,它是一个接口有一个实现类WindowManagerImp,跟一下其addView()方法
#WindowManagerImp
private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
public void addView(…) {
…
mGlobal.addView(view, params, mContext.getDisplayNoVerify(), mParentWindow, mContext.getUserId());
…
}
内部调用了mGlobal的addView()方法,其实不光addView几乎所有WindowManager方法都是通过委托mGlobal去实现,这种写法看似很奇怪,但实际上这种设计不仅不奇怪而且还很精妙,具体精妙在何处?我列出以下三点:
WindowManager提供的功能全局通用不会与某个View/Window单独绑定,为了节省内存理应设计出一个单例。
WindowManagerImp具备多个职责如Token管理、WindowManager功能等,所以通过单一设计原则将WindowManager功能拆分到另一个类中即WindowManagerGlobal,并将其定义为单例。
为了不违背迪米特法则又通过组合模式将WindowManagerGlobal屏蔽在内部。
回归正题,来看mGlobal的addView()方法:
#WindowManagerGlobal
private final ArrayList mViews = new ArrayList();
private final ArrayList mRoots = new ArrayList();
private final ArrayList
new ArrayList
public void addView(…) {
…
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
…
root.setView(view
, wparams, panelParentView, userId);
…
}
}
首先创建一个ViewRootImpl类型对象root,然后将view、root、wparams加入到对应的集合,由WindowManagerGlobal的单例对象统一管理,最后执行root的setView()。 根据我多年阅读源码的经验 答案应该就在root.setView()里,继续跟
ViewRootImpl
public void addView(…) {
…
ViewRootImpl root;
synchronized (mLock) {
root = new ViewRootImpl(view.getContext(), display);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
…
root.setView(view[外链图片转存中…(img-JBvfqt3k-1642414700807)]
, wparams, panelParentView, userId);
…
}
}
首先创建一个ViewRootImpl类型对象root,然后将view、root、wparams加入到对应的集合,由WindowManagerGlobal的单例对象统一管理,最后执行root的setView()。 根据我多年阅读源码的经验 答案应该就在root.setView()里,继续跟
ViewRootImpl
欢迎分享,转载请注明来源:内存溢出
评论列表(0条)