Android技术知识Android开发Android知识

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

2017-04-03  本文已影响496人  TrillGates

本系列文章是写关于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文件加载到了内容容器里头。
总结:

《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
(出处: 阳光沙滩)

上一篇下一篇

猜你喜欢

热点阅读