Android 绘制流程(从Activity创建 -> 内容展示)
如果说,岁月是一首歌,那么我们便是歌者,纵使孤独,仍会固执高歌
背景
刚入门Android开发时,水水对启动一个“页面”,到页面“显示”执行过程的印象:
- StartActivity 启动新的Activity
- Activity执行onCreate生命周期
- setContentView 加载Layout布局
- onResume 应用进入前台显示
通常会把需要显示的内容,写进xml布局文件中,然后Activity创建后,进入前台就会展示。
再后来了解到ActivityThread,PhoneWindow,ViewRootImpl,DecorView 这些UI绘制过程中的关键组成,本次作为新晋职场老油条,水水将对Android绘制流程中从Activity创建到页面显示中的关键类,关键方法进行整理性说明。
说明
源代码基于 Android api 29/ androidx 1.1.0
关键类 :ActivityThread
ActivityThread就是平时常说的Android主线程或UI线程,ActivityThread中的main方法可以看做为App的入口。关于ActivityThread及App启动,可以看这篇。 本文中关于启动时的UI绘制流程,在ActivityThread中主要关注一下两个重要方法。即在LaunchActivityItem生命周期事件之后,ActivityThread中执行的流程,该过程中会执行两个 关键方法:
- ActivityThread&performLaunchActivity
- ActivityThread&handleResumeActivity 本文会从两个方法展开,分析从Activity创建 -> 内容展示究竟创建了哪些类,并执行了哪些方法。
关键方法 performLaunchActivity:
顾名思义这是一个执行Launch(启动)Activity的方法,进入代码:
ActivityThread.java -> performLaunchActivity
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
......
// 创建Activity所属的ContextImpl
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {
java.lang.ClassLoader cl = appContext.getClassLoader();
// 通过Instrumentation创建Activity
// Instrumentation中最终newInstance反射创建Activity
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
......
}
try {
......
// 关键方法执行, Activity的attach方法
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,
r.assistToken);
......
activity.mCalled = false;
// 执行Instrumentation 中的callActivityOnCreate,最终执行Activity的onCreate生命周期
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
mInstrumentation.callActivityOnCreate(activity, r.state);
}
} catch (SuperNotCalledException e) {
......
return activity;
}
Instrumentation中的callActivityOnCreate方法:
Instrumentation.java -> callActivityOnCreate
public void callActivityOnCreate(Activity activity, Bundle icicle,
PersistableBundle persistentState) {
prePerformCreate(activity);
// 执行Activity的performCreate
activity.performCreate(icicle, persistentState);
postPerformCreate(activity);
}
Activity中的prePerformCreate方法:
@UnsupportedAppUsage
final void performCreate(Bundle icicle, PersistableBundle persistentState) {
.....
// 执行自身onCreate生命周期
if (persistentState != null) {
onCreate(icicle, persistentState);
} else {
onCreate(icicle);
}
......
}
阶段小结
在performLaunchActivity方法中,可以看到:
- 执行了Activity所属Context的真正执行类ContextImpl的创建
- 创建完了Activity之后,在Instrumentation中通过newInstance方式,反射创建了Activity
- 创建完Activity之后,在onCreate方法执行之前先调用了关键方法:attach
- 在执行完attach方法后,通过层层调用,最终执行Activity的onCreate生命周期方法
执行流程: performLaunchActivity - > Activity创建 -> Activtiy.attach -> Activity.onCreate
深入attach方法,看看究竟做了什么操作:
Activity.java -> attach
@UnsupportedAppUsage
final void attach(Context context, ActivityThread aThread,
...... {
attachBaseContext(context);
mFragments.attachHost(null /*parent*/);
// 执行了PhoneWindow的创建,并将其赋值给Activity中的mWindow
mWindow = new PhoneWindow(this, window, activityConfigCallback);
mWindow.setWindowControllerCallback(this);
mWindow.setCallback(this);
mWindow.setOnWindowDismissedCallback(this);
mWindow.getLayoutInflater().setPrivateFactory(this);
if (info.softInputMode != WindowManager.LayoutParams.SOFT_INPUT_STATE_UNSPECIFIED) {
mWindow.setSoftInputMode(info.softInputMode);
}
if (info.uiOptions != 0) {
mWindow.setUiOptions(info.uiOptions);
}
// 将当前线程赋值给mUiThread,即 ActivityThread(主线程) == UIThread
mUiThread = Thread.currentThread();
mMainThread = aThread;
mInstrumentation = instr;
mToken = token;
mAssistToken = assistToken;
.....
//设置WindowManager
mWindow.setWindowManager(
(WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
mToken, mComponent.flattenToString(),
(info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
if (mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
mWindowManager = mWindow.getWindowManager();
......
}
从上面的代码中,可以看到 PhoneWindow在Activity的attach方法中被创建,并赋值给Activity中的mWindow变量。PhoneWindow作为Android中Window的唯一实现类。Window可以看做Android显示系统中,在Framework层对于窗口的抽象,而PhoneWindow为这个抽象的具体实现类。关于窗口和Android显示系统,可以看这篇。
再到onCreate方法:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
}
程序会在onCreate方法中执行setContentView方法
深入setContentView,简单看看究竟发生了什么:
AppCompatDelegateImpl 顾名思义就是AppCompatActivity(兼容性Activity)的委托类的实现
AppCompatDelegateImpl.java -> setContentView
@Override
public void setContentView(int resId) {
// 创建SubDecor,以及DecorView,并SubDecor对于DecorView的完成替换
// 这里的SubDecor,顾名思义就是一个替补性质的ViewGroup
ensureSubDecor();
// 获取mSubDecor中的R.id.content对应的ViewGroup
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
// 清空ViewGroup中的子View
contentParent.removeAllViews();
// resId对应的资源文件通过LayoutInflater对象转换为View树,并且添加至mContentParent视图中
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
ensureSubDecor()方法:
AppCompatDelegateImpl.java -> ensureSubDecor
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
// 创建SubDecor
mSubDecor = createSubDecor();
......
}
}
AppCompatDelegateImpl.java -> createSubDecor
private ViewGroup createSubDecor() {
// 获取设置的App 主题,并获取主题中关于TITLE,ACTION_BAR等属性的值
TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
a.recycle();
throw new IllegalStateException(
"You need to use a Theme.AppCompat theme (or descendant) with this activity.");
}
if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
requestWindowFeature(Window.FEATURE_NO_TITLE);
}
......
mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
a.recycle();
// Now let's make sure that the Window has installed its decor by retrieving it
ensureWindow();
// 获取DecorView,最终执行类为PhoneWindow,调用PhoneWindow中的installDecor方法
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
// 根据上设置的Window属性,加载对应的layout布局并赋值给subDecor
if (!mWindowNoTitle) {
if (mIsFloating) {
......
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_dialog_title_material, null);
mHasActionBar = mOverlayActionBar = false;
} else if (mHasActionBar) {
......
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
......
}
} else {
if (mOverlayActionMode) {
subDecor = (ViewGroup) inflater.inflate(
R.layout.abc_screen_simple_overlay_action_mode, null);
} else {
subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
}
.......
// 将subDecor中的R.id.title对应的View赋值给mTitleView,在AppCompat包下的AppCompatActivity或Dialog中title即是这个View,Activity下的setTitle方法会通过委托的形式,调用AppCompatDelegateImpl中的方法。
if (mDecorContentParent == null) {
mTitleView = (TextView) subDecor.findViewById(R.id.title);
}
......
// Make the decor optionally fit system windows, like the window's decor
ViewUtils.makeOptionalFitsSystemWindows(subDecor);
......
// 这里的contentView 对应subDecor中的contentView
final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
R.id.action_bar_activity_content);
// 这里的windowContentView对应DecorView中的R.id.content对应的View
final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
if (windowContentView != null) {
// 如果有些子view已经add到windowContentView,需要进行合并
while (windowContentView.getChildCount() > 0) {
// 移除DecorView中原先contentView的子View,并将移除的child子View,add到subDecor的contentView上,完成merge合并操作
final View child = windowContentView.getChildAt(0);
windowContentView.removeViewAt(0);
contentView.addView(child);
}
// 将原先的DecorView中的contentView,设值为NO_ID
windowContentView.setId(View.NO_ID);
// 将merge之后的subDecor中的contentView,设置id为R.id.content,完成替换前的最后一步操作
contentView.setId(android.R.id.content);
// The decorContent may have a foreground drawable set (windowContentOverlay).
// Remove this as we handle it ourselves
if (windowContentView instanceof FrameLayout) {
((FrameLayout) windowContentView).setForeground(null);
}
}
// Now set the Window's content view with the decor
// 最后将subDecor这个View通过setContentView传入PhoneWindow,本质上就是讲SubDecor通过addView,加到PhoneWindow中的contentParent上,完成替换。
mWindow.setContentView(subDecor);
......
return subDecor;
}
AppCompatActivity 和 Activity在setContentView,在setContentView的过程中,都会创建DecorView,区别点在于:AppCompatActivity中,还会根据给Window设置的属性,创建一个subDecor替换性质的ViewGroup,这个ViewGroup的创建及布局选择,会依据AppCompat包下的主题,比如常见的Theme.AppCompat.Light.DarkActionBar。
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
<!-- Customize your theme here. -->
<item name="colorPrimary">@color/colorPrimary</item>
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
再创建两者之后,通过PhoneWindow中的findViewById(android.R.id.content)找到原本DecorView中的content位置,并将原本content中的子View移除并add到subDecor上,并给subDecor和DecorView中的content重新设置ID,subDecor的ID将设置R.id.content,最后将其add到PhoneWindow中的contentParent上即完成整个替换的过程。
关于mWindow.getDecorView(),即在PhoneWindow中创建DecorView,详细的介绍有很多,可以看看这篇:
performLaunchActivity小结:
在performLaunchActivity的过程中,先后执行了
1.Activity创建
2.Activity的attch
- 创建了PhoneWindow以及对应的WindowManager
3.Activity的onCreate
- 执行setContentView,完成了DerView的创建
在performLaunchActivity过程中,完成Activity中设置resId对应的布局文件的从DecorView到最后子View的加载(过程中涉及xml解析,递归以及反射,详细细节可以看看这篇,但到Activity的onCreate位置,视图还并未显示,需要再执行performResumeActivity方式。
关键方法handleResumeActivity:
ActivityThread.java -> handleResumeActivity
@Override
public void handleResumeActivity(IBinder token, boolean finalStateRequest, boolean isForward,
String reason) {
......
// performResumeActivity最终会执行Activity中的onResume生命周期方法
final ActivityClientRecord r = performResumeActivity(token, finalStateRequest, reason);
......
if (a.mVisibleFromClient) {
if (!a.mWindowAdded) {
a.mWindowAdded = true;
// WindowManager.addView方法
wm.addView(decor, l);
}
......
}
......
Looper.myQueue().addIdleHandler(new Idler());
}
可以看到在DecorView被add到Window上之前,会先执行onResume生命周期,当然指的是在Activity首次启动的过程中,mWindowAdded为false的情况下。
WindowManager有对应的实现类WindowManagerImpl,当然WindowManagerImpl也并不是最终的方法执行类,WindowManagerGlobal才是,并且它是一个单例。再来看WindowManagerGlobal中的addView方法:
WindowManagerGlobal.java -> addView
public void addView(View view, ViewGroup.LayoutParams params,
Display display, Window parentWindow) {
......
生命ViewRootImpl
ViewRootImpl root;
View panelParentView = null;
synchronized (mLock) {
......
// 创建ViewRootImpl
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mViews.add(view);
mRoots.add(root);
mParams.add(wparams);
try {
// 调用ViewRootImpl的setView方法
root.setView(view, wparams, panelParentView);
} catch (RuntimeException e) {
// BadTokenException or InvalidDisplayException, clean up.
if (index >= 0) {
removeViewLocked(index, true);
}
throw e;
}
}
}
ViewRootImpl.java -> setView
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
synchronized (this) {
if (mView == null) {
// 将DecorView赋值到mView变量
mView = view;
......
mAttachInfo.mDisplayState = mDisplay.getState();
// 执行requestLayout
requestLayout();
......
}
.......
}
以上,可以看到performResumeActivity,在addView方法中,主要去创建了Window对应的ViewRootImpl,在创建完ViewRootImpl之后将DecorView传入其中,最后执行ViewRootImpl的requestlayout方法。
ViewRootImpl是什么?
ViewRootImpl是DecorView的父类,平常在代码中的执行绘制或者是刷新api时,往往从子View一层一层往上,经历若干个ViewGroup到达DecorView,DecorView则会将调用它的父类方法即ViewRootImpl中的相对应绘制或刷新api,而这些api最终往往会走到ViewRootImpl中的scheduleTraversals,将刷新事件发送至Choreographer中,过程中choreography会申请VSync刷新信号,并在信号到来之际执行doframe方法,再返回到ViewRootImple中执行performTraversals。关于Choreography编舞者及刷新机制可以看这篇。
ViewRootImpl.java -> performTraversals
private void performTraversals() {
// 这里的mView,就是通过setView方法传入的DecorView
final View host = mView;
.....
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
.....
performLayout(lp, mWidth, mHeight);
.....
performDraw();
}
这里的对应了DecorView即ViewGroup中的measure,layout,draw三个绘制过程中的重要方法。
ViewRootImpl.java -> performMeasure
private void performMeasure(int childWidthMeasureSpec, int childHeightMeasureSpec) {
if (mView == null) {
return;
}
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "measure");
try {
// 执行DecorView.measure
mView.measure(childWidthMeasureSpec, childHeightMeasureSpec);
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}
private void performLayout(WindowManager.LayoutParams lp, int desiredWindowWidth,
int desiredWindowHeight) {
// 判断DecorView是否为空
final View host = mView;
if (host == null) {
return;
}
......
// 执行DecorView的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
if (numViewsRequestingLayout > 0) {
ArrayList<View> validLayoutRequesters = getValidLayoutRequesters(mLayoutRequesters,
false);
if (validLayoutRequesters != null) {
// 执行DecorView的layout方法
host.layout(0, 0, host.getMeasuredWidth(), host.getMeasuredHeight());
}
......
}
performDraw()涉及内容很多,这里不做展开了。
小结
在handleResumeActivity之前,DecorView已经被创建但未绘制,在handleResumeActivity中,先执行了onResume生命周期,后创建了ViewRootImpl作为DecorView的父类,配置Choreography进行刷新以及ViewTree的绘制工作。
由此可见:
在Activity首次创建的过程中,在执行onResume生命周期时,控件树已经被创建,即我们可以通过findViewById获取到UI控件,但这个时期,内容并未展示,而是去向系统申请刷新信号,当信号到来之时,才进行绘制(measure,layout,draw)工作。
总结
在performLaunchActivity到handleResumeActivity的过程中,先后执行了
1.Activity创建
2.Activity的attch
- 创建了PhoneWindow以及对应的WindowManager
3.Activity的onCreate
- 执行setContentView,完成了DerView的创建 4.Activity的onResume
5.WindowMananger.addView
- 创建ViewRootImpl,并将DecorView传入
- 通过Choreography申请刷新信号 6.VSync刷新信号到来
- ViewRootImpl执行performTraversals
- ViewTree控件树,执行measure,layout,draw方法
其中draw方法会根据是否开启硬件加速有多种实现机制。
我是超级水的Zheng滴水,若写的有不对的地方,欢迎指出! 以上,谢谢!