天天看點

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

UI 繪制流程及原理全過程解析

UI 繪制流程類文章在網上一搜一大把,但是都有一定的斷層。比如隻分析了View的三大流程 onMeasure 、 onLayout 、 onDraw ,但是這三個方法的調用鍊卻沒有描述。這篇文章就應運而生。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

Window 頂層 DecorView 身世

我們都知道,通過

startActivity

啟動一個頁面時,如果在

onCreate

沒有調用

setContentView

,則目前頁面是一個空白頁面。我們通過 Layout Inspector 檢視目前 View 層級如下圖。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

發現最頂層的就是 DecorView。此時我們通過

setContentView

添加一個 TextView,在看一下 View 樹如下圖。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

通過對比可以看出我們的 TextView 是添加在了 id 為 content 的 ViewGroup 中。

但是我們沒有添加這個 DecorView,秉着魯迅的名言——存在即合理 的原則,是以 DecorView 就是系統幫我們添加了,下面我們就從

startActivity

入口去 Reading the fuck code。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

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 中,就梳理完成了。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

在來一張圖總結一下這麼個過程。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

到這裡,交接儀式已經完成了。完美的從 Activity 啟動交接到 View 的繪制了。下面在分析 View 的繪制相關。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

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 了。

最後我們将這三大流程來個圖總結一下。

UI 繪制流程及原理全過程解析UI 繪制流程及原理全過程解析

ok,完美收官!

繼續閱讀