Android中視窗的建立過程

小編發表於2016-12-30

WindowManagerService的角度來看,標題中所說的“視窗”並非Window類,而是一個View類。單從語義來講,視窗就是使用者所看到的螢幕上的某個獨立的介面,比如一個Activity介面,一個對話方塊等等。
Window類是一個頂級視窗外觀和行為策略的抽象基類。它只是提供標準的UI策略,如背景,標題區域,預設鍵處理等。

視窗的型別

Framework定義了三種視窗型別,三種型別的定義在WindowManager類中。

  • 第一種為應用視窗。所謂的應用視窗是指該視窗對應一個Activity,由於載入Activity是由AmS完成的,因此,對於應用程式來講,要建立一個應用類視窗,只能在Activity內部完成。
  • 第二種是子視窗。所謂的子視窗是指,該視窗必須有一個父視窗,父視窗可以是一個應用型別視窗,也可以是任何其他型別的視窗。比如PopupWindow、OptionMenu
  • 第三類是系統視窗。系統視窗不需要對應任何Activity,也不需要有父視窗。對於應用程式而言,理論上是無法建立系統視窗的,因為所有的應用程式都沒有這個許可權,然而系統程式卻可以建立系統視窗。

建立應用視窗

Android程式啟動流程一文中已經描述了App程式的啟動、建立Application,以及例項化Activity。

帶著疑問而來,Activity直觀提供給開發者的是onCreate()/onResume()等回撥方法,通過setContentView()設定需要顯示的佈局,那麼View是怎麼顯示到螢幕中的?為什麼在onCreate方法中獲取不到View的寬和高呢?

