UI 繪制流程及原理全過程解析
UI 繪制流程類文章在網上一搜一大把,但是都有一定的斷層。比如隻分析了View的三大流程 onMeasure 、 onLayout 、 onDraw ,但是這三個方法的調用鍊卻沒有描述。這篇文章就應運而生。
Window 頂層 DecorView 身世
我們都知道,通過
startActivity
啟動一個頁面時,如果在
onCreate
沒有調用
setContentView
,則目前頁面是一個空白頁面。我們通過 Layout Inspector 檢視目前 View 層級如下圖。
發現最頂層的就是 DecorView。此時我們通過
setContentView
添加一個 TextView,在看一下 View 樹如下圖。
通過對比可以看出我們的 TextView 是添加在了 id 為 content 的 ViewGroup 中。
但是我們沒有添加這個 DecorView,秉着魯迅的名言——存在即合理 的原則,是以 DecorView 就是系統幫我們添加了,下面我們就從
startActivity
入口去 Reading the fuck code。
Activity 啟動流程之 PhoneWindow
當我們調用
startActivity
之後,最終會調用
Instrumentation
的
execStartActivity
, Instrumentation 主要負責 Activity 相關生命周期函數的調用。
//Activity.java
public void startActivityForResult(@RequiresPermission Intent intent, int requestCode,
@Nullable Bundle options) {
......
Instrumentation.ActivityResult ar =
mInstrumentation.execStartActivity(
this, mMainThread.getApplicationThread(), mToken, this,
intent, requestCode, options);
......
}
//Instrumentation.java
public ActivityResult execStartActivity(
Context who, IBinder contextThread, IBinder token, Activity target,
Intent intent, int requestCode, Bundle options) {
......
int result = ActivityManagerNative.getDefault()
.startActivity(whoThread, who.getBasePackageName(), intent,
intent.resolveTypeIfNeeded(who.getContentResolver()),
token, target != null ? target.mEmbeddedID : null,
requestCode, 0, null, options);
......
return null;
}
//ActivityManagerService.java
public int startActivity(IBinder whoThread, String callingPackage,
Intent intent, String resolvedType, Bundle bOptions) {
......
return mActivityStarter.startActivityMayWait(appThread, -1, callingPackage, intent,
resolvedType, null, null, null, null, 0, 0, null, null,
null, bOptions, false, callingUser, null, tr);
}
而 AMS 的 startActivity 最終會通過 ActivityStackSupervisor 調用 ActivityThread 的 scheduleLaunchActivity。在 handle 中調用了 handleLaunchActivity 處理 Activity 的啟動事件。
//ActivityThread.java
public final void scheduleLaunchActivity(Intent intent, IBinder token, int ident,
ActivityInfo info, Configuration curConfig, Configuration overrideConfig,
CompatibilityInfo compatInfo, String referrer, IVoiceInteractor voiceInteractor,
int procState, Bundle state, PersistableBundle persistentState,
List<ResultInfo> pendingResults, List<ReferrerIntent> pendingNewIntents,
boolean notResumed, boolean isForward, ProfilerInfo profilerInfo) {
......
sendMessage(H.LAUNCH_ACTIVITY, r);
......
}
private class H extends Handler {
public void handleMessage(Message msg) {
switch (msg.what) {
case LAUNCH_ACTIVITY: {
......
handleLaunchActivity(r, null, "LAUNCH_ACTIVITY");
......
}
}
}
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
......
Activity a = performLaunchActivity(r, customIntent);
......
//performLaunchActivity 流程執行完 ,在來說handleResumeActivity。
handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
......
}
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);
......
}
在 Activity 的 attach 方法中建立了一個 PhoneWindow 對象。
public class Activity extends ContextThemeWrapper
implements LayoutInflater.Factory2,
Window.Callback, KeyEvent.Callback,
OnCreateContextMenuListener, ComponentCallbacks2,
Window.OnWindowDismissedCallback, WindowControllerCallback {
private Window mWindow;
final void attach(...){
......
mWindow = new PhoneWindow(this, window);
......
}
}
可以看到 PhoneWindow 是 Window 的一個實作類
/**
* Abstract base class for a top-level window look and behavior policy. An
* instance of this class should be used as the top-level view added to the
* window manager. It provides standard UI policies such as a background, title
* area, default key processing, etc.
*
* <p>The only existing implementation of this abstract class is
* android.view.PhoneWindow, which you should instantiate when needing a
* Window.
*
* 解釋:每一個 Activity 都持有一個 Window 對象,但是 Window 是一個抽象類,
* 這裡 Android 為 Window 提供了唯一的實作類 PhoneWindow。
* 也就是說 Activity 中的 window 執行個體就是一個 PhoneWindow 對象。
*/
public abstract class Window {
}
在 PhoneWindow 中看到了我們熟悉的 DecorView。在 Activity 的 attach 方法中會調用如下構造函數初始化 PhoneWindow 的同時生成了一個 DecorView。
public class PhoneWindow extends Window implements MenuBuilder.Callback {
// This is the top-level view of the window, containing the window decor.
//這是視窗的頂層視圖。
private DecorView mDecor;
public PhoneWindow(Context context, Window preservedWindow) {
......
mDecor = (DecorView) preservedWindow.getDecorView();
......
}
}
public final View getDecorView() {
if (mDecor == null || mForceDecorInstall) {
installDecor();
}
return mDecor;
}
private void installDecor() {
......
mDecor = generateDecor(-1);
......
mContentParent = generateLayout(mDecor);
......
}
protected DecorView generateDecor(int featureId) {
Context context;
if (mUseDecorContext) {
Context applicationContext = getContext().getApplicationContext();
if (applicationContext == null) {
context = getContext();
} else {
context = new DecorContext(applicationContext, getContext().getResources());
if (mTheme != -1) {
context.setTheme(mTheme);
}
}
} else {
context = getContext();
}
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
......
layoutResource = R.layout.screen_simple;
......
//負責将 screen_simple 布局檔案添加到 decor 中
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
......
}
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true"
android:orientation="vertical">
<ViewStub android:id="@+id/action_mode_bar_stub"
android:inflatedId="@+id/action_mode_bar"
android:layout="@layout/action_mode_bar"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="?attr/actionBarTheme" />
<FrameLayout
android:id="@android:id/content"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:foregroundInsidePadding="false"
android:foregroundGravity="fill_horizontal|top"
android:foreground="?android:attr/windowContentOverlay" />
</LinearLayout>
最後會在 handleResumeActivity 将我們建立的 DecorView 添加到 Window中。
final void handleResumeActivity(IBinder token,boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
......
//該 wm 是 WindowManager 的實作類 WindowManagerImpl
if (a.mVisibleFromClient && !a.mWindowAdded) {
a.mWindowAdded = true;
wm.addView(decor, l);
}
......
}
至此,我們就從 Activity 啟動,到 DecorView 建立,再到系統預設布局檔案添加到 DecorView 中,就梳理完成了。
在來一張圖總結一下這麼個過程。
到這裡,交接儀式已經完成了。完美的從 Activity 啟動交接到 View 的繪制了。下面在分析 View 的繪制相關。
View 繪制流程
上面我們追蹤到了 WindowManagerImpl 的 addView 方法。然後會執行 ViewRootImpl 的 setView 方法。
public void addView(View view, ViewGroup.LayoutParams params,Display display, Window parentWindow) {
......
ViewRootImpl root;
root = new ViewRootImpl(view.getContext(), display);
view.setLayoutParams(wparams);
mRoots.add(root);
root.setView(view, wparams, panelParentView);
......
}
單獨看 ViewRootImpl 名字,就能看出他是所有 View 的根節點的一個實作類。在看看 setView 方法中做了什麼事。
public final class ViewRootImpl implements ViewParent,View.AttachInfo.Callbacks, ThreadedRenderer.HardwareDrawCallbacks {
/**
* We have one child
*/
public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
......
requestLayout();
......
}
@Override
public void requestLayout() {
if (!mHandlingLayoutInLayoutRequest) {
checkThread();
mLayoutRequested = true;
//這裡會通過 Choreographer 編舞者 執行一個runnable,
//最終執行 doTraversal() 方法
scheduleTraversals();
}
}
void doTraversal() {
......
performTraversals();
......
}
private void performTraversals() {
......
//Ask host how big it wants to be
performMeasure(childWidthMeasureSpec, childHeightMeasureSpec);
......
performLayout(lp, mWidth, mHeight);
......
performDraw();
......
}
}
到這裡是不是就很熟悉了,我們常見的 View 三大流程 layout 、 measure 、 draw 了。
最後我們将這三大流程來個圖總結一下。
ok,完美收官!