基於 Android Q 10.0 系統 setContentView源碼解析

setContentView源碼解析

總結一下代碼邏輯:

1 Activity的PhoneWindow屬性創建DecorView contentParent

2 LayoutInflater解析XML文件遍歷子view創建根節點(也就是我們的contentView)

1 MainActivity.java

public class MainActivity extends AppCompatActivity {

    public static final String TAG = "_MainActivity_";
    private TextView mTv;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        //我們研究的就是這個setContentView
        setContentView(R.layout.activity_main);
        mTv = findViewById(R.id.tv);
     
    }
}

2 AppCompatActivity.java

@Override
public void setContentView(@LayoutRes int layoutResID) {
    getDelegate().setContentView(layoutResID);
}

@NonNull
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }

3 AppCompatDelegate.java

 @NonNull
    public static AppCompatDelegate create(@NonNull Activity activity,
            @Nullable AppCompatCallback callback) {
        return new AppCompatDelegateImpl(activity, callback);
    }

public abstract void setContentView(@LayoutRes int resId);

4 AppCompatDelegateImpl.java

@Override
    public void setContentView(int resId) {
        //確保decorView和ContentView存在,如果不存在就創建它  --->第一階段
        ensureSubDecor();
        //可以看出來android.R.id.content就是我們的contentView的id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
      //contentView根佈局清除完之後纔可以填充視圖   ---->第二階段
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

爲了方便我把代碼分成兩個階段

第一階段

private void ensureSubDecor() {
    if (!mSubDecorInstalled) {
        //檢查你的Appcompat屬性是否存在,按照你的主題屬性來配置Activity
        mSubDecor = createSubDecor();
         ......
    }
}

 private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);

        //下面這句太熟悉了,每次我們主題設置設置錯誤或者沒有設置主題就會報錯
        if (!a.hasValue(R.styleable.AppCompatTheme_windowActionBar)) {
            a.recycle();
            throw new IllegalStateException(
                    "You need to use a Theme.AppCompat theme (or descendant) with this activity.");
        }

        //下面就是根據你的主題屬性來配置Activity
        if (a.getBoolean(R.styleable.AppCompatTheme_windowNoTitle, false)) {
            requestWindowFeature(Window.FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBar, false)) {
            // Don't allow an action bar if there is no title.
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionBarOverlay, false)) {
            requestWindowFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.AppCompatTheme_windowActionModeOverlay, false)) {
            requestWindowFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        mIsFloating = a.getBoolean(R.styleable.AppCompatTheme_android_windowIsFloating, false);
        a.recycle();
   
        //下面這兩行很重要,涉及到和Window的綁定了
        //所以我們要首先知道這個mWindow到底是什麼?
        ensureWindow();
   
   
        mWindow.getDecorView(); //代碼見 4.3PhoneWindow.java
        ...
        mWindow.setContentView(subDecor);//代碼見 4.3PhoneWindow.java
       .....
 }

 private void ensureWindow() {
       ...
        if (mWindow == null && mHost instanceof Activity) {
            //(Activity) mHost).getWindow()又是什麼鬼?
            attachToWindow(((Activity) mHost).getWindow());
        }
        ...
    }

private void attachToWindow(@NonNull Window window) {
        ...
        //當前mWindow在此處被唯一賦值,值就是(Activity) mHost).getWindow()
        mWindow = window;
    }


4.1 Activity.java
public Window getWindow() {
    return mWindow;
}

//mWindow又在哪裏被賦值了呢?
 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,
            Window window, ActivityConfigCallback activityConfigCallback, IBinder assistToken) {
     ...
     attachBaseContext(context);
     mWindow = new PhoneWindow(this, window, activityConfigCallback);
      //這句話後面會提到
      mWindow.getLayoutInflater().setPrivateFactory(this);
     ...
 }
//這裏的attach方法在哪裏被調用了呢?其實就是Activity啓動過程中發生的, 代碼在ActivityThread.java類

到此爲止我們知道Activity裏面的Window其實就是PhoneWindow,每創建一個Activity其實就新建一個PhoneWindow對象

所以AppCompatDelegateImpl.java對mWindow的操作其實就是對PhoneWindow的操作。

4.2 ActivityThread.java
ActivityThread.java
  
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
    ...
       //注意這裏的outerContext是Activity
       appContext.setOuterContext(activity);
                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,
                        r.assistToken);
  ...
}
4.3 PhoneWindow.java
@Override
public final @NonNull View getDecorView() {
    if (mDecor == null || mForceDecorInstall) {
        installDecor();
    }
    return mDecor;
}

 @Override
    public void setContentView(int layoutResID) {
        
        if (mContentParent == null) {
            installDecor();
        } else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            mContentParent.removeAllViews();
        }
        ...
    }

這兩個方法都要調用installDecor(),這是用來幹嘛的呢?是來創建DecorView和ContentView的

