深入 Activity 三部曲(2)View 繪制流程之 DecorView 添加至窗口的過程

UI 優化系列專題,來聊一聊 Android 渲染相關知識,主要涉及 UI 渲染背景知識如何優化 UI 渲染兩部內容。


UI 優化系列專題
  • UI 渲染背景知識

View 繪制流程之 setContentView() 到底做了什么?
View 繪制流程之 DecorView 添加至窗口的過程
深入 Activity 三部曲(3)View 繪制流程
Android 之 LayoutInflater 全面解析
關于渲染,你需要了解什么?
Android 之 Choreographer 詳細分析

  • 如何優化 UI 渲染

Android 之如何優化 UI 渲染(上)
Android 之如何優化 UI 渲染(下)


在上一篇文章《View 加載過程之 setContentView() 到底做了什么 ?》,我們分析了 View 繪制流程的整個創建過程,再來簡單回顧下該內容。

  • 每個 Activity 都有一個關聯的 Window 對象,用來描述應用程序窗口,每個窗口內部又包含一個 DecorView 對象,DecorView 對象用來描述窗口的視圖 — xml 布局。通過 setContentView() 設置的 View 布局最終添加到 DecorView 的 content 容器中。

DecorView 作為 Activity 的最頂層視圖,它的整個創建過程我們已經知道了,那它又是如何添加至窗口中的呢?還是通過幾個問題來了解下今天要分析的內容。

  1. DecorView 添加至窗口的過程
  • View 繪制流程的開始時機?
  • WindowManager 是什么?它的作用有哪些?
  1. View 繪制流程
  • ViewRootImpl 是什么?它的核心工作是什么?
  • 為什么 View 的 requestLayout() 最終會執行到 ViewRootImpl 的 requestLayout() ?
  • View 繪制流程為什么一定要在 Main 線程?

如果以上問題你都能夠熟練并正確的回答出來,那么恭喜你可以直接跳過該篇文章。


View 的繪制時機

要分析 DecorView 如何添加到窗口上面的,我們要從 Activity 的創建過程開始說起,首先 Activity 的創建以及生命周期的調用,都是通過進程間通信完成的,在應用進程內完成調度任務的是 ActivityThread。ActivityThread 是我們應用進程的入口類(main 函數所在)。

在 ActivityThread 中,完成 Activity 創建任務的方法是 handleLaunchActivity 方法:

private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
    //... 省略

    // 獲取遠程WindowManagerService代理對象
    WindowManagerGlobal.initialize();

    // 創建Activity,并調用其onCreate方法
    // 創建PhoneWindow、DecorView等
    Activity a = performLaunchActivity(r, customIntent);

    if (a != null) {
        r.createdConfig = new Configuration(mConfiguration);
        reportSizeConfigurations(r);
        Bundle oldState = r.state;
        //回調Activity的onResume
        //也會完成 Actiivty UI 繪制流程
        handleResumeActivity(r.token, false, r.isForward,
                !r.activity.mFinished && !r.startsNotResumed, r.lastProcessedSeq, reason);
    }

    //... 省略

}

