Android View 原始碼解析(一) - setContentView

Android架構發表於2019-01-30

本系列主要是探討View的繪製過程及部分相關的實現機制的原始碼分析

setContentView分析

相關關係

Android View 原始碼解析(一) -  setContentView

Activity中有Window成員 例項化為PhoneWindow PhoneWindow是抽象Window類的實現類

Window提供了繪製視窗的通用API PhoneWindow中包含了DecorView物件 是所有視窗(Activity介面)的根View

具體的構如下

Android View 原始碼解析(一) -  setContentView

具體的可以通過hierarchyviewer工具分析一下

PhoneWindow的setContentView分析

Window類的setContentView方法 而Window的setContentView方法是抽象的 所以檢視PhoneWindow的setContentView()

  1. setContentView方法
  // This is the view in which the window contents are placed. It is either
  // mDecor itself, or a child of mDecor where the contents go.
  private ViewGroup mContentParent;

  @Override
  public void setContentView(int layoutResID) {
      // Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
      // decor, when theme attributes and the like are crystalized. Do not check the feature
      // before this happens.
      if (mContentParent == null) {
          //第一次呼叫
          //下面會詳細分析
          installDecor();
      } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
          //移除該mContentParent下的所有View
          //又因為這個的存在  我們可以多次使用setContentView()
          mContentParent.removeAllViews();
      }
      //判斷是否使用了Activity的過度動畫
      if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
        //設定動畫場景
          final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                  getContext());
          transitionTo(newScene);
      } else {
          //將資原始檔通過LayoutInflater物件裝換為View樹
          //在PhoneWindow的建構函式中 mLayoutInflater = LayoutInflater.from(context);
          mLayoutInflater.inflate(layoutResID, mContentParent);
      }

      //View中
      /**
       * Ask that a new dispatch of {@link #onApplyWindowInsets(WindowInsets)} be performed.
       */
      // public void requestApplyInsets() {
      //     requestFitSystemWindows();
      // }
      mContentParent.requestApplyInsets();
      final Callback cb = getCallback();
      if (cb != null && !isDestroyed()) {
          cb.onContentChanged();
      }
  }

  @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 {
        //已經為View 直接使用View的addView方法追加到當前mContentParent中
          mContentParent.addView(view, params);
      }
      mContentParent.requestApplyInsets();
      final Callback cb = getCallback();
      //呼叫CallBack介面的onContentChange來通知Activity元件檢視發生了變化
      if (cb != null && !isDestroyed()) {
          cb.onContentChanged();
      }
  }
複製程式碼
  1. installDecor方法
  //擷取部分主要分析程式碼
  private void installDecor() {
      if (mDecor == null) {
          //如果mDecor為空則建立一個DecorView例項
          // protected DecorView generateDecor() {
          //   return new DecorView(getContext(), -1);
          // }
          mDecor = generateDecor();  
          mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
          mDecor.setIsRootNamespace(true);
          if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
              mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
          }
      }
      if (mContentParent == null) {
          //根據視窗的風格修飾 選擇對應的修飾佈局檔案 將id為content的FrameLayout賦值於mContentParent
          mContentParent = generateLayout(mDecor);
          ...
        }
  }
複製程式碼
  protected ViewGroup generateLayout(DecorView decor) {
       // Apply data from current theme.
       //根據當前style修飾相應樣式

       TypedArray a = getWindowStyle();

       ...
       //一堆if判斷

       // 增加視窗修飾

       int layoutResource;
       int features = getLocalFeatures();

       ...
       //根據features選擇不同的窗簾修飾佈局檔案得到
       //把選中的視窗修飾佈局檔案新增到DecorView中, 指定contentParent的值
       View in = mLayoutInflater.inflate(layoutResource, null);
       decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
       mContentRoot = (ViewGroup) in;

       ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
       if (contentParent == null) {
           throw new RuntimeException("Window couldn't find content container view");
       }

       ...
       return contentParent;
   }
複製程式碼

該方法的主要功能為 根據視窗的style為該視窗選擇不同的視窗根佈局檔案 將mDecor作為根檢視將視窗布局新增,獲取id為content的FrameLayout返回給mContentParent物件 實質為闡釋mDecor和mContentParent物件

  1. (擴充套件)關於設定Activity屬性需要在setContentView方法之前呼叫的問題

在設定Activity屬性的時候 比如requestWindowFeature(Window.FEATURE_NO_TITLE) 需要在setContentView方法之前呼叫

  public boolean requestFeature(int featureId) {
      if (mContentParent != null) {
          throw new AndroidRuntimeException("requestFeature() must be called before adding content");
      }
      ...
  }
複製程式碼
  1. onContentChanged方法

在PhoneWindow中沒有重寫getCallback相關方法 而在Window類下

  /**
   * Return the current Callback interface for this window.
   */
  public final Callback getCallback() {
      return mCallback;
  }
複製程式碼

mCallback相關的賦值方法

  /**
   * Set the Callback interface for this window, used to intercept key
   * events and other dynamic operations in the window.
   *
   * @param callback The desired Callback interface.
   */
  public void setCallback(Callback callback) {
      mCallback = callback;
  }
複製程式碼

setCallback方法在Activity中被使用

  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.setCallback(this);
        ...
  }
複製程式碼

說明Activity實現了Window的CallBack介面 然後在Activity中找到onContentChanged方法

  public void onContentChanged() {
  }
複製程式碼

對空方法. 說明在Activity的佈局改動時 (setContentView或者addContentView 方法執行完畢後會呼叫改方法) 所以各種View的findViewById方法什麼的可以放在這裡

  1. setContentView原始碼總結
  • 建立一個DecorView的物件mDector 該mDector將作為整個應用視窗的根檢視
  • 根據根據Feature等style theme建立不同的視窗修飾佈局檔案 並且通過findViewById獲取Activity佈局檔案該存放的地方
  • 將Activity的佈局檔案新增至id為content的FrameLayout內
  • 執行到當前頁面還沒有顯示出來
  1. Activity頁面顯示

我們都知道Activity的實際開始於ActivityThread的main方法 當該方法調運完之後會呼叫該類的performLaunchActivity方法來建立要啟動的Activity元件 這個過程中還會為該Activity元件建立視窗物件和檢視物件 當元件建立完成後用過呼叫該類的handleResumeActivity方法將其啟用

  final void handleResumeActivity(IBinder token,
             boolean clearHide, boolean isForward, boolean reallyResume) {
               ...
             if (!r.activity.mFinished && willBeVisible
                     && r.activity.mDecor != null && !r.hideForNow) {
                 ...
                 if (r.activity.mVisibleFromClient) {
                     r.activity.makeVisible();
                     //這裡這裡 通過呼叫Activity的makeVisible方法來顯示我們通過setContentView建立的mDecor
                 }
                 ...
             }
         } else {
           ...
         }
     }
複製程式碼
  //Activity的makeVisible方法
  void makeVisible() {
       if (!mWindowAdded) {
           ViewManager wm = getWindowManager();
           wm.addView(mDecor, getWindow().getAttributes());
           mWindowAdded = true;
       }
       mDecor.setVisibility(View.VISIBLE);
   }
複製程式碼

至此通過setContentView方法設定的頁面才最後顯示出來

相關文章