Activity setContentView的過程

setContentView的過程

基于sdk30

  • setContentView是展示頁面重要的方法,需要通過setContentView才能xml顯示到頁面上來
  • setContentView有2種傳參方式一種是通過layoutID一種是直接傳遞view 本質(zhì)上都是一樣
  • 值得注意的是繼承AppCompatActivity和Activity的流程是稍有差別的。

Activity的過程

  • 源碼太多只抄了關(guān)鍵代碼
  • getWindow().setContentView(layoutResID);
  • getWindow()指的是Window甜奄,Window是一個(gè)抽象類晤硕,唯一實(shí)現(xiàn)類是PhoneWindow邢滑,MockWindow不算他是在com.android.setupwizardlib.test.util目錄
  • Activtiy的window是在attach方法中實(shí)例化的,attach是在ActivityThread.performLaunchActivity放中調(diào)用,(多嘴一句Activtiy侧蘸,Application的實(shí)例化也是在這塊實(shí)現(xiàn)的撤嫩,Activtiy和Application都是通過反射newInstance去創(chuàng)建的)


    1
2
  • 通過上面分析,其實(shí)最終調(diào)用的是PhoneWindow的setContentView方法
  @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.removeAllViews();
        }

        if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
  • 開始分析installDecor方法蠢终,直接看generateDecor怎么實(shí)現(xiàn)DecorView(mDecor是DecorView頂層試圖最外面的view)
private void installDecor() {
//...
  mForceDecorInstall = false;
        if (mDecor == null) {
            mDecor = generateDecor(-1);
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        } else {
            mDecor.setWindow(this);
        }
        //...
}
  • generateDecor方法序攘,直接將context和WindowManager.LayoutParams實(shí)例化DecorView(注意的是如果是mUseDecorContext為true,也就是通過PhoneWindow(Context context, Window preservedWindow,
    ActivityConfigCallback activityConfigCallback)這構(gòu)造參數(shù)實(shí)現(xiàn)寻拂,才會使mUseDecorContext為tur程奠。而這只有Activity new出來的PhoneWindow才調(diào)用了這個(gè)構(gòu)造, 使用這里DecorView的context才會是getApplicationContext())


    3
protected DecorView generateDecor(int featureId) {
        // System process doesn't have application context and in that case we need to directly use
        // the context we have. Otherwise we want the application context, so we don't cling to the
        // activity.
        Context context;
        if (mUseDecorContext) {
            Context applicationContext = getContext().getApplicationContext();
            if (applicationContext == null) {
                context = getContext();
            } else {
                context = new DecorContext(applicationContext, this);
                if (mTheme != -1) {
                    context.setTheme(mTheme);
                }
            }
        } else {
            context = getContext();
        }
        return new DecorView(context, featureId, this, getAttributes());
    }
  • 創(chuàng)建完DecorView的過程看完繼續(xù)分析mContentParent = generateLayout(mDecor); 這部分其實(shí)挺操蛋的只要關(guān)注2個(gè)點(diǎn)就行了
    protected ViewGroup generateLayout(DecorView decor) {
// ...省略
     mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
      ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
      //...省略
    }
  • 上面就是通過判斷不同屬性去setFlags以及拿到確定layoutResource的系統(tǒng)預(yù)設(shè)的xml文件


    4
  • onResourcesLoaded這個(gè)就是將layoutResource添加到DecorView里面去

5
  • findViewById(ID_ANDROID_CONTENT)這個(gè)就是先通過DecorView的findViewById然后在通過Viewgroup的findViewTraversal循環(huán)拿到view祭钉。(Activtiy和Diaolog的findViewById都是通過DecorView的findViewById去找的)
  • 看setContentView的最后一部分
    //判斷是否有過度動畫
 if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
  • hasFeature(FEATURE_CONTENT_TRANSITIONS)有沒有過度動畫,如果有就去調(diào)用transitionTo
 private void transitionTo(Scene scene) {
        if (mContentScene == null) {
            scene.enter();
        } else {
            mTransitionManager.transitionTo(scene);
        }
        mContentScene = scene;
    }
  • 第一次mContentScene肯定是null的直接看 scene.enter();
