美文网首页Android技术知识Android开发Android知识
《Android中Activity的层次结构分析》---SetC

《Android中Activity的层次结构分析》---SetC

作者: TrillGates | 来源:发表于2017-04-03 10:21 被阅读496次

    本系列文章是写关于View的,仅作参考,因为真的看懂,是需要下一份源代码,下一个阅读工具去跟代码的。如果看不懂了,这里仅仅是一个路线。所以,仅仅看这个文章是不能全懂的。建议想学的同学去跟一次代码!

    View我先不说,先静静地看着我装逼吧!
    先从Activity的SetContentView()开始为了方便开发者开发应用,google呢封装了多种Activity,它们有各自的用途。比如说看图吧:

    在上面的图片中,我们可以清楚地看到了Activity的子孙体系!一般来说,我们是直接继承Activity或者继承AppCompatActivity。其他的则根据需求而写了,比如说,我们Activity直接是显示List的话,那么我们可以直接 继承自ListActivity,如果我们的Activity要承载Fragment的话,直接继承AppComaptActivity或者FragmentActivity。好啦,这些都不是重点,我们的重点是SetContentView这个方法,我们小时候学习Android开发就知道,这个方法用于设置布局的,所以我们在研究View呢,就从这里入手了!
    在以上这些终多的Activity的孩子中,通过查看源码,我们可以知道,除了AppCompatActivity是自己处理SetContentView这个方法的,其他都是调用Activity的SetContentView方法
    那好了,又来图片了:


    AppCompatActivity的setContentView()看着这张图片,我们先分析AppCompatActivity里的setContentView方法吧。我们进去看源代码,可以看到这样子的!

    @Override
    public void setContentView(@LayoutRes int layoutResID) {
        getDelegate().setContentView(layoutResID);
    }
     
    @Override
    public void setContentView(View view) {
        getDelegate().setContentView(view);
    }
     
    @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }
    

    在这几个重载的方法里,我们可以发现,都是getDelegate(),这个又是何方神圣呢?我们继续往下研究它吧!

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

    原来,调用这个方法是用于获取到一个AppCompatDelegate的单例对象。那我们就继续往下看这个类到底是干嘛的吧!
    AppCompatDelegate类的解释
    AppCompatDelegate这个类是一个抽象类,它有作用是什么呢?其实它的继承层级比较多,就是为了达到兼容的。你一看继承体系就明白了:


    再看看上面这个Create方法的代码是怎么样的吧!

    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
        if (BuildCompat.isAtLeastN()) {
            return new AppCompatDelegateImplN(context, window, callback);
        } else if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV9(context, window, callback);
        }
    }
    

    到这里,你懂了吧,为什么别的Activity都是调用Activity的SetContentView的方法,而AppCompatActivity需要自己去实现呢,就是因为它要根据不同的版本,去创建这个AppCompatDelegate的实现类,这样子才可以达到兼用的效果嘛,要不怎么叫AppCompatActivity呢,对吧!而这个实现类有什么用呢?我们回到前面的getDelegate().setContentView的几个重载方法吧:
    这几个方法呢是在AppCompatDelegateImplV9里头重写的

    @Override
    public void setContentView(View v) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v);
        mOriginalWindowCallback.onContentChanged();
    }
     
    @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();
    }
     
    @Override
    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }
    

    其实三个的本质都是一样的,如果是资源ID,就去加载成View,如果是View那么就直接添加到父类容器里头。其实,android也是自己准备一个容器,把开发都写的布局加载进去而已。这也是我们后面会详细说到的如何加载成View,android的UI体系。这里我们还是要继续深挖下去,我们先拿一个setContentView(int resId)的方法进行简单的注释一下吧:

    @Override
    public void setContentView(int resId) {
        //这里是保证创建了mSubDecor,下一行代码要用到嘛,其实它就是一个容器。
        ensureSubDecor();
        //通过id找到这个容器
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        //干掉这个容器上的所有控件
        contentParent.removeAllViews();
        //把xml资源文件加载成view
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        //这个是回调,告诉回调传入的地方UI的内容已经改变了
        mOriginalWindowCallback.onContentChanged();
    }
    

    到这里,我们姑且认为这个AppCompatActivity的setContentView暂时完成,我们还留有几个要解决的地方,一个是Inflate是如何把xml加载成view的。
    到这里,我们继续把Activity的setContentView也看完。
    Activity的setContentView()
    在Acitivyt下的SetContentView也有三个重载的方法,如下:

    public void setContentView(int layoutResID) {
    getWindow().setContentView(layoutResID);
    initActionBar();
    }
    
    public void setContentView(View view) {
    getWindow().setContentView(view);
    initActionBar();
    }
    
    
    public void setContentView(View view, ViewGroup.LayoutParams params) {
    getWindow().setContentView(view, params);
    initActionBar();
    }
    

    它们三个重载的方法都是一样的道理,我们只看一个的话就差不多了:

    public void setContentView(@LayoutRes int layoutResID) {
    getWindow().setContentView(layoutResID);
    initWindowDecorActionBar();
    }
    

    这个方法呢,就是Activity的setContentView方法了,它没有AppCompatActivity那样子,去getDelegate()方法来设置这个View.它直接使用getWindows()去设置ContentView.那我们就跟进去呗。
    getWindow()这个方法,返回的是PhoneWindow,后面我们再说一下PhoneWindow是怎么创建的,我们现在先看它的setContentView这个方法吧。
    PhoneWindow
    这个类是继承自Window这个类的,而SetContentView则是Winodw这个类里的抽象方法,PhonWindow是Window的唯一实现类,所以到这一层的话,真的是把xml通过Inflater映射成View加载到ContentView里头了。

    public class PhoneWindow extends Window implements MenuBuilder.Callback {
    

    上面getWindow反回的就是Window,而Window是一个抽象类来的。而这个mWindow创建是怎么创建的呢?就是通过new PhoneWindow来创建的:

    mWindow = new PhoneWindow(this);//这里的this指的是Activity,传入的是上下文,其实Activity的继承自Context的
    

    到这里的话,我们知道了这个mWindow其实是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();
            }
        }
         
        @Override
        public void setContentView(View view) {
            setContentView(view, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
        }
         
        @Override
        public void setContentView(View view, ViewGroup.LayoutParams params) {
            // 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)) {
                view.setLayoutParams(params);
                final Scene newScene = new Scene(mContentParent, view);
                transitionTo(newScene);
            } else {
                mContentParent.addView(view, params);
            }
            mContentParent.requestApplyInsets();
            final Callback cb = getCallback();
            if (cb != null && !isDestroyed()) {
                cb.onContentChanged();
            }
        }
    

    上面三个呢就是设置内容布局的方法,一个一个来吧。

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

    看到了吧,这个方法又来了:installDecor();这个方法前面我们说到过的ensureSubDecor()是一样的,这个是用来保证mContentParent不为空。怎么保证呢?

    mContentParent = generateLayout(mDecor);
    

    接着就干掉所有的view,和前面的AppCompatActivity是一样的吧。

    mLayoutInflater.inflate(layoutResID, mContentParent)
    

    最后就把这个UI的xml文件加载到了内容容器里头。
    总结:

    • Window类(for Activity)和AppCompatDelegate类(for AppCompatActivity)的作用类似
    • Window类的唯一实现类是PHoneWindow,而AppCompatDelegate的(唯一:当版本定了时唯一)实现类是AppCompatDelegateImplBase下的对应当前版本子类。
    • Activity是通过PhoneWindow里的setContentView把XML布局设置到contentView里头的。
    • AppCompatActivity是通过对应版本的AppCompatDelegateImpl各个对应版本的实现类的setContentView来把内容xml的内容设置到contentView里头的。
      待研究的问题:
    • Inflater是怎么把xml文件变成View的。
    • ContentView PhoneWindow DecorView的各级关系是什么?它们是怎么联系起来的!

    《Android中Activity的层次结构分析》---SetContentView开始
    http://bbs.sunofbeaches.com/forum.php?mod=viewthread&tid=5932&fromuid=1
    (出处: 阳光沙滩)

    下一篇文章是接着上面的

    LayoutInfloater如何把XMl加载成View的呢?
    http://bbs.sunofbeaches.com/forum.php?mod=viewthread&tid=5958&fromuid=1
    (出处: 阳光沙滩)

    相关文章

      网友评论

        本文标题:《Android中Activity的层次结构分析》---SetC

        本文链接:https://www.haomeiwen.com/subject/apbnottx.html