handleLaunchActivity 方法主要完成以下兩部分內容:

  • performLaunchActivity 方法,完成 Activity 的創建以及 onCreate 生命周期方法回調。

  • handleResumeActivity 方法,完成 onResume 生命周期方法回調后,發起 DecorView 向窗口添加過程,開始 View 繪制流程。

  1. performLaunchActivity 方法,Activity 的創建過程。
    private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {

    //... 省略

    try {
        // 通過反射創建當前Activity實例
        java.lang.ClassLoader cl = appContext.getClassLoader();
        activity = mInstrumentation.newActivity(
                cl, component.getClassName(), r.intent);
        StrictMode.incrementExpectedActivityCount(activity.getClass());
        r.intent.setExtrasClassLoader(cl);
        r.intent.prepareToEnterProcess();
        if (r.state != null) {
            r.state.setClassLoader(cl);
        }
    } catch (Exception e) {
         //... 省略
    }

    try {
        Application app = r.packageInfo.makeApplication(false, mInstrumentation);


        if (activity != null) {  
            //調用Activity的attach方法,其中PhoneWindow就是該該方法中被創建
            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.mCalled = false;
            // 回調Activity的onCreate方法
            if (r.isPersistable()) {
                mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
            } else {
                mInstrumentation.callActivityOnCreate(activity, r.state);
            }
           
            // 當前Activity賦值給保存它記錄的ActivityClientRecord
            r.activity = activity;
            r.stopped = true;
            if (!r.activity.mFinished) {
                // 回調Activity的onStart方法
                activity.performStart();
                r.stopped = false;
            }
            // Activity的狀態恢復onRestoreInstanceState方法被回調
            // 如果Bundle為null,表示無數據,是不會調用onRestoreInstanceState
            if (!r.activity.mFinished) {
                if (r.isPersistable()) {
                    if (r.state != null || r.persistentState != null) {
                        mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state,
                                r.persistentState);
                    }
                } else if (r.state != null) {
                    mInstrumentation.callActivityOnRestoreInstanceState(activity, r.state);
                }
            }
            // 回調Activity的onPostCreate方法
            if (!r.activity.mFinished) {
                activity.mCalled = false;
                if (r.isPersistable()) {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state,
                            r.persistentState);
                } else {
                    mInstrumentation.callActivityOnPostCreate(activity, r.state);
                }
                if (!activity.mCalled) {
                    throw new SuperNotCalledException(
                        "Activity " + r.intent.getComponent().toShortString() +
                        " did not call through to super.onPostCreate()");
                }
            }
        }
        r.paused = true;
        // 保存當前ActivityClientRecord
        mActivities.put(r.token, r);

    } catch (SuperNotCalledException e) {
        throw e;

    } catch (Exception e) {
          // ... 省略           
    }

       return activity;
    }

系統通過 ClassLoader 反射創建當前 Activity 實例,然后調用 Activity 的 attach 方法,還記的上篇文章《View 繪制流程之 setContentView 到底做了什么?》,PhoneWindow 就是在 attach 方法中被創建的。

attach 方法執行結束后,依次回調 Activity 生命周期函數:onCreate() —> onStart() —> onRestoreInstanceState()(注意該方法不是必須,取決于是否真的保存了數據,即 Bundle != null)—> onPostCreate()。

  1. handleResumeActivity 方法,View 在 Activity 的繪制起始點。

如果僅從名字來看,大家肯定會想到它最終會回調 Activity 的 onResume 生命周期方法。但實際它可能比我們想象的要復雜。

final void handleResumeActivity(IBinder token,
        boolean clearHide, boolean isForward, boolean reallyResume, int seq, String reason) {
    // 獲取當前Activity的ActivityClientRecord
    ActivityClientRecord r = mActivities.get(token);
    if (!checkAndUpdateLifecycleSeq(seq, r, "resumeActivity")) {
        return;
    }

    mSomeActivitiesChanged = true;

    // 執行Activity的onResume生命周期方法
    r = performResumeActivity(token, clearHide, reason);

    if (r != null) {
        final Activity a = r.activity;

        final int forwardBit = isForward ?
                WindowManager.LayoutParams.SOFT_INPUT_IS_FORWARD_NAVIGATION : 0;

        boolean willBeVisible = !a.mStartedActivity;
        if (!willBeVisible) {
            try {
                // 活動是否可見狀態
                willBeVisible = ActivityManager.getService().willActivityBeVisible(
                        a.getActivityToken());
            } catch (RemoteException e) {
                throw e.rethrowFromSystemServer();
            }
        }
        // 重點在這里
        if (r.window == null && !a.mFinished && willBeVisible) {
            //獲取當前PhoneWindow,就是在setContentView中創建的
            r.window = r.activity.getWindow();
            //獲取當前Activity的根視圖DecorView
            View decor = r.window.getDecorView();
            //設置DecorView不可見
            decor.setVisibility(View.INVISIBLE);
            //WindowManager是一個接口,實現類是WindowManagerImpl
            ViewManager wm = a.getWindowManager();
            WindowManager.LayoutParams l = r.window.getAttributes();
            //當前DecorView賦值給Activity
            a.mDecor = decor;
            l.type = WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
            l.softInputMode |= forwardBit;
            if (r.mPreserveWindow) {
                a.mWindowAdded = true;
                r.mPreserveWindow = false;
                // 首次創建此時,該DecorView還沒有關聯ViewRootImpl
                ViewRootImpl impl = decor.getViewRootImpl();
                if (impl != null) {
                    impl.notifyChildRebuilt();
                }
            }
            if (a.mVisibleFromClient) {
                if (!a.mWindowAdded) {
                    a.mWindowAdded = true;
                    // 這里將DecorView關聯到WindowManager
                    // 最后也會與ViewRootImpl進行關聯
                    wm.addView(decor, l);
                } else {
                    //如果Window已經被添加,則只需要更新屬性
                    a.onWindowAttributesChanged(l);
                }
            }
        } else if (!willBeVisible) {}} else {}
}

