天天看點

Android面試黑洞:從Activity建立到View呈現中間發生了什麼?前言圖解Activity啟動到View顯現過程源碼解密總結

作者:青黴素

前言

前段時間公司招人,作為面試官,我經常讓面試者簡述View的繪制流程。他們基本都能講明白View的測量(measure)、布局(layout)、繪制(draw)等過程。還有少數人會提到DecorView和ViewRootImp的作用。但是,當我繼續追問關于Window的内容時,幾乎沒有人回答上來。而本章将會帶你深入了解Window、DecorView、ViewRootImp。除此之外,你還能在本章找到以下問題的答案:

  1. 為什麼要有設計Window?
  2. 子線程真的不能更新UI嗎?
  3. 為什麼在Activity的onCreate方法中無法擷取View的寬和高?

圖解Activity啟動到View顯現過程

Android面試黑洞:從Activity建立到View呈現中間發生了什麼?前言圖解Activity啟動到View顯現過程源碼解密總結

上圖出現了五個對象:Activity、Window、WindowManager、DecorView、ViewRootImpl,我分别解釋一下它們的作用。

  • Activity:Activity像是一個指揮官,它不處理具體的事務,隻在适當的時候指揮Window/WindowManager工作。例如:在attach時建立Window對象、onResume後通知WindowManager添加view。
  • Window:Window是一個視窗,它是View的容器。Android中的視圖以View樹的形式組織在一起,而View樹必須依附在Window上才能工作。一個Window對應着一個View樹。啟動Activity時會建立一個Window,顯示Dialog時也會建立一Window。是以Activity内部可以有多個Window。由于View的測量、布局、繪制隻是在View樹内進行的,是以一個Window内View的改動不會影響到另一個Window。Window是一個抽象類,它隻有一個實作類PhoneWindow。
  • WindowManager:WindowManager是Window的管理類。它不直接操作Window,而是操作Window内的DecorView。WindowManager是一個接口。它的具體實作類是WindowManagerImpl。
    public interface WindowManager{
        public void addView(View view, ViewGroup.LayoutParams params);
        public void updateViewLayout(View view, ViewGroup.LayoutParams params);
        public void removeView(View view);
    }
               
  • DecorView是View樹的頂級View,它是FrameLayout的子類。根據Activity設定的Theme,DecorView會有不同布局。但無論布局怎麼變,DecorView都有一個Id為R.id.content的FrameLayout。Activity.setContentView()方法就是在這個FrameLayout中添加子View。
  • ViewRootImpl是連接配接WindowManager和DecorView的紐帶,View的三大流程均是通過ViewRootImpl來完成的。

源碼解密

在Android插件化之啟動Activity中,我介紹過Activity的啟動流程。其中handleLaunchActivity()方法是啟動Activity的核心方法。本節就以它為切入點開始分析。

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        //省略代碼...

      	//performLaunchActivity
        Activity a = performLaunchActivity(r, customIntent);
        if (a != null) {
            r.createdConfig = new Configuration(mConfiguration);
            Bundle oldState = r.state;

            //handleResumeActivity
            handleResumeActivity(r.token, false, r.isForward,!r.activity.mFinished && !r.startsNotResumed);

            //省略代碼...
        }
    }
           

handleLaunchActivity()主要調用了兩個方法:performLaunchActivity()和handleResumeActivity()

  • performLaunchActivity:完成Activity的建立,以及調用Activity的 onCreate()和onStart()方法。
  • handleResumeActivity:調用Activity的onResume()方法,處理View的呈現。

performLaunchActivity

我們進入performLaunchActivity()方法,核心代碼如下:

private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
        ActivityInfo aInfo = r.activityInfo;
        ComponentName component = r.intent.getComponent();
        java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
      	//建立Activity
        Activity activity = mInstrumentation.newActivity(cl, component.getClassName(), r.intent);
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);

        if (activity != null) {
            //建立Context
            Context appContext = createBaseContextForActivity(r, activity);
            CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
            Configuration config = new Configuration(mCompatConfiguration);

            //調用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);

            //省略代碼...

            //調用Activity.onCreate()方法。
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
            r.activity = activity;

            if (!r.activity.mFinished) {
              	//調用Activity.onStart()方法。
                activity.performStart();
            }
        }
        r.paused = true;
        mActivities.put(r.token, r);
        return activity;
    }
           

performLaunchActivity()主要做了以下幾件事:

  1. 建立Activity。
  2. 建立Context。
  3. 調用Activity.attach(),建立Window,關聯WindowManager。
  4. 調用Activity.onCreate()。
  5. 調用Activity.onStart()。

attach

上面說了,在Activity.attach()方法執行時會建立Window并為Window關聯WindowManager。我們看一下僞代碼。

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) {
        attachBaseContext(context);
        //建立Window
        mWindow = new PhoneWindow(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);
        }
      	//設定UI線程
        mUiThread = Thread.currentThread();
        mMainThread = aThread;
        //關聯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();
    }
           