public void enter() {

        // Apply layout change, if any
        if (mLayoutId > 0 || mLayout != null) {
            // empty out parent container before adding to it
            getSceneRoot().removeAllViews();

            if (mLayoutId > 0) {
                LayoutInflater.from(mContext).inflate(mLayoutId, mSceneRoot);
            } else {
                mSceneRoot.addView(mLayout);
            }
        }

        // Notify next scene that it is entering. Subclasses may override to configure scene.
        if (mEnterAction != null) {
            mEnterAction.run();
        }

        setCurrentScene(mSceneRoot, this);
    }

  • 看到這得出一個(gè)結(jié)論瞄沙, 無論有沒有動畫都有會走LayoutInflater.inflater方法
  • 最后看下inflater到底干啥了,通過套娃調(diào)用找到inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)傳這3個(gè)參數(shù)的inflate
  • 知道XmlResourceParser這個(gè)是個(gè)解析xml的東西就好了,感興趣的可以百度下慌核。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");

           //...省略

                if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                  

                    //...省略
                    rInflateChildren(parser, temp, attrs, true);
        
                  if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }

                    // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                    //將view添加到rootview
                        root.addView(temp, params);
                    }
                    }
            return result;
        }
    }
  • 先看if (TAG_MERGE.equals(name)) 這個(gè)判斷條件是意思是如果根布局是個(gè)merge那就走rInflate這個(gè)方法(關(guān)于merge距境、include、ViewStub感興趣的可以百度下)
 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();
        }
    }
  • 通過while遍歷去判斷xml的name去解析不同的標(biāo)簽直接關(guān)注else的重點(diǎn)createViewFromTag
  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
            boolean ignoreThemeAttr) {
        //...省略

        try {
            View view = tryCreateView(parent, name, context, attrs);

            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;
                }
            }

            return view;
        }
        //...省略
    }
  • 先看下tryCreateView這個(gè)
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);
        }

        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) {
            view = mPrivateFactory.onCreateView(parent, name, context, attrs);
        }

        return view;
    }
  • 發(fā)現(xiàn)這里是通過mFactory2或者mFactory去創(chuàng)建view的垮卓,如果上面createViewFromTag的 View view = tryCreateView(parent, name, context, attrs);不為空了就沒法走LayoutInflater自己的onCreateView了垫桂,mFactory和mFactory2是在構(gòu)造方法的時(shí)候賦值的
protected LayoutInflater(LayoutInflater original, Context newContext) {
        mContext = newContext;
        mFactory = original.mFactory;
        mFactory2 = original.mFactory2;
        mPrivateFactory = original.mPrivateFactory;
        setFilter(original.mFilter);
        initPrecompiledViews();
    }
  • 去探究下LayoutInflater的創(chuàng)建過程先回到PhoneWindow
 public PhoneWindow(Context context) {
       super(context);
       mLayoutInflater = LayoutInflater.from(context);
       mRenderShadowsInCompositor = Settings.Global.getInt(context.getContentResolver(),
               DEVELOPMENT_RENDER_SHADOWS_IN_COMPOSITOR, 1) != 0;
   }
  • 結(jié)合上面的內(nèi)容LayoutInflater其實(shí)在attach通過實(shí)例化Phoneview的時(shí)候就創(chuàng)建了直接看LayoutInflater.from
   public static LayoutInflater from(Context context) {
       LayoutInflater LayoutInflater =
               (LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
       if (LayoutInflater == null) {
           throw new AssertionError("LayoutInflater not found.");
       }
       return LayoutInflater;
   }

  • context.getSystemService(),這下子得回到Activity去看了,畢竟這里的context是Activity繼承的ContextThemeWrapper的(ContextThemeWrapper繼承ContextWrapper粟按,ContextWrapper繼承Context抽象類)
  • getSystemService通過super之后到了ContextThemeWrapper的getSystemService
 @Override
   public Object getSystemService(String name) {
       if (LAYOUT_INFLATER_SERVICE.equals(name)) {
           if (mInflater == null) {
               mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
           }
           return mInflater;
       }
       return getBaseContext().getSystemService(name);
   }
  • 看到如果是LAYOUT_INFLATER_SERVICE并且mInflater為null直接走cloneInContext诬滩,看到這又有點(diǎn)小蒙圈了怎么又用到了LayoutInflater.from()這不是是玩蛇么。直接去看下getBaseContext()是何方神圣
  • 回到Activtiy的attach方法看到傳遞了一個(gè)Context然后調(diào)用了attachBaseContext(context)才賦值了getBaseContext()灭将。莫非...
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);
        //省略...
        }

  • ActivityThread.performLaunchActivity調(diào)用activity.attach的地方
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
       //省略...
        ContextImpl appContext = createBaseContextForActivity(r);
         //省略...
          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);
                        //...
  • 所以到最后終歸是走了ContextImpl的getSystemService