先來看下 onResume 生命周期方法的回調過程,performResumeActivity 方法如下:

public final ActivityClientRecord performResumeActivity(IBinder token,
        boolean clearHide, String reason) {
    //獲取當前Activity的ActivityClientRecord
    ActivityClientRecord r = mActivities.get(token);

    //判斷Activity沒有被Finish
    if (r != null && !r.activity.mFinished) {
        if (clearHide) {
            r.hideForNow = false;
            r.activity.mStartedActivity = false;
        }
        try {
            //onStateNotSave方法在onResume之前
            //表示當前Activity的狀態不在被保存
            //一般在onNewIntent方法或者onActivityResult時可能會使用
            r.activity.onStateNotSaved();
            //同上,這里是通知該Activity的Fragment
            r.activity.mFragments.noteStateNotSaved();
            if (r.pendingIntents != null) {
                deliverNewIntents(r, r.pendingIntents);
                r.pendingIntents = null;
            }
            if (r.pendingResults != null) {
                deliverResults(r, r.pendingResults);
                r.pendingResults = null;
            }
            //回調Activity的onResume方法
            r.activity.performResume();

            //... 省略

            r.paused = false;
            r.stopped = false;
            r.state = null;
            r.persistentState = null;
        } catch (Exception e) {
             //... 省略
        }
    }
    return r;
}
  • 注意 onStateNotSaved 方法,如果不是看源碼可能還真不一定知道它的存在,Activity 提供了onSaveInstanceState(Bundle outState) 幫助我們保存 Activity 相關狀態信息,onStateNotSaved 方法就是告知我們當前 Activity 相關狀態信息不再被保存。

  • performResume 方法最終回調 Activity 的 onResume 方法。

不過,我們今天要分析的重點是 DecorView 如何添加到窗口中的?回到
handleResumeActivity 方法,重點部分來了(注意查看上面貼出源碼):

//獲取當前Activity中PhoneWindow,就是在setContentView時關聯的
r.window = r.activity.getWindow();
//獲取當前Activity的根視圖DecorView
View decor = r.window.getDecorView();
//設置DecorView不可見
decor.setVisibility(View.INVISIBLE);
//WindowManager是一個接口,實現類是WindowManagerImpl
//WindowManager是在PhoneWindow創建后,調用其setWindowManager
//被添加,這里直接通過getWindowManager獲取
ViewManager wm = a.getWindowManager();
WindowManager.LayoutParams l = r.window.getAttributes();

getWindow() 就是在 Activity 的 attach 方法關聯的 PhoneWindow。

  • getDecorView(),獲取當前 PhoneWindow 中 DecorView,將其置為不可見狀態。

  • getWindowManager(),獲取當前 PhoneWindow 的
    WindowManager。調用 WindowManager 的 addView 方法,將 DecorView 添加到 WindowManager 中。

WindowManager 是何妨神圣?翻看它的源碼發現是一個接口,繼承自 ViewManager:

public interface WindowManager extends ViewManager {  
         //... 省略
}

ViewManager 中主要包含三個操作 View 的關鍵方法:

public interface ViewManager {

  //添加View到Window
  public void addView(View view, ViewGroup.LayoutParams params);
  //更新View的LayoutParams
  public void updateViewLayout(View view, ViewGroup.LayoutParams params);
  //在Window中移除該View
  public void removeView(View view);
}

