学了又忘?通俗易懂 Android视图系统的设计与实现!(2)

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()执行ActivityonCreate()方法

先来看attach做了什么事情:

#Activity

final void attach(…){

mWindow = new PhoneWindow(this, window, activityConfigCallback);

mWindow.setWindowManager(…);

mWindowManager = mWindow.getWindowManager();

}

复制代码

Activity会在attach()方法中创建一个PhoneWindow对象并复制给成员变量mWindow,随后执行WindowManagersetter、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可知 Activityattach()运行完毕后会执行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)创建mContentParentgenerateLayout(mDecor)代码很长就不贴了,内部会通过mDecor获取到mContentParent并为其设置主题、背景等

到此阶段DecorView创建完毕并与XML Layout建立了关联,但此时根View(DecorView)还未与窗口建立关联,所以是看不到的。

为什么要在onCreate执行setContentView?

通过setContentView可以创建DecorView,而一个Activity通常只有一个DecorView(撇去Dialog等),如若将setContentView放在start、resume可能会创建多个DecorView,进而会造成浪费。所以onCreate是创建DecorView的最佳时机

2.4 ViewRootImpl如何协调View和Window的关系?

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处 会间接调用ActivityonResume方法

  • 注释2处 通过Activity获取PhoneWindow、DecorView、WindowManager,它们的创建时机前面小结有写,忘记的可以回翻阅读。

  • 注释3处 调用了WindowManageraddView方法,顾名思义就是将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());

}

复制代码

内部调用了mGlobaladdView()方法,其实不光addView几乎所有WindowManager方法都是通过委托mGlobal去实现,这种写法看似很奇怪,但实际上这种设计不仅不奇怪而且还很精妙,具体精妙在何处?我列出以下三点:

  • WindowManager提供的功能全局通用不会与某个View/Window单独绑定,为了节省内存理应设计出一个单例

  • WindowManagerImp具备多个职责如Token管理、WindowManager功能等,所以通过单一设计原则WindowManager功能拆分到另一个类中即WindowManagerGlobal,并将其定义为单例。

  • 为了不违背迪米特法则又通过组合模式将WindowManagerGlobal屏蔽在内部。

回归正题,来看mGlobaladdView()方法:

#WindowManagerGlobal

/**

  • 用来存储所有的DecorView

*/

private final ArrayList mViews = new ArrayList();

/**

  • 用来存储所有的ViewRootImpl

*/

private final ArrayList mRoots = new ArrayList();

/**

  • 用来存储所有的LayoutParams

*/

private final ArrayList<WindowManager.LayoutParams> mParams =

new ArrayList<WindowManager.LayoutParams>();

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的单例对象统一管理,最后执行rootsetView()。 根据我多年阅读源码的经验 答案应该就在root.setView()里,继续跟

ViewRootImpl

public void setView(…) {

synchronized (this) {

if (mView == null) {

mView = view;

//注释1

requestLayout();

//注释2

res = mWindowSession.addToDisplayAsUser(mWindow, mSeq, mWindowAttributes,

getHostVisibility(), mDisplay.getDisplayId(), userId, mTmpFrame,

mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,

mAttachInfo.mDisplayCutout, inputChannel,

mTempInsets, mTempControls);

//注释3

view.assignParent(this);

}

}

}

void assignParent(ViewParent parent) {

if (mParent == null) {

mParent = parent;

} else if (parent == null) {

mParent = null;

}

}

复制代码

ViewRootImpl#setView()方法很长,我做了下精简列出几个关键步骤

  • 注释1,requestLayout()通过一系列调用链最终会开启mView(DecorView)绘制(measure、layout、draw)。这一流程很复杂,由于篇幅原因本文就不提了,感兴趣的可查阅Choreographer相关知识

  • 注释2,mWindowSession是一个IWindowSession类型的AIDL文件,它会通过Binder IPC通知WMS在屏幕上开辟一个窗口,关于WMS的实现流程也非常庞大,我们点到为止。这一步执行完我们的View就可以显示到屏幕上了

  • 注释3,最后一步执行了View#assignParent,内部将mParent设置为ViewRootImpl。所以,虽然ViewRootImpl不是一个View,但它是所有View的顶层Parent

最后

自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
过华为、OPPO等大厂,18年进入阿里一直到现在。**

深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长,自己不成体系的自学效果低效漫长且无助

因此我收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。

[外链图片转存中…(img-R0WIfXPj-1715894574432)]

[外链图片转存中…(img-NZwOV99y-1715894574434)]

[外链图片转存中…(img-PQZpc2jR-1715894574435)]

[外链图片转存中…(img-WTg2Rvz8-1715894574436)]

既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点!不论你是刚入门Android开发的新手,还是希望在技术上不断提升的资深开发者,这些资料都将为你打开新的学习之门

如果你觉得这些内容对你有帮助,需要这份全套学习资料的朋友可以戳我获取!!

由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!

  • 18
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值