#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?
通过
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处 会间接调用
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
/**
- 用来存储所有的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
的单例对象统一管理,最后执行root
的setView()
。 根据我多年阅读源码的经验 答案应该就在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
小结开头我有提到,好多人将API中的Window/PhoneWindow
等价于窗口
,但实际上操作开辟窗口
的是ViewRootImpl
,并且负责管理View
的绘制,是整个视图系统最关键的一环。
疑惑
经常听到有人说
onStart
阶段处于可见模式,对此我感到疑惑。通过源码的分析可知onResume
执行完毕后才会创建窗口
并开启DecorView
的绘制,所以在onStart
连窗口都没有何谈可见
?
注意点:
初学Android时经常在
onCreate
时机获取View
宽高而犯错,原因是View
是在onResume
后才开始绘制,所以在此之前无法获取到View
宽高状态,此时可以通过View.post{}
或者addOnGlobalLayoutListener
来获取宽高
自我介绍一下,小编13年上海交大毕业,曾经在小公司待过,也去过华为、OPPO等大厂,18年进入阿里一直到现在。
深知大多数初中级Android工程师,想要提升技能,往往是自己摸索成长或者是报班学习,但对于培训机构动则近万的学费,着实压力不小。自己不成体系的自学效果低效又漫长,而且极易碰到天花板技术停滞不前!
因此收集整理了一份《2024年Android移动开发全套学习资料》,初衷也很简单,就是希望能够帮助到想自学提升又不知道该从何学起的朋友,同时减轻大家的负担。
既有适合小白学习的零基础资料,也有适合3年以上经验的小伙伴深入学习提升的进阶课程,基本涵盖了95%以上Android开发知识点,真正体系化!
由于文件比较大,这里只是将部分目录截图出来,每个节点里面都包含大厂面经、学习笔记、源码讲义、实战项目、讲解视频,并且会持续更新!
如果你觉得这些内容对你有帮助,可以扫码获取!!(备注:Android)
最后
简历首选内推方式,速度快,效率高啊!然后可以在拉钩,boss,脉脉,大街上看看。简历上写道熟悉什么技术就一定要去熟悉它,不然被问到不会很尴尬!做过什么项目,即使项目体量不大,但也一定要熟悉实现原理!不是你负责的部分,也可以看看同事是怎么实现的,换你来做你会怎么做?做过什么,会什么是广度问题,取决于项目内容。但做过什么,达到怎样一个境界,这是深度问题,和个人学习能力和解决问题的态度有关了。大公司看深度,小公司看广度。大公司面试你会的,小公司面试他们用到的你会不会,也就是岗位匹配度。
选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!
是岗位匹配度。
选定你想去的几家公司后,先去一些小的公司练练,学习下面试技巧,总结下,也算是熟悉下面试氛围,平时和同事或者产品PK时可以讲得头头是道,思路清晰至极,到了现场真的不一样,怎么描述你所做的一切,这绝对是个学术性问题!
面试过程一定要有礼貌!即使你觉得面试官不尊重你,经常打断你的讲解,或者你觉得他不如你,问的问题缺乏专业水平,你也一定要尊重他,谁叫现在是他选择你,等你拿到offer后就是你选择他了。
金九银十面试季,跳槽季,整理面试题已经成了我多年的习惯!在这里我和身边一些朋友特意整理了一份快速进阶为Android高级工程师的系统且全面的学习资料。涵盖了Android初级——Android高级架构师进阶必备的一些学习技能。
附上:我们之前因为秋招收集的二十套一二线互联网公司Android面试真题(含BAT、小米、华为、美团、滴滴)和我自己整理Android复习笔记(包含Android基础知识点、Android扩展知识点、Android源码解析、设计模式汇总、Gradle知识点、常见算法题汇总。)
[外链图片转存中…(img-3sObbsw4-1713447504555)]
《互联网大厂面试真题解析、进阶开发核心学习笔记、全套讲解视频、实战项目源码讲义》点击传送门即可获取!