setContentView

順着流程圖,Activity.setContentView()方法會被調用。Activity.setContentView()内又直接調用了PhoneWindow.setContentView()。我們直接看PhoneWindow.setContentView()的源碼。

private DecorView mDecor;

    //setContentView傳過來的View會被add到mContentParent中。mContentParent的Id是R.id.content。
    private ViewGroup mContentParent;

    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        if (mContentParent == null) {
            installDecor();
        }else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
      	//省略代碼。。。
    }

    private void installDecor() {
        if (mDecor == null) {
            //初始化DecorView
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor);
            //省略代碼。。。
        }
    }
           

如果mContentParent為null,會執行installDecor()方法。generateDecor()源代碼很長,但是它的邏輯很簡單主要是根據Activity Theme的不同,初始化不同的布局。DecorView的布局雖然不同,但它們都一個Id為R.id.content的FrameLayout。Activity.setContentView()就是在這個FrameLayout中添加子View。

handleResumeActivity

performLaunchActivity()執行完後,緊接着執行handleResumeActivity()。

final void handleResumeActivity(IBinder token,
            boolean clearHide, boolean isForward, boolean reallyResume) {

        //處理Activity的onRestart onResume生命周期。
        ActivityClientRecord r = performResumeActivity(token, clearHide);

        if (r != null) {
            if (r.window == null && !a.mFinished) {
                r.window = r.activity.getWindow();
                View decor = r.window.getDecorView();
                //設定DecorView不可見
              	decor.setVisibility(View.INVISIBLE);

                ViewManager wm = a.getWindowManager();
                WindowManager.LayoutParams l = r.window.getAttributes();

                if (a.mVisibleFromClient) {
                    a.mWindowAdded = true;
                    //利用WindowManager添加DecorView。
                    wm.addView(decor, l);
                }
            }
            if (!r.activity.mFinished && r.activity.mDecor != null) {
                if (r.activity.mVisibleFromClient) {
                    //設定DecorView可見。
                    r.activity.makeVisible();
                }
            }

            //IPC調用,通知AMS Activity啟動完成。
            ActivityManagerNative.getDefault().activityResumed(token);

        } else {
            try {
                ActivityManagerNative.getDefault()
                    .finishActivity(token, Activity.RESULT_CANCELED, null, false);
            } catch (RemoteException ex) {
            }
        }
    }
           

handleResumeActivity()處理的事情比較多。我總結為以下幾個過程:

  1. 通過performResumeActivity()處理Activity的onRestart onResume的生命周期。
  2. 将DecorView設定為InVisible。
  3. 通過WindowManager.addView()将DecorView繪制完成。
  4. 将DecorView設定為Visiable。
  5. 通知AMS Activity啟動完成。

WindowManger.addView()

前面說過,WindowManger是一個抽象類,它的實作類是WindowManagerImpl。WindowManager.addView()封裝了View繪制的細節。我們着重看一下。

public final class WindowManagerImpl implements WindowManager {
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    private final Display mDisplay;
    private final Window mParentWindow;

    private IBinder mDefaultToken;

    public WindowManagerImpl(Display display) {
        this(display, null);
    }

    private WindowManagerImpl(Display display, Window parentWindow) {
        mDisplay = display;
        mParentWindow = parentWindow;
    }

    public void setDefaultToken(IBinder token) {
        mDefaultToken = token;
    }

    @Override
    //這裡的View是PhoneWindow内建立的DecorView。
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
        applyDefaultToken(params);
        mGlobal.addView(view, params, mDisplay, mParentWindow);
    }

    private void applyDefaultToken(@NonNull ViewGroup.LayoutParams params) {
        if (mDefaultToken != null && mParentWindow == null) {
            final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
            if (wparams.token == null) {
                wparams.token = mDefaultToken;
            }
        }
    }
}
           

WindowManagerImpl.addView會調用WindowManagerGlobal.addView()。在WindowManagerGlobal.addView()方法執行之前,會先執行applyDefaultToken()方法。這個方法其實是給傳進來的DecorView加一個身份辨別,表示這個DecorView屬于哪個Activity。這樣系統(WindowManagerService)才會知道要把DecorView繪制到哪個Activity。

我們繼續追蹤WindowManagerGlobal.addView(),僞代碼如下:

private final ArrayList<View> mViews = new ArrayList<View>();
    private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
    private final ArrayList<WindowManager.LayoutParams> mParams = new ArrayList<WindowManager.LayoutParams>();

    //這裡的View是PhoneWindow内建立的DecorView。
    public void addView(View view, ViewGroup.LayoutParams params,
            Display display, Window parentWindow) {

        final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
      	//省略代碼....

        root = new ViewRootImpl(view.getContext(), display);

       	//省略代碼...

        mViews.add(view);
        mRoots.add(root);
        mParams.add(wparams);

        root.setView(view, wparams, panelParentView);
    }
           