@Override
    public Object getSystemService(String name) {
        //...省略
        return SystemServiceRegistry.getSystemService(this, name);
    }
  • SystemServiceRegistry的getSystemService
 public static Object getSystemService(ContextImpl ctx, String name) {
        if (name == null) {
            return null;
        }
        final ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        //...省略
         final Object ret = fetcher.getService(ctx);
       //...省略
        return ret;
 }
  • SYSTEM_SERVICE_FETCHERS這個(gè)在SystemServiceRegistry類被加載的時(shí)候就去put了大量對象,后續(xù)只要通過名稱就可以拿到對象了比如LAYOUT_INFLATER_SERVICE
   registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
  • 所以上面LayoutInflater.from(getBaseContext()).cloneInContext(this)實(shí)際就是調(diào)用了PhoneLayoutInflater.cloneInContext
   public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
    
     protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
        super(original, newContext);
    }
  • 這樣看來現(xiàn)在LayoutInflater的mFactory和mFactory2還是空的疼鸟,上面的View view = tryCreateView(parent, name, context, attrs);為null這樣就繼續(xù)走onCreateView或者createView了
 View view = tryCreateView(parent, name, context, attrs);

            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;
                }
            }

            return view;
  • 看下if判斷這個(gè)-1==name.indexOf('.')猜惋,如果name不包含.比如TextView,ImgaeView走onCreateView伞辛,否則如自定義com.xxx.xxxView陡舅,androidx.constraintlayout.widget.ConstraintLayout吮旅, androidx.recyclerview.widget.RecyclerView這些需要在xml使用全路徑的view就走createView
  • 先看onCreateView,由于上面LayoutInflater的創(chuàng)建是mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this)恩掷,所以要去看下PhoneLayoutInflater.onCreateView也沒有重寫。
private static final String[] sClassPrefixList = {
        "android.widget.",
        "android.webkit.",
        "android.app."
    };
  protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
        for (String prefix : sClassPrefixList) {
            try {
                View view = createView(name, prefix, attrs);
                if (view != null) {
                    return view;
                }
            } catch (ClassNotFoundException e) {
                // In this case we want to let the base class take a crack
                // at it.
            }
        }

        return super.onCreateView(name, attrs);
    }
    
  • 先假設(shè)generateLayout中 mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);糟秘,mLayoutInflater的layout為R.layout.screen_simple黔攒,他的第一個(gè)布局是LinearLayout而LinearLayout的全路徑為android.widget.LinearLayout。接著往createView看,PhoneLayoutInflater沒有重寫createView舶斧,所以又回到了LayoutInflater.createView