那它的實現類是誰呢,其實大家根據 Google 工程師的命名規范也大概能猜到,沒錯 WindowManagerImpl,它是在哪里被創建的呢? 前面我們也有分析過 PhoneWindow 是在 Activity 的 attach 方法中被創建,而 WindowManager 就是在 PhoneWindow 被創建后,調用它的 setWindowManager 方法完成:

// PhoneWindow 的 setWindowManager 方法
public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
        boolean hardwareAccelerated) {
    if (wm == null) {
        //這個是SystemServerRegistry中注冊的WindowManagerImpl,即應用進程窗口管理者WindowManager。
        wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
    }
    //為當前PhoneWindow創建本地WindowManagerImpl
    mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
}

看下代表 Activity 級別的窗口管理者 WindowManager 創建過程:

public WindowManagerImpl createLocalWindowManager(Window parentWindow) {
    //為當前 PhoneWindow 關聯一個 WindowManagerImpl
    return new WindowManagerImpl(mContext, parentWindow);
}

驗證下我們的猜測,WindowManagerImpl 是否實現了 WindowManager ?它的聲明如下:

public final class WindowManagerImpl implements WindowManager {
    //實際WindowManagerImpl中的操作代理給了WindowManagerGlobal
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();

    //... 省略
}

系統為每個 PhoneWindow 都關聯一個 WindowManagerImpl。這里要說明的是,在 Android 中,Window(PhoneWindow) 是 View 的容器,每個窗口都會關聯一個 Surface。而 WindowManager 則負責管理這些窗口,并把它們的數據傳遞給 SurfaceFlinger。

即 addView 方法實際調用到 WindowManagerImpl 中:

public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
    applyDefaultToken(params);
    //丟給了WindowManagerGlobal的addView
    mGlobal.addView(view, params, mContext.getDisplay(), mParentWindow);
}

mGlobal 是 WindowManagerGlobal。它又是何妨神圣?

  • 實際上 WindowManager 管理窗口的操作又委托給了 WindowManagerGlobal,WindowMnagerGlobal 是一個單例,即應用進程內僅有一個,這好像也是命名 Global 的原因吧。

WindowManagerGlobal 中主要包含幾個比較重要的 Window 管理容器:

//保存DecorView
private final ArrayList<View> mViews = new ArrayList<View>();
//保存DecorView對應的ViewRootImpl
private final ArrayList<ViewRootImpl> mRoots = new ArrayList<ViewRootImpl>();
//保存DecorView的LayoutParams
private final ArrayList<WindowManager.LayoutParams> mParams =
        new ArrayList<WindowManager.LayoutParams>();
//保存已經被removeView的DecorView
private final ArraySet<View> mDyingViews = new ArraySet<View>();

WindowManagerImpl 將添加操作委托給 WindowManagerGlobal, addView 方法如下:

//view是DecorView
public void addView(View view, ViewGroup.LayoutParams params,
                    Display display, Window parentWindow) {
    if (view == null) {
        //DecorView不能為null
        throw new IllegalArgumentException("view must not be null");
    }
    if (display == null) {
        //每個Activity都會關聯一個Display
        //通過ContextImpl創建
        throw new IllegalArgumentException("display must not be null");
    }
    if (!(params instanceof WindowManager.LayoutParams)) {
        //必須是WindowManager.LayoutParams
        throw new IllegalArgumentException("Params must be WindowManager.LayoutParams");
    }

    final WindowManager.LayoutParams wparams = (WindowManager.LayoutParams) params;
    if (parentWindow != null) {
        parentWindow.adjustLayoutParamsForSubWindow(wparams);
    } else {
        // If there's no parent, then hardware acceleration for this view is
        // set from the application's hardware acceleration setting.
        final Context context = view.getContext();
        //應用是否配置了硬件加速
        if (context != null
                && (context.getApplicationInfo().flags
                & ApplicationInfo.FLAG_HARDWARE_ACCELERATED) != 0) {
            //硬件加速
            wparams.flags |= WindowManager.LayoutParams.FLAG_HARDWARE_ACCELERATED;
        }
    }

    //重要的ViewRootImpl
    //繪制流程的真正發起點
    ViewRootImpl root;
    View panelParentView = null;

    synchronized (mLock) {
        // ... 省略

        //判斷DecorView是否已經添加到Window
        int index = findViewLocked(view, false);
        //如果已經存在該DecorView
        if (index >= 0) {
            //此時它必須是已經被移除Window,在mDyingViews容器
            if (mDyingViews.contains(view)) {
                //如果存在,就將這個已經存在的view對應的window移除
                mRoots.get(index).doDie();
            } else {
                //否則說明已經被添加,不需要再添加
                throw new IllegalStateException("View " + view
                        + " has already been added to the window manager.");
            }
        }

        // ... 省略

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

        view.setLayoutParams(wparams);
        //將DecorView保存在Window中
        mViews.add(view);
        //保存當前ViewRootImpl
        mRoots.add(root);
        mParams.add(wparams);

        try {
            //調用ViewRootImpl的setView
            //參數DecorView
            root.setView(view, wparams, panelParentView);
        } catch (RuntimeException e) {
            // ... 省略
        }
    }
}
  1. 注意 findViewLocked 方法,如果 DecorView 被重復添加將會拋異常。