首先會建立ViewRootImpl,随後把View、ViewRootImpl、LayoutParams都儲存在List中,以供将來更新UI使用。它們的index值相同,這樣就三者就對應起來了。最後,調用ViewRootImpl.setView()方法。

ViewRootImpl.setView()

public class ViewRootImpl{

  	View mView;
  	//這裡的View是PhoneWindow内建立的DecorView。
  	public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
        synchronized (this) {
            if (mView == null) {
                mView = view;

                //省略代碼。。。

                // Schedule the first layout -before- adding to the window
                // manager, to make sure we do the relayout before receiving
                // any other events from the system.
                requestLayout();

                //省略代碼。。。

              	//IPC通信,通知WMS渲染。
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                            getHostVisibility(), mDisplay.getDisplayId(),
                            mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                            mAttachInfo.mOutsets, mInputChannel);

                //省略代碼。。。
            }
        }
    }

  	@Override
    public void requestLayout() {
        if (!mHandlingLayoutInLayoutRequest) {
            //檢查目前執行的線程是不是UI線程
            checkThread();
            mLayoutRequested = true;
            //處理DecorView的measure、layout、draw。
            scheduleTraversals();
        }
    }

    void checkThread() {
        if (mThread != Thread.currentThread()) {
            throw new CalledFromWrongThreadException(
                    "Only the original thread that created a view hierarchy can touch its views.");
        }
    }
}
           

ViewRootImpl.setView()的僞代碼有兩句,但我們隻關心requestLayout。因為mWindowSession.addToDisplay()就是通過IPC通知WMS去渲染,我們再去分析WMS意義已經不大了。requestLayout()方法首先會檢查目前執行的線程是不是UI線程,随後調用scheduleTraversals()。scheduleTraversals會把本次請求封裝成一個TraversalRunnable對象,這個對象最後會交給Handler去處理。最後ViewRootImpl.performTraversals()被調用。調用鍊如下:

final TraversalRunnable mTraversalRunnable = new TraversalRunnable();

void scheduleTraversals() {
    if (!mTraversalScheduled) {
        mTraversalScheduled = true;
        //省略代碼。。。
        mChoreographer.postCallback(Choreographer.CALLBACK_TRAVERSAL, mTraversalRunnable, null);
        //省略代碼。。。
    }
}

final class TraversalRunnable implements Runnable {
        @Override
        public void run() {
            doTraversal();
        }
    }

void doTraversal() {
    if (mTraversalScheduled) {
        mTraversalScheduled = false;
        mHandler.getLooper().getQueue().removeSyncBarrier(mTraversalBarrier);

      	//處理DecorView的measure、layout、draw。
        performTraversals();

        if (mProfile) {
            Debug.stopMethodTracing();
            mProfile = false;
        }
    }
}
           

performTraversals()主要是處理View樹的measure、layout、draw等流程。不清楚的同學可以去看《Android開發藝術探索》第四章,我在這裡就不繼續深入了。

總結

下面我回答文章前言部分提出的幾個問題。

  • 為什麼要有設計Window?

    要了解Window需要從面向對象的角度出發。

    1. 假如沒有Window,那Window管理View樹的代碼必然會放到Activity中。這樣Activity就變得十分龐大,這與我們前面說的Activity指揮官的角色相違背。
    2. 把View樹的管理工作封裝到Window後,在調用Dialog.show()、Dialog.hide()等Window切換時,Activity隻需要負責Window的顯示和隐藏即可。
    3. View的測量、布局、繪制隻是在View樹内進行的,把一個View樹封裝在一個Window中友善視圖管理。
  • 子線程真的不能更新UI嗎?

    更新視圖時,線程檢查是在ViewRootImpl的checkThread()中。ViewRootImpl的初始化是在Activity的onResume()方法之後。是以,如果有子線程在onResume之前更新UI是可以成功的。當然還有一種Hook ViewRootImpl的mThread的方法也可以更新UI。這裡不做介紹了。

  • Activity的onCreate方法為什麼無法擷取View的寬和高?

    這個問題和子線程不能更新UI的問題很像,也是方法執行時機的一個問題。View的measure、layout、draw。發生在Activity.onResume()之後,是以在onResume()之前都是無法擷取View的寬、高等資訊的。

文末

感謝大家關注我,分享Android幹貨,交流Android技術。

對文章有何見解,或者有何技術問題,都可以在評論區一起留言讨論,我會虔誠為你解答。

Android架構師系統進階學習路線、58萬字學習筆記、教學視訊免費分享位址:我的GitHub

Android面試黑洞:從Activity建立到View呈現中間發生了什麼?前言圖解Activity啟動到View顯現過程源碼解密總結