private void installDecor() {
    mForceDecorInstall = false;
    if (mDecor == null) {
       //生成DecorView
        mDecor = generateDecor(-1);
       ...
    } else {
        mDecor.setWindow(this);
    }
    if (mContentParent == null) {
       //生成mContentParent
        mContentParent = generateLayout(mDecor);
         ...
    }
  {
    
    protected DecorView generateDecor(int featureId) {
        ...
        //說明一個PhoneWindow也是創建一個DecorView
        return new DecorView(context, featureId, this, getAttributes());
    }
    
     protected ViewGroup generateLayout(DecorView decor) {
        ...
        //去找佈局中的ID_ANDROID_CONTENT控件
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
        ...
        return contentParent;
     }

第二階段

@Override
    public void setContentView(int resId) {
        //確保decorView和ContentView存在,如果不存在就創建它  --->第一階段
        ensureSubDecor();
        //可以看出來android.R.id.content就是我們的contentView的id
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
      //其實這裏相當於PhoneWindow暴露出一個對象供需要填充view的類使用
      //contentView根佈局清除完之後纔可以填充視圖   ---->第二階段
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }
4.4 LayoutInflater.java
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
    return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
    ....
   //開始解析我們寫的XML文件了
    XmlResourceParser parser = res.getLayout(resource);
    try {
        return inflate(parser, root, attachToRoot);
    } finally {
        parser.close();
    }
    ....
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    ...
     //先去找root節點
     final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    // 解析子view
    rInflateChildren(parser, temp, attrs, true);

    //將所有的節點添加到view樹上
   if (root != null && attachToRoot) {
                 root.addView(temp, params);
     }             
}

private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
    return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
        boolean ignoreThemeAttr) {
        ...
         //父類是  AppcompatActivity根據view名稱new一個對象出來。
         View view = tryCreateView(parent, name, context, attrs);
        //父類是  Activity 或者是自定義的的View  則反射生成一個view
        if (view == null) {
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {
                        view = onCreateView(context, parent, name, attrs);
                    } else {
                        view = createView(context, name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
         ...
        }
public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }

  //一臉懵逼mFactory2是什麼?mFactor是什麼?mPrivateFactor又是什麼?
  //首先是mFactory2嘗試加載view,如果mFactory爲空纔會使用mFactory加載
  //如果兩者都爲空或者兩者創建的view都是空,就會嘗試讓mPrivateFactory創建一下
    View view;
    if (mFactory2 != null) {
        view = mFactory2.onCreateView(parent, name, context, attrs);
    } else if (mFactory != null) {
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;
    }

    if (view == null && mPrivateFactory != null) {
        // 4.1 Activity.java   mWindow.getLayoutInflater().setPrivateFactory(this);
        // mPrivateFactory其實就是Activity,所以在這裏就是調用Activity的onCreateView
        view = mPrivateFactory.onCreateView(parent, name, context, attrs);
    }
    return view;
}
4.5 Activity.java —> mPrivateFactory
@Nullable
public View onCreateView(@Nullable View parent, @NonNull String name,
        @NonNull Context context, @NonNull AttributeSet attrs) {
    if (!"fragment".equals(name)) {
        //這個name就是view的名字,我們平時聲明的都是TextView/Button等等
        return onCreateView(name, context, attrs);
    }
    //所以知道爲啥我們要使用<fragment>而不是<Fragment>創建fragment節點了吧
    //只有fragment纔會被檢測到,通過碎片控制器生成碎片
    return mFragments.onCreateView(parent, name, context, attrs);
}

@Nullable
    public View onCreateView(@NonNull String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        //Activity只負責fragment的創建,其他view一律不管
        return null;
    }
//這裏就把mPrivateFactory講完了,我們看下另外兩個mFactory和mFactory2
4.6 LayoutInflater.java
//先找到這幾個變量定義的地方
@UnsupportedAppUsage
private Factory mFactory;
@UnsupportedAppUsage
private Factory2 mFactory2;

//查詢了賦值的操作,共有三處
 protected LayoutInflater(LayoutInflater original, Context newContext) {
        ...  
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        ...
    }

 public void setFactory(Factory factory) {
        ...
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = factory;
        } else {
            mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
        }
    }

 //層層尋找調用此方法的地方,接下來邏輯是倒過來的 調用鏈從順序變爲倒序
 public void setFactory2(Factory2 factory) {
        ...
        mFactorySet = true;
        if (mFactory == null) {
            mFactory = mFactory2 = factory;
        } else {
            mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
        }
    }
4.7 LayoutInflaterCompat.java
public static void setFactory2(
            @NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
        inflater.setFactory2(factory);

        if (Build.VERSION.SDK_INT < 21) {
            final LayoutInflater.Factory f = inflater.getFactory();
            if (f instanceof LayoutInflater.Factory2) {
                // The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
                // We will now try and force set the merged factory to mFactory2
                forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
            } else {
                // Else, we will force set the original wrapped Factory2
                forceSetFactory2(inflater, factory);
            }
        }
    }