throw new IllegalStateException("View " + view
                    + " has already been added to the window manager.");
  1. 創建 ViewRootImpl 對象,它最后會與當前 DecorView 進行關聯,作為 DecorView 的 parent。ViewRootImpl 是 View 繪制流程的真正發起點。后面會分析到。
  • 在 Android 中,所有的元素都在 Surface 這張畫紙上進行繪制和渲染,普通 View(例如非 SurfaceView 或 TextureView) 是沒有 Surface 的,一般 Activity 包含多個 View 形成 View Hierachy 的樹形結構,只有最頂層的 DecorView 才是對 WindowManagerService “可見的”。而為普通 View 提供 Surface 的正是 ViewRootImpl。

保存 DecorView 和對應的 ViewRootImpl 在 WindowManager 中,繼續跟蹤最后 root.setView(view, wparams, panelParentView) 方法

 public void setView(View view, WindowManager.LayoutParams attrs, View panelParentView) {
    synchronized (this) {
        // 判斷是否已經關聯過View
        if (mView == null) {
            // DecorView賦值到ViewRootImpl中
            mView = view;

            // 獲取當前屏幕最新的狀態,并且給當前DecorView注冊屏幕監聽,用來檢測滅屏和亮屏
            mAttachInfo.mDisplayState = mDisplay.getState();
            mDisplayManager.registerDisplayListener(mDisplayListener, mHandler);

            // ... 省略

            // 獲取屏幕兼容模式,一般情況下不考慮 mTranslator是空的
            CompatibilityInfo compatibilityInfo =
                    mDisplay.getDisplayAdjustments().getCompatibilityInfo();
            mTranslator = compatibilityInfo.getTranslator();

            // If the application owns the surface, don't enable hardware acceleration
            if (mSurfaceHolder == null) {
                enableHardwareAcceleration(attrs);
            }

             // ... 省略

            mAdded = true;
            int res; /* = WindowManagerImpl.ADD_OKAY; */

            // 繪制流程真正開始的地方
            requestLayout();
           
            try {
                mOrigWindowType = mWindowAttributes.type;
                mAttachInfo.mRecomputeGlobalAttributes = true;
                collectViewAttributes();
                // mWindowSession本質是一個Binder,它的實際類型是Session,這里獲取到的是遠程代理對象
                // 發送跨進程消息,將DecorView顯示出來
                // mWindow是ViewRootImpl中的一個靜態類,這個類可以用來和WindowSession交互,就等于間接在和WindowManagerService交互
                res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                        getHostVisibility(), mDisplay.getDisplayId(),
                        mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                        mAttachInfo.mOutsets, mInputChannel);
            } catch (RemoteException e) {
                // ... 省略
            } finally {
                if (restore) {
                    attrs.restore();
                }
            }

            // 處理WindowManagerService的返回結果,這里你會看到一些非常熟悉的異常
            // 一大堆兒關于Window操作異常
            if (res < WindowManagerGlobal.ADD_OKAY) {
                mAttachInfo.mRootView = null;
                mAdded = false;
                mFallbackEventHandler.setView(null);
                unscheduleTraversals();
                setAccessibilityFocus(null, null);
                switch (res) {
                    case WindowManagerGlobal.ADD_BAD_APP_TOKEN:
                    case WindowManagerGlobal.ADD_BAD_SUBWINDOW_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not valid; is your activity running?");
                    case WindowManagerGlobal.ADD_NOT_APP_TOKEN:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- token " + attrs.token
                                + " is not for an application");
                    case WindowManagerGlobal.ADD_APP_EXITING:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- app for token " + attrs.token
                                + " is exiting");
                    case WindowManagerGlobal.ADD_DUPLICATE_ADD:
                        throw new WindowManager.BadTokenException(
                                "Unable to add window -- window " + mWindow
                                + " has already been added");
                    case WindowManagerGlobal.ADD_STARTING_NOT_NEEDED:
                        // Silently ignore -- we would have just removed it
                        // right away, anyway.
                        return;
                    case WindowManagerGlobal.ADD_MULTIPLE_SINGLETON:
                        throw new WindowManager.BadTokenException("Unable to add window "
                                + mWindow + " -- another window of type "
                                + mWindowAttributes.type + " already exists");
                    case WindowManagerGlobal.ADD_PERMISSION_DENIED:
                        throw new WindowManager.BadTokenException("Unable to add window "
                                + mWindow + " -- permission denied for window type "
                                + mWindowAttributes.type);
                    case WindowManagerGlobal.ADD_INVALID_DISPLAY:
                        throw new WindowManager.InvalidDisplayException("Unable to add window "
                                + mWindow + " -- the specified display can not be found");
                    case WindowManagerGlobal.ADD_INVALID_TYPE:
                        throw new WindowManager.InvalidDisplayException("Unable to add window "
                                + mWindow + " -- the specified window type "
                                + mWindowAttributes.type + " is not valid");
                }
                throw new RuntimeException(
                        "Unable to add window -- unknown error code " + res);
            }

            // ... 省略

            // 將ViewRootImpl添加到DecorView
            // 這也是后續調用View.requestLayout,最終會調用到ViewRootImpl的requestLayout
            // 調用View的requestLayout會不斷的調用parent.requestLayout,實際最終走到ViewRootImpl的requestLayout
            view.assignParent(this);

           // ... 省略
        }
    }
}
  1. requestLayout 繪制流程的真正起始方法:
  // 這個是不是很熟悉
  @Override
  public void requestLayout() {
      if (!mHandlingLayoutInLayoutRequest) {
          //檢查操作線程
          //Android中檢查UI繪制流程是否在主線程是在ViewRootImpl中
          checkThread();
          mLayoutRequested = true;
          scheduleTraversals();
      }
  }

