在很早以前,楼主简单的学习过Activity
的结构,但是当时介于各种原因,只是浅尝辄止,并没有进行深入的学习。同时,我发现自己今年在毕业之后有点颓废,不再有去年那股学习劲儿。经过多次的自省,发现自己是因为找不到学习的方向而颓废的。
经过深刻反思自我之后,为了改变现在的状况,也为了弥补弥补当初学习Activity
的遗憾,还为了提升自身的技术水平,我决定从Activity
开始入手,学习并记录Android framework的知识。
本文打算介绍并分析Activity
的结构设计,包含:Activity
、Window
和View
相关知识。本文参考资料:
1. 结构介绍
大伙儿应该都知道,Activity的结构分为三层,分别是:Activity
、Window
和View
,不同层承担着不同的责任。
上面的图简单的描述了
Activity
整个结构的构建流程。这里我再简单的介绍一下这几个部分的作用。
- ActivityThread:每个流程调用起始地点,至于
ActivityThread
的内容不是本文的重点,所以本文不会过多介绍,后续有相关的文章来介绍。- Activity:相当于是一个管理者,负责创建
WindowManager
和Window
,同时初始化View
。- Window:承载着
View
,同时代Activity
处理一切View
的事务。- WindowManager:从字面意思来理解是
Window
的管理,其实是管理Window
上的View
,包括addView
和remove
。
而这几个角色的对应关系是:ActivityThread
全局唯一,整个App进程中只一个实例。ActivityThread
可以对应多个Activity
,一个Activity
对应一个Window
(这里不考虑Dialog
之类的),一个Window
对应一个WindowManager
。
本文打算参考上面的流程图,分别分析Activity的attach过程、Activity的onCreateView过程和WindowManager的addView过程。
2. Activity的attach
Activity的attach过程就是一个初始化的过程,分别对Window
进行初始化、WindowManager
进行初始化。我们先来看看ActivityThread
的handleLaunchActivity
方法:
public Activity handleLaunchActivity(ActivityClientRecord r,
// ······
final Activity a = performLaunchActivity(r, customIntent);
// ······
}
handleLaunchActivity
方法主要调用performLaunchActivity
来启动一个Activity
,再来看看performLaunchActivity
方法:
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
// ·······
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, window, r.configCallback);
// ·······
}
在这里,我们看到了Activity
的attach
方法的调用,我们来看看attach
方法里面做了哪些事情。
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config, String referrer, IVoiceInteractor voiceInteractor,
Window window, ActivityConfigCallback activityConfigCallback) {
// ······
mWindow = new PhoneWindow(this, window, activityConfigCallback);
// ······
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
// ······
}
attach
方法一共做了两件事情:
- 初始化
Window
,其中Window的构造方法还做了一些事,比如说,我们比较关心的LayoutInflater
的初始化。- 初始化
WindowManager
,并且set到Window
里面去。
3. Activity的onCreate
当初,我们刚入门Android的时候就知道,在Activity
的onCreate
方法调用setContentView
可以给当前页面设置一个布局。当时有没有觉得这个非常的神奇,并且对其充满了好奇,可惜的是当时自己对整个Android的设计还很陌生,不敢去探究。今天我们可以正式进入setContentView的内部了,去一探究竟🤪。
其实Activity
的onCreate
过程不仅做setContentView
操作,其实还做了其他的事情。从源码中我们也可以看到,比如说,初始化AppCompatDelegate
(这里以AppCompatActivity
为例),设置Theme等。不过这些不是本文的重点,后面我会专门的文章来分析这些过程,本文的重点是setContentView
方法。
Activity
的setContentView
没有做啥事,实际做事的是它的Window。我们来看看Window
的setContentView
方法:
@Override
public void setContentView(int layoutResID) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
setContentView
方法里面主要是做了如下几件事:
- 通过
installDecor
方法创建DecorView
和mContentParent
。DecorView
的创建在generateDecor
方法;mContentParent
的创建在generateLayout
方法里面,generateLayout
方法里面有一个重要地方根据属性的配置设置了Window
的flags
和features
。这里flags
的生效是在DecorView
的updateColorViews
方法,会根据flags
来计算DecorView
的大小;features
将那种布局加载到DecorView
,这将影响到Activity
默认的布局样式。不过我有一点疑惑至今无解,从mContentParent
的注释了解到,mContentParent
也有可能是DecorView
,但是我看了installDecor
以及内部调用的generateLayout
方法,都没有找到相关可能性。- 将
ContentView
加载到mContentParent
上去。
针对上面设置flag生效的解释我还想补充一下,大伙儿应该都知道,在Android中,我们可以通过setFlags
或者addFlags
来改变一些属性,但是有没有想过是怎么生效的呢?其实过程是非常的简单,我们就不一一的追踪源码了,直接来看一下调用流程:
Window#setFlags
-> PhoneWindow#dispatchWindowAttributesChanged
-> DecorView#updateColorViews
。
最终,会根据设置好的Flag来计算DecorView
的位置和大小,例如说,我们设置了FLAG_FULLSCREEN
让界面全屏,最终在updateColorViews
方法这么来计算DecorView
的高度:
// If we didn't request fullscreen layout, but we still got it because of the
// mForceWindowDrawsStatusBarBackground flag, also consume top inset.
boolean consumingStatusBar = (sysUiVisibility & SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN) == 0
&& (sysUiVisibility & FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS) == 0
&& (attrs.flags & FLAG_LAYOUT_IN_SCREEN) == 0
&& (attrs.flags & FLAG_LAYOUT_INSET_DECOR) == 0
&& mForceWindowDrawsStatusBarBackground
&& mLastTopInset != 0;
上面的代码表达的意思非常简单,就是判断是否消费状态栏的高度。至于其他Flag也是如此,有兴趣的同学可以看一看。
4. WindowManager的addView
我们知道,DecrorView
的ViewParent
是ViewRootImpl
,而View
最重要的三大流程就是由ViewRootImpl
触发的。在正式介绍这一块的知识之前,我们先来看一个简单的结构图:
这个图相信大家已经熟悉的不能再熟悉了,不过这里我还是将它贴出来。参考上面的图,我们可以知道,经过上面的两个流程,我们将
DecorView
部分创建完成,现在还需要两件事需要做:
- 初始化
ViewRootImpl
,并且将ViewRootImpl
与Window
绑定。- 将之前创建好的
DecorView
添加到ViewRootImpl
里面去。
我们先来看看ActivityThread
的handleResumeActivity
方法:
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
// ······
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
// ·······
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;
// ······
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
// ·······
}
}
// ······
}
handleResumeActivity
方法里面主要做了两件事:
- 调用
performResumeActivity
进而回调Activity
的onResume
方法。- 调用
WindowManager
的addView
方法,将DecorView
添加到ViewRootImpl
中去。
调用的addView
方法最终是进入WindowManager
的实现类WindowManagerImpl
中去,而在WindowManagerImpl
的addView
方法中调用了WindowManagerGlobal
的addView
方法。
从名字中,我们就可以看出来,WindowManagerGlobal
是属于全局的。我们再来看看WindowManagerGlobal
的addView
方法:
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
// ······
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
// ·······
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
// do this last because it fires off messages to start doing things
try {
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
addView
方法主要是做了两件事:
- 初始化
ViewRootImpl
,并且将该Window
相关信息保存起来,包括:ViewRootImpl
、DecorView
和LayoutParams
。- 调用
ViewRootImpl
的setView
方法,将DecorView
添加到ViewRooImpl
,并且触发View
的三大流程。
我们都知道,每个Window
都对应着一个DecorView
,而从这里我们可以发现,每个DecorView
都对应着一个ViewRootImpl
,从而得知,如果是一个Dialog或者其他新Window
的界面,必定有一个新的ViewRootImpl
来触发View
的三大流程,而不是由宿主Window
的ViwRootImpl
触发的。
(1). 为什么在Activity的onResume方法调用Handler的post不能获取View的宽高呢?
在回答这个问题之前,我们应该需要注意的是,这里是Handler的post方法,而不是View的post方法。ps:View的post方法能拿到View的宽高。
我们知道Activity
的onResume
方法的执行是在ViewRootImpl
触发测量过程之前,同时ViewRootImpl
是通过如下的方式来触发测量过程的:
void scheduleTraversals() {
if (!mTraversalScheduled) {
mTraversalScheduled = true;
mTraversalBarrier = mHandler.getLooper().getQueue().postSyncBarrier();
mChoreographer.postCallback(
Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
if (!mUnbufferedInputDispatch) {
scheduleConsumeBatchedInput();
}
notifyRendererOfFramePending();
pokeDrawLockIfNeeded();
}
}
我们发现这里通过Handler post了一个异步消息来进行测量。可是,尽管post的是异步消息,在onResume
方法post的消息也先于它执行,因为它在其后post的。所以,在Activity的onResume方法调用Handler的post不能获取View的宽高。
5. 总结
到此位置,对Activity
的结构分析也差不多,在这里,我们做一个简单的总结。
- Activity的结构分为三层,分别是:
Activity
、Window
和View
。- 结构的创建过程分为三步:1. 创建Activity,并且创建与其相关的
WindowManager
和Window
,对应着是Activity
的attach
方法的调用;2. 初始化DecorView
和ContentView
,对应着的是Activity
的onCreate
方法;3. 创建ViewRootImpl
,并且将DecorView
添加到ViewRootImpl
中,同时触发View
树的三大流程。- 在
generateLayout
方法里面,会根据设置不同的flags
来计算DecorView
的大小和位置,计算逻辑在updateColorViews
方法里面;还是根据设置不同的features
方法来选择默认加载到DecorView
中,比如说设置了NO_ACTION_BAR
的features
,就会加载不带ActionBar
的布局。
网友评论