看完以下內容,將會豁然開朗。

  1. ActivityThread例項化Activity,接著呼叫Activityattach方法,此方法的作用①設定Activity的內部變數,②為此Activity建立Window物件

    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) {
    
         //省略設定內部變數程式碼
    
         mWindow = new PhoneWindow(this);
         mWindow.setCallback(this);
         //給mWindow中的mWindowManager變數賦值
         mWindow.setWindowManager(
             (WindowManager)context.getSystemService(Context.WINDOW_SERVICE),
             mToken, mComponent.flattenToString(),
             (info.flags & ActivityInfo.FLAG_HARDWARE_ACCELERATED) != 0);
         mWindowManager = mWindow.getWindowManager();
    }複製程式碼
  2. 建立好Window物件後,需要給Window物件中的mWindowManager變數賦值,該變數的型別是WindowManager類 。每一個Window內部都有一個WindowManager物件。而WindowManager只是一個介面類,真正的實現是WindowManagerImpl

    public void setWindowManager(WindowManager wm, IBinder appToken, String appName,
             boolean hardwareAccelerated) {
         if (wm == null) {
             wm = (WindowManager)mContext.getSystemService(Context.WINDOW_SERVICE);
         }
         //這裡可以看出mWindowManager是WindowManagerImpl型別。
         mWindowManager = ((WindowManagerImpl)wm).createLocalWindowManager(this);
    }複製程式碼
  3. 接下來ActivityThread呼叫了performLaunchActivity(),內部呼叫了 callActivityOnCreate(),輾轉呼叫到ActivityonCreate()。感覺重見天日一般,此方法對於我們來說再熟悉不過了。此時我們毫不猶豫呼叫了setContentView();實際上呼叫了Window物件setContentView()

    public void setContentView(@LayoutRes int layoutResID) {
       getWindow().setContentView(layoutResID);
    }複製程式碼

    接著繼續分析Window中如何把一個layout.xml檔案作為Window介面。

  4. Window的具體實現是PhoneWindow,PhoneWindow中setContentView程式碼如下:

    @Override
    public void setContentView(View view) {
       setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
    }
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
       if (mContentParent == null) {
           installDecor();
       } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           mContentParent.removeAllViews();
       }
    
       if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
           view.setLayoutParams(params);
           final Scene newScene = new Scene(mContentParent, view);
           transitionTo(newScene);
       } else {
           mContentParent.addView(view, params);
       }
    }複製程式碼

    ①首先呼叫installDecor()方法,建立DecorView。
    ②而後呼叫generateLayout(DecorView decor)建立mContentParent,並把它加入到DecorView中。
    ③給 mContentParent 變數賦值,其值是通過呼叫ViewGroup contentParent=(ViewGroup)findViewById(ID_ANDROID_CONTENT)獲 得 的 ,ID_ANDROID_CONTENT 正是 id=contentFrameLayout
    ④安裝完視窗修飾後,就可以把使用者介面layout.xml檔案新增到視窗修飾中,這是通過在setContentView()中 呼叫inflate()方法完成的,該方法的第二個引數正是mContentParent,即 id=contentFrameLayout
    ⑤最後,回撥 cb.onContentChanged()方法,通知應用程式視窗內容發生了改變,因為從無到有了。而cb正是Activity自身,因為Activity實現了 Window.CallBack介面,並且在attach()方法中將自身作為Window物件的Callback介面實現。

    Android中視窗的建立過程
    Activity的視窗組成

    至此,僅僅是建立了DecorView,並新增了mContentParentDecorView還沒有被WindowManager正式addView

  5. 接下來ActivityThread呼叫了handleResumeActivity(),首先呼叫performResumeActivity(),輾轉呼叫了ActivityonResume(),接著會呼叫ActivitymakeVisible()。該方法及後續的各種呼叫將完成真正的把視窗新增進WmS之中。

    void makeVisible() {
        if (!mWindowAdded) {
            ViewManager wm = getWindowManager();
            wm.addView(mDecor, getWindow().getAttributes());
            mWindowAdded = true;
        }
        mDecor.setVisibility(View.VISIBLE);
    }複製程式碼
  6. 在第2點中講到WindowManager只是一個介面類,真正的實現是WindowManagerImpl,因此真正的addView操作就在此類中。然而它又交給了WindowManagerGlobal處理。
    private final WindowManagerGlobal mGlobal = WindowManagerGlobal.getInstance();
    @Override
    public void addView(@NonNull View view, @NonNull ViewGroup.LayoutParams params) {
       mGlobal.addView(view, params, mDisplay, mParentWindow);
    }複製程式碼
  7. WindowManagerGlobaladdView過程,一個應用程式內部無論有多少個Activity, 但只有一個WindowManagerGlobal物件在 WindowManagerGlobal類中維護三個集合,用於儲存該應用程式中所擁有的視窗的狀態。而後呼叫setView(),完成最後的、真正意義上的新增工作。

    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>();
    public void addView(View view, ViewGroup.LayoutParams params,
         Display display, Window parentWindow) {
         //省略檢查引數合法性的程式碼
         ViewRootImpl root;
         View panelParentView = null;
    
         synchronized (mLock) {
             //省略部分程式碼
             root = new ViewRootImpl(view.getContext(), display);
             view.setLayoutParams(wparams);
    
             mViews.add(view);
             mRoots.add(root);
             mParams.add(wparams);
         }
    
         // do this last because it fires off messages to start doing things
         try {
             root.setView(view, wparams, panelParentView);
         } catch (RuntimeException e) {
             //省略
             throw e;
         }
    }複製程式碼
  8. setView方法篇幅相對較長,其主要執行的操作有
    ①給 ViewRoot的重要變數賦值;
    ②呼叫requestLayout(),發出介面重繪請求;進而執行scheduleTraversals(),進行View的繪製工作。
    ③)呼叫sWindowSession.addToDisplay(),通知WmS新增視窗。

    res = mWindowSession.addToDisplay(mWindow, mSeq, mWindowAttributes,
                             getHostVisibility(), mDisplay.getDisplayId(),
                             mAttachInfo.mContentInsets, mAttachInfo.mStableInsets,
                             mAttachInfo.mOutsets, mInputChannel);複製程式碼

    mWindowSession的型別是IWindowSession,它是一個Binder物件,真正的實現類是Session,也就是說Window的新增過程是一次IPC呼叫。新增請求交給WmS去處理了。


到此為止,從客戶端的角度來講,已經完成了視窗建立的全部工作。從而介面才會呈現到使用者面前。可見在onCreate的時候View並沒有做測量、佈局、繪製操作,因此無法獲取到寬高資料。

希望後續有時間補一張流程圖。

篇幅較長,很感謝你能從頭閱讀完。具體內容可參閱《Android核心剖析》

相關文章