注意 checkThread(),相信每個 Android 開發人員都曾遇到過在子線程更新 View 操作會拋異常。

void checkThread() {
    // 這就是為什么在子線程對View繪制過程操作會拋異常
    if (mThread != Thread.currentThread()) {
        throw new CalledFromWrongThreadException(
                "Only the original thread that created a view hierarchy can touch its views.");
    }
}

然后 scheduleTraversals 方法發送 Runnable 消息到主線程,完成 View 繪制的三大流程:measure、layout、draw。這部分內容在下節詳細介紹。

  1. mWindowSession 本質是一個 Binder 對象,它的實際類型是 Session。每個應用進程都會對應一個 Session。WindowManagerService(WMS) 保存這些 Session 用來記錄所有向 WMS 提出窗口管理服務的客戶端。
//通過WindowManagerService遠程調用為應用進程創建一個Session對象
public IWindowSession openSession(IWindowSessionCallback callback, IInputMethodClient client,
        IInputContext inputContext) {
    if (client == null) throw new IllegalArgumentException("null client");
    if (inputContext == null) throw new IllegalArgumentException("null inputContext");

    //每個應用進程都關聯一個Session
    Session session = new Session(this, callback, client, inputContext);
    return session;
}
  1. mWindow 是 ViewRootImpl 靜態內部類 W,內部通過弱引用持有當前 ViewRootImpl,它將作為客戶端保存在 WMS 中,用來和 WindowSession 交互,實際間接在與 WMS 交互。
//mWindow 是 ViewRootImpl 的靜態內部類
//內部持有當前ViewRootImpl
static class W extends IWindow.Stub {
    //弱引用持有當前ViewRootImpl
    //因為它將被保存在WindowManagerService
    private final WeakReference<ViewRootImpl> mViewAncestor;
    private final IWindowSession mWindowSession;