4.8 AppCompatDelegateImpl.java
@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        //傳了參數this,說明mFactory2就是AppCompatDelegateImpl實例
        LayoutInflaterCompat.setFactory2(layoutInflater, this);
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                    + " so we can not install AppCompat's");
        }
    }
}
4.9 AppCompatActivity.java
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    //在這裏進行installViewFactory
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}
4.10 MainActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
    //父類去執行了註冊mFatory2的工作
    super.onCreate(savedInstanceState);
}

現在我們確定了一點,mFactory2在MainActivity.java中的super.onCreate(savedInstanceState)就獲得了初始化。mFactory2就是AppCompatDelegateImpl實例化對象,下面回到4.4 LayoutInflater.java代碼中繼續分析

View view;
if (mFactory2 != null) {
    //這裏mFactory2就是AppCompatDelegateImpl
    view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
    view = mFactory.onCreateView(name, context, attrs);
} else {
    view = null;
}

Back 4 AppCompatDelegateImpl.java

@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    return createView(parent, name, context, attrs);
}

 @Override
    public View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs) {
        ...
        //創建了一個AppCompatViewInflater來解析View
        mAppCompatViewInflater = new AppCompatViewInflater();
        return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
                IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
                true, /* Read read app:theme as a fallback at all times for legacy reasons */
                VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
        );
    }

5 AppCompatViewInflater.java

 final View createView(View parent, final String name, @NonNull Context context,
            @NonNull AttributeSet attrs, boolean inheritContext,
            boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
             switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Button":
                view = createButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                verifyNotNull(view, name);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                verifyNotNull(view, name);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                verifyNotNull(view, name);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                verifyNotNull(view, name);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                verifyNotNull(view, name);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                verifyNotNull(view, name);
                break;
            default:
                // The fallback that allows extending class to take over view inflation
                // for other tags. Note that we don't check that the result is not-null.
                // That allows the custom inflater path to fall back on the default one
                // later in this method.
                view = createView(context, name, attrs);
            }

 @NonNull
    protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
        return new AppCompatTextView(context, attrs);
    }

    @NonNull
    protected AppCompatImageView createImageView(Context context, AttributeSet attrs) {
        return new AppCompatImageView(context, attrs);
    }

    @NonNull
    protected AppCompatButton createButton(Context context, AttributeSet attrs) {
        return new AppCompatButton(context, attrs);
    }

    @NonNull
    protected AppCompatEditText createEditText(Context context, AttributeSet attrs) {
        return new AppCompatEditText(context, attrs);
    }

    @NonNull
    protected AppCompatSpinner createSpinner(Context context, AttributeSet attrs) {
        return new AppCompatSpinner(context, attrs);
    }

    @NonNull
    protected AppCompatImageButton createImageButton(Context context, AttributeSet attrs) {
        return new AppCompatImageButton(context, attrs);
    }

這就是爲啥你XML佈局即使寫成TextView但是使用的時候發現也是AppcompatTextView的原因,系統自動幫你做了轉換處理,最終返回了一個View.我們已經知道根節點的view是如何生成的了但是子節點是怎麼創建出來的呢?

6 LayoutInflater.java

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
    ...
     //先去找root節點
     final View temp = createViewFromTag(root, name, inflaterContext, attrs);

    // 解析子view,重點是這裏!
    rInflateChildren(parser, temp, attrs, true);
    
    //將根節點附加在contentParent上
   if (root != null && attachToRoot) {
                 root.addView(temp, params);
     }             
}

final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }

void rInflate(XmlPullParser parser, View parent, Context context,
            AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {

        final int depth = parser.getDepth();
        int type;
        boolean pendingRequestFocus = false;

        while (((type = parser.next()) != XmlPullParser.END_TAG ||
                parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {

            if (type != XmlPullParser.START_TAG) {
                continue;
            }

            final String name = parser.getName();

            if (TAG_REQUEST_FOCUS.equals(name)) {
                pendingRequestFocus = true;
                consumeChildElements(parser);
            } else if (TAG_TAG.equals(name)) {
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {
                if (parser.getDepth() == 0) {
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);
            } else if (TAG_MERGE.equals(name)) {
                throw new InflateException("<merge /> must be the root element");
            } else {
                 //又開始新一輪的遍歷了
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
                rInflateChildren(parser, view, attrs, true);
                viewGroup.addView(view, params);
            }
        }

        if (pendingRequestFocus) {
            parent.restoreDefaultFocus();
        }

        if (finishInflate) {
            parent.onFinishInflate();
        }
    }

大致就到這裏啦,至於Activity的setContentView創建過程基本和AppcompatActivity一樣,只是Activity使用的是反射,AppcompatActivity做了一層包裝(XXXView包裝成AppcompaXXX)

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章