public final View createView(@NonNull Context viewContext, @NonNull String name,
            @Nullable String prefix, @Nullable AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        
        Constructor<? extends View> constructor = sConstructorMap.get(name);
      
        Class<? extends View> clazz = null;
            //...
        try {
            //...

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                clazz = Class.forName(prefix != null ? (prefix + name) : name, false,
                        mContext.getClassLoader()).asSubclass(View.class);
                        //...
                constructor = clazz.getConstructor(mConstructorSignature);
                //...省略
           】

           //...
                final View view = constructor.newInstance(args);
               //..
                return view;
            } 
            //...省略
    }
  • 看到Class是通過prefix加名稱補(bǔ)全得到的view欣鳖,再通過constructor.newInstance去實(shí)例化,如LinearLayout通過循環(huán)補(bǔ)全android.widget.LinearLayout才能用反射創(chuàng)建出來這也就是說名為啥TextView,ImageView茴厉,LinearLayout這些不用在xml寫全路徑(這些都在 "android.widget.",
    "android.webkit.",
    "android.app."目錄里面這里通過for循環(huán)sClassPrefixList的方式自動補(bǔ)全了路徑)泽台,其他的需要。
  • 到了這里if (-1 == name.indexOf('.'))的else判斷也不用再去看了矾缓,最終都是通過全路徑得到class怀酷,通過反射實(shí)例化出來。
  • 看下rInflateChildren這個(gè)創(chuàng)建子view
 final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
            boolean finishInflate) throws XmlPullParserException, IOException {
        rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
    }
  • rInflate已經(jīng)扯完了
  • 回到inflate中if (TAG_MERGE.equals(name))這個(gè)判斷的地方看下else部分
   if (TAG_MERGE.equals(name)) {
                    if (root == null || !attachToRoot) {
                        throw new InflateException("<merge /> can be used only with a valid "
                                + "ViewGroup root and attachToRoot=true");
                    }

                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    // Temp is the root view that was found in the xml
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);
                    //...
                    rInflateChildren(parser, temp, attrs, true);
                    if (DEBUG) {
                        System.out.println("-----> done inflating children");
                    }
                     // We are supposed to attach all the views we found (int temp)
                    // to root. Do that now.
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                }
  • 又是createViewFromTag和rInflateChildren嗜闻,到這流程其實(shí)已經(jīng)扯完了蜕依。inflate其實(shí)就是通過xml解析出來的名稱
    、AttributeSet去反射創(chuàng)建view然后addView到rootview琉雳。
  • 回到PhoneWindow中setContentView那句代碼
 mLayoutInflater.inflate(layoutResID, mContentParent);
  • 綜合上面的分析样眠,不就是將自己Activty的xml布局添加到父布局么