    W(ViewRootImpl viewAncestor) {
        mViewAncestor = new WeakReference<ViewRootImpl>(viewAncestor);
        mWindowSession = viewAncestor.mWindowSession;
    }

     // ... 省略

    /**
     * View 顯示狀態發生變化
     * 重新執行scheduleTraversals遍歷過程
     */
    public void dispatchAppVisibility(boolean visible) {
        final ViewRootImpl viewAncestor = mViewAncestor.get();
        if (viewAncestor != null) {
            viewAncestor.dispatchAppVisibility(visible);
        }
    }


   /**
    * 熟悉的Window焦點發生變化 
    */
    public void windowFocusChanged(boolean hasFocus, boolean inTouchMode) {
        final ViewRootImpl viewAncestor = mViewAncestor.get();
        if (viewAncestor != null) {
            //最終回到View的windowFocusChanged方法
            //和Activity的windowFocusChanged方法
            viewAncestor.windowFocusChanged(hasFocus, inTouchMode);
        }
    }

    // ... 省略
}

簡單看下它在 WindowManagerService 中的保存:

//mWindowMap是HashMap
//client是應用進程ViewRootImpl中W(mWindow)靜態內部類,這里持有需要窗口服務的代理類
//win是WindowState,保存窗口信息,在WMS用于描述一個窗口
mWindowMap.put(client.asBinder(), win);
  1. 注意看 if ( res < WindowManagerGlobal.ADD_OKAY ),這里是不是有很多眼熟的異常信息,一大堆兒異常撲面而來。相信在你的 Bug 記錄中它們肯定出現過。

  2. 方法最后 view.assignParent(this),為什么 view 的 requestLayout 方法,最終會調用到 ViewRootImpl 的 requestLayout 方法。答案就在這里。

void assignParent(ViewParent parent) {
    if (mParent == null) {
        //將ViewRootImpl作為DecorView的parent
        mParent = parent;
    } else if (parent == null) {
        mParent = null;
    } else {
        throw new RuntimeException("view " + this + " being added, but"
                + " it already has a parent");
    }
}

將 ViewRootImpl 作為 DecorView 的 parent。這就是為什么調用 View 的 requestLayout 方法后最終會走到 ViewRootImpl 的 requestLayout 方法。

View 的 requestLayout() 會不斷調用其 Parent 的 requestLayout(),最后調用到 DecorView 的 Parent,此時 Parent 就是 ViewRootImpl。到了 ViewRootImpl 的 requestLayout() 就會重新發起繪制流程。

至此,我們可以回答文章開頭提出的相關問題了。通過一張圖來了解下 DecorView 添加至窗口的過程。

image.png
  • View 在 Activity 的繪制時機是在生命周期函數 onResume 之后,真正開始的方法是在 ViewRootImpl 的 requestLayout()。

  • WindowManager 只是一個接口,定義了 Window 操作的基礎 API,實現類是 WindowManagerImpl;每個 PhoneWindow 都會關聯一個 WindowManagerImpl,實際管理工作又委托給了 WindowManagerGlobal,負責管理應用進程所有 Window(實際是 DecorView)。

  • ViewRootImpl 會作為 DecorView 的 parent,View 的 requestLayout 方法會一直調用 parent 的 requestLayout()。即最終會調用到 ViewRootImpl 的 requestLayout 方法。

  • 在 requestLayout 方法會去檢查當前執行 View 繪制的線程,如果不是 Main 線程就會拋出異常。


以上便是個人在學習 DecorView 添加至 Window 過程的學習和體會,文中如有不妥或有更好的分析結果,歡迎大家指出。

文章如果對你有幫助,請留個贊吧!

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
禁止轉載,如需轉載請通過簡信或評論聯系作者。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,345評論 6 531
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,494評論 3 416
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事?!?“怎么了?”我有些...
    開封第一講書人閱讀 176,283評論 0 374
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 62,953評論 1 309
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,714評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,186評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,255評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,410評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,940評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,776評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,976評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,518評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,210評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,642評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,878評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,654評論 3 391
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,958評論 2 373

推薦閱讀更多精彩內容