成熱打鐵下看下AppCompatActivity的setContentView過程

 @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
    
    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }
    
    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        if (Build.VERSION.SDK_INT >= 24) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (Build.VERSION.SDK_INT >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else {
            return new AppCompatDelegateImplV14(context, window, callback);
        }
    }
    AppCompatDelegateImplN繼承AppCompatDelegateImplV23
    AppCompatDelegateImplV23繼承AppCompatDelegateImplV14
    AppCompatDelegateImplV14繼承AppCompatDelegateImplV9
    AppCompatDelegateImplV9繼承AppCompatDelegateImplBase實(shí)現(xiàn)MenuBuilder.Callback、 LayoutInflater.Factory2接口
  • 通過一番源碼跳轉(zhuǎn)到AppCompatDelegateImplV9的setContentView
   @Override
    public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mOriginalWindowCallback.onContentChanged();
    }
    
    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            //重點(diǎn)
            mSubDecor = createSubDecor();

            // If a title was set before we installed the decor, propagate it now
            CharSequence title = getTitle();
            if (!TextUtils.isEmpty(title)) {
                onTitleChanged(title);
            }

            applyFixedSizeWindow();

            onSubDecorInstalled(mSubDecor);

            mSubDecorInstalled = true;

            // Invalidate if the panel menu hasn't been created before this.
            // Panel menu invalidation is deferred avoiding application onCreateOptionsMenu
            // being called in the middle of onCreate or similar.
            // A pending invalidation will typically be resolved before the posted message
            // would run normally in order to satisfy instance state restoration.
            PanelFeatureState st = getPanelState(FEATURE_OPTIONS_PANEL, false);
            if (!isDestroyed() && (st == null || st.menu == null)) {
                invalidatePanelMenu(FEATURE_SUPPORT_ACTION_BAR);
            }
        }
    }
    
    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.");
        }

        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();

        //重點(diǎn)
        // Now let's make sure that the Window has installed its decor by retrieving it
        mWindow.getDecorView();

        final LayoutInflater inflater = LayoutInflater.from(mContext);
        ViewGroup subDecor = null;


        if (!mWindowNoTitle) {
            if (mIsFloating) {
                // If we're floating, inflate the dialog title decor
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_dialog_title_material, null);

                // Floating windows can never have an action bar, reset the flags
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
                /**
                 * This needs some explanation. As we can not use the android:theme attribute
                 * pre-L, we emulate it by manually creating a LayoutInflater using a
                 * ContextThemeWrapper pointing to actionBarTheme.
                 */
                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

                Context themedContext;
                if (outValue.resourceId != 0) {
                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
                } else {
                    themedContext = mContext;
                }

                // Now inflate the view using the themed context and set it as the content view
                subDecor = (ViewGroup) LayoutInflater.from(themedContext)
                        .inflate(R.layout.abc_screen_toolbar, null);

                mDecorContentParent = (DecorContentParent) subDecor
                        .findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());

                /**
                 * Propagate features to DecorContentParent
                 */
                if (mOverlayActionBar) {
                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
                }
                if (mFeatureProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                }
                if (mFeatureIndeterminateProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                }
            }
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(
                        R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }

            if (Build.VERSION.SDK_INT >= 21) {
                // If we're running on L or above, we can rely on ViewCompat's
                // setOnApplyWindowInsetsListener
                ViewCompat.setOnApplyWindowInsetsListener(subDecor,
                        new OnApplyWindowInsetsListener() {
                            @Override
                            public WindowInsetsCompat onApplyWindowInsets(View v,
                                    WindowInsetsCompat insets) {
                                final int top = insets.getSystemWindowInsetTop();
                                final int newTop = updateStatusGuard(top);

                                if (top != newTop) {
                                    insets = insets.replaceSystemWindowInsets(
                                            insets.getSystemWindowInsetLeft(),
                                            newTop,
                                            insets.getSystemWindowInsetRight(),
                                            insets.getSystemWindowInsetBottom());
                                }

                                // Now apply the insets on our view
                                return ViewCompat.onApplyWindowInsets(v, insets);
                            }
                        });
            } else {
                // Else, we need to use our own FitWindowsViewGroup handling
                ((FitWindowsViewGroup) subDecor).setOnFitSystemWindowsListener(
                        new FitWindowsViewGroup.OnFitSystemWindowsListener() {
                            @Override
                            public void onFitSystemWindows(Rect insets) {
                                insets.top = updateStatusGuard(insets.top);
                            }
                        });
            }
        }

        if (subDecor == null) {
            throw new IllegalArgumentException(
                    "AppCompat does not support the current theme features: { "
                            + "windowActionBar: " + mHasActionBar
                            + ", windowActionBarOverlay: "+ mOverlayActionBar
                            + ", android:windowIsFloating: " + mIsFloating
                            + ", windowActionModeOverlay: " + mOverlayActionMode
                            + ", windowNoTitle: " + mWindowNoTitle
                            + " }");
        }

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }

        // Make the decor optionally fit system windows, like the window's decor
        ViewUtils.makeOptionalFitsSystemWindows(subDecor);
        //重點(diǎn)
        final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
        //重點(diǎn)
        final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
        if (windowContentView != null) {
            // There might be Views already added to the Window's content view so we need to
            // migrate them to our content view
            while (windowContentView.getChildCount() > 0) {
                final View child = windowContentView.getChildAt(0);
                windowContentView.removeViewAt(0);
                contentView.addView(child);
            }

            // Change our content FrameLayout to use the android.R.id.content id.
            // Useful for fragments.
            //重點(diǎn)
            windowContentView.setId(View.NO_ID);
            //重點(diǎn)
            contentView.setId(android.R.id.content);

            // The decorContent may have a foreground drawable set (windowContentOverlay).
            // Remove this as we handle it ourselves
            if (windowContentView instanceof FrameLayout) {
                ((FrameLayout) windowContentView).setForeground(null);
            }
        }

        //重點(diǎn)
        // Now set the Window's content view with the decor
        mWindow.setContentView(subDecor);

        contentView.setAttachListener(new ContentFrameLayout.OnAttachListener() {
            @Override
            public void onAttachedFromWindow() {}

            @Override
            public void onDetachedFromWindow() {
                dismissPopups();
            }
        });

        return subDecor;
    }
    
  • 這里AppCompatActivity通過AppCompatDelegateImplV9包了一層layou去調(diào)用了PhoneWindow的setContentView的方法剩下的又是Activity的流程了
  • 注意的是下面幾行行代碼
  1. 找到ContentFrameLayout
  final ContentFrameLayout contentView = (ContentFrameLayout) subDecor.findViewById(
                R.id.action_bar_activity_content);
  1. 找到android.R.id.content 這個(gè)其實(shí)就是activity流程中PhoneWindow.generateLayout方法創(chuàng)建的layout布局id如下圖
    final ViewGroup windowContentView = (ViewGroup) mWindow.findViewById(android.R.id.content);
6
  1. 把原來Activity中設(shè)定的布局名稱設(shè)置成null
   windowContentView.setId(View.NO_ID);
  1. 把現(xiàn)在設(shè)定的布局名稱設(shè)置成content翠肘,父布局就變成AppCompatActivity創(chuàng)建的ContentFrameLayout了檐束,就這樣完成了父布局的替換
      contentView.setId(android.R.id.content);
  • setContentView方法中,不也是將自己Activty的xml布局添加到父布局么束倍,和Activity不同的是這里通過包裝操作將原來的父布局變成了AppCompatActivity的contentParent了
LayoutInflater.from(mContext).inflate(resId, contentParent);

到這里流程講完了總結(jié)一下

Activity被ActivityThread創(chuàng)建出來調(diào)用了attch方法拿到ContextImpl被丧,調(diào)用PhoneWindow的setContentView方先創(chuàng)建了DecorView,通過一番判斷設(shè)定了flag和父布局xml绪妹,然后把這個(gè)父布局添加到DecorView甥桂,在把自己Activty的xml添加到父布局。

AppCompatActivity在Activity的基礎(chǔ)上添加了層父布局喂急,通過改名替換的操作替換了Activity的父怒拒格嘁,后續(xù)自己activity的xml將添加這父布局

LayoutInflater的inflate是將xml解析出來,通過全路徑(ImageView廊移、TextView這些通過循環(huán)補(bǔ)全)名稱和屬性去反射創(chuàng)建View糕簿,并將這個(gè)view添加到參數(shù)root布局里面

流程圖
  • Activtiy
Activtiy
  • AppCompatActivity


    AppCompatActivity
最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市狡孔,隨后出現(xiàn)的幾起案子懂诗,更是在濱河造成了極大的恐慌,老刑警劉巖苗膝,帶你破解...
    沈念sama閱讀 222,627評論 6 517
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件殃恒,死亡現(xiàn)場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)离唐,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 95,180評論 3 399
  • 文/潘曉璐 我一進(jìn)店門病附,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人亥鬓,你說我怎么就攤上這事完沪。” “怎么了嵌戈?”我有些...
    開封第一講書人閱讀 169,346評論 0 362
  • 文/不壞的土叔 我叫張陵覆积,是天一觀的道長。 經(jīng)常有香客問我熟呛,道長宽档,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 60,097評論 1 300
  • 正文 為了忘掉前任庵朝,我火速辦了婚禮吗冤,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘九府。我一直安慰自己欣孤,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 69,100評論 6 398
  • 文/花漫 我一把揭開白布昔逗。 她就那樣靜靜地躺著,像睡著了一般篷朵。 火紅的嫁衣襯著肌膚如雪勾怒。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 52,696評論 1 312
  • 那天声旺,我揣著相機(jī)與錄音笔链,去河邊找鬼。 笑死腮猖,一個(gè)胖子當(dāng)著我的面吹牛鉴扫,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播澈缺,決...
    沈念sama閱讀 41,165評論 3 422
  • 文/蒼蘭香墨 我猛地睜開眼坪创,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了姐赡?” 一聲冷哼從身側(cè)響起莱预,我...
    開封第一講書人閱讀 40,108評論 0 277
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎项滑,沒想到半個(gè)月后依沮,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 46,646評論 1 319
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 38,709評論 3 342
  • 正文 我和宋清朗相戀三年危喉,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了宋渔。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 40,861評論 1 353
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡辜限,死狀恐怖皇拣,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情列粪,我是刑警寧澤审磁,帶...
    沈念sama閱讀 36,527評論 5 351
  • 正文 年R本政府宣布,位于F島的核電站岂座,受9級特大地震影響态蒂,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜费什,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 42,196評論 3 336
  • 文/蒙蒙 一钾恢、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧鸳址,春花似錦瘩蚪、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,698評論 0 25
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至巡球,卻和暖如春言沐,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酣栈。 一陣腳步聲響...
    開封第一講書人閱讀 33,804評論 1 274
  • 我被黑心中介騙來泰國打工险胰, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人矿筝。 一個(gè)月前我還...
    沈念sama閱讀 49,287評論 3 379
  • 正文 我出身青樓起便,卻偏偏與公主長得像,于是被迫代替她去往敵國和親窖维。 傳聞我的和親對象是個(gè)殘疾皇子榆综,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 45,860評論 2 361

推薦閱讀更多精彩內(nèi)容