setContentView源码分析:
大致架构:
我们来对上图做出简单解释:DecorView是一个应用窗口的根容器,继承FrameLayout。DecorView只有一个布局,它是一个垂直LinearLayout,包含两个子元素,一个是TitleView(ActionBar的容器),另一个是ContentView(窗口内容的容器)。关于ContentView,它是一个FrameLayout(id为android.R.id.content),我们用的setContentView就是设置到ContentView。上图还表达了每个Activity都与一个Window(具体来说是PhoneWindow)相关联,用户界面则由Window所承载。
window
window是一个抽象类,phoneWindow是其唯一的继承类。
这个抽象类包含了三个核心组件:
WindowManager.LayoutParams: 窗口的布局参数;
Callback: 窗口的回调接口,通常由Activity实现;
ViewTree: 窗口所承载的控件树。
实际上,窗口是一个宏观的思想,它是屏幕上用于绘制各种UI元素及响应用户输入事件的一个矩形区域。通常具备以下两个特点:
1.独立绘制,不与其它界面相互影响;
2.不会触发其它界面的输入事件;
在Android系统中,窗口是独占一个Surface实例的显示区域,每个窗口的Surface由WindowManagerService分配。我们可以把Surface看作一块画布,应用可以通过Canvas或OpenGL在其上面作画。画好之后,通过SurfaceFlinger将多块Surface按照特定的顺序(即Z-order)进行混合,而后输出到FrameBuffer中,这样用户界面就得以显示。
PhoneWindow
PhoneWindow
这个类是Framework为我们提供的Android窗口的具体实现。我们平时调用setContentView()方法设置Activity的用户界面时,实际上就完成了对所关联的PhoneWindow的ViewTree的设置。我们还可以通过Activity类的requestWindowFeature()方法来定制Activity关联PhoneWindow的外观,这个方法实际上做的是把我们所请求的窗口外观特性存储到了PhoneWindow的mFeatures成员中,在窗口绘制阶段生成外观模板时,会根据mFeatures的值绘制特定外观。
Window#getWindowStyle
通过Window的getWindowStyle方法从style.xml
中获取此应用程序窗口主题的属性:
synchronized (this) {
if (mWindowStyle == null) {
mWindowStyle = mContext.obtainStyledAttributes(
com.android.internal.R.styleable.Window);
}
return mWindowStyle;
}
Window#findViewById
这个方法是我们最常用的方法之一,在Activity中调用findViewById方法,内部会调用Window的findViewById方法,最终调用的是View中的findViewById方法,这里不做深入研究。
return getDecorView().findViewById(id);
Window#setContentView(int)
在Window中该方法是抽象方法,查看它的唯一子类PhoneWindow中的实现。
Activity.setContentView()源码分析
public void setContentView(@LayoutRes int layoutResID) {
// 获取Window 调用window的setContentView方法,发现是抽象类,所以需要找具体的实现类PhoneWindow
getWindow().setContentView(layoutResID);
}
// PhoneWindow 中的 setContentView方法
@Override
public void setContentView(int layoutResID) {
// 如果mContentParent 等于空,调用installDecor();
//mContentParent就是我们自己写的文件布局,installDecor()就是加载DecorView,DecorView是一个继承FrameLayout的布局文件。
if (mContentParent == null) {
//此方法的作用 1.加载一个decorview确定window最基本的布局
//2.从decorView的布局中选择一个控件给mContentParent
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 把我们自己的布局layoutId加载到mContentParen。
mLayoutInflater.inflate(layoutResID, mContentParent);
}
installDecor就是加载一个decorView(相应的布局文件),并从布局文件中的id为android.R.id.content的FrameLayout设置给mContentParent:
private DecorView mDecor;
private void installDecor() {
if (mDecor == null) {
// 先去创建一个 DecorView
mDecor = generateDecor(-1);
}
// ......
// 省略调一些代码,看着晕,不过这也太省了。
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
// generateDecor 方法
protected DecorView generateDecor(int featureId) {
// 就是new一个DecorView ,DecorView extends FrameLayout 不同版本的源码有稍微的区别,
// 低版本DecorView 是PhoneWindow的内部类,高版本是一个单独的类,不过这不影响。
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
int layoutResource; //系统布局文件的Id
// 都是一些判断,发现 layoutResource = 系统的一个资源文件,
//根据获得的主题(style.xml),调用不同的requestFeature参数,从而生成不同的布局。所以requestFeature要在setContentView之前。
TypedArray a = getWindowStyle();
if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
requestFeature(FEATURE_NO_TITLE);
}
if(){}else if(){}else if(){
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 把系统布局解析加载到 DecorView
// 某些源码是 addView() 其实是一样的
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ID_ANDROID_CONTENT 是 android.R.id.content,这个View是从DecorView里面去找的,
// 也就是 从系统的layoutResource里面找一个id是android.R.id.content的一个FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 把这个ViewGroup返回给我们mContentParent
return contentParent;
}
好了,我们文字总结一下:
- setContentView()通过getwindow()获取PhoneWindow实例,phonewindow获取decorView的实例。
- 然后通过一系列的判断要加载哪个系统布局文件的id,然后把布局文件解析加载到decorView,再从decorView布局文件中找到一个id为android.R.id.content的控件给mContentParent
- 最后通过inflate把我们传过来的布局id加载到mContentParent
兼容包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;
}
// window 还是那个window ,留意一下就行 , 返回 AppCompatDelegateImpl,但是都是相互继承
public static AppCompatDelegate create(@NonNull Activity activity,
@Nullable AppCompatCallback callback) {
return new AppCompatDelegateImpl(activity, callback);
}
//重载方法
public static AppCompatDelegate create(@NonNull Dialog dialog,
@Nullable AppCompatCallback callback)
public static AppCompatDelegate create(@NonNull Context context, @NonNull Window window,
@Nullable AppCompatCallback callback)
public static AppCompatDelegate create(@NonNull Context context, @NonNull Activity activity,
@Nullable AppCompatCallback callback)
// 下面其实就没啥好看的了,一个一个点进去,仔细看看就好了。
//与Activity没啥区别了,都是先创建一个decorView实例,然后加载系统布局文件,最后在decorView找到id为android.R.id.content的
@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() {
mSubDecor = createSubDecor();
}
LayoutInflater源码
Inflater的三种使用方法:
View layoutView = View.inflate(this,R.layout.activity_main,null);
layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null);
layoutView = LayoutInflater.from(this).inflate(R.layout.activity_main,null,false);
//上面的第一个方法调用的是第二种方法
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
//第二种方法调用的是第三种方法
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
先看看LayoutInflate.from(this)如何实例化一个LayoutInflate
// LayoutInflater 方法里面,是一个静态的方法
public static LayoutInflater from(Context context) {
// 通过context获取系统的服务
LayoutInflater LayoutInflater =
// context.getSystemService()是一个抽象类,所以我们必须找到实现类ContextImpl
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
// ContextImpl 里面的实现方法
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
// SystemServiceRegistry 里面的getSystemService方法
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
// 这是一个静态的HashMap集合
private static final HashMap> SYSTEM_SERVICE_FETCHERS =
new HashMap>();
// 静态的代码块中
static{
// 注册LayoutInflater服务
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
// 注册很多的其他服务......
}
文字总结:通过context的实例化contextImpl的获取的,contextImpl#getSystemService调用了StsyemServiceRegister.getSystemService方法,这个方法通过一个静态map获得我们的LayoutInflate,初始化是在静态代码块中通过registerService注册了很多服务。这是一个单例模式,全局只有一个LayoutInflate对象。
.inflate(LayoutRes,parent,attachToRoot)生成View
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
// 获取一个 XmlResourceParser 解析器,这个应该并不陌生,就是待会需要去解析我们的layoutId.xml文件
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
......
//保存传进来的这个view,并最终返回result
View result = root;
try {
// Look for the root node.
int type;
//在这里找到xml标签
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//获取这个xml标签的名字
final String name = parser.getName();
......
//判断根节点是否merge标签
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException(" can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//xml根为merge调用
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//根据view名称构造view.
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//布局参数
ViewGroup.LayoutParams params = null;
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//temp设置布局参数
temp.setLayoutParams(params);
}
}
......
//在这里,先获取到了temp,再把temp当做root传进去rInflateChildren
//进行加载temp后面的子所有view
rInflateChildren(parser, temp, attrs, true);
......
if (root != null && attachToRoot) {
//把view添加到root中并设置布局参数
root.addView(temp, params);
}
//返回我们的xml布局文件
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
......
} catch (Exception e) {
......
} finally {
......
}
return result;
}
}
// 创建View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// ......
try {
// 创建我们的View
View view;
if (mFactory2 != null) {
// 先通过mFactory2 创建,其实在 AppCompatActivity里面会走这个方法,也就会去替换某些控件
// 所以我们就 看到了上面的内容
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
// 走mFactory
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// ......省略
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 判断是不是自定义View,自定义View在布局文件中com.hc.BannerView是个全类名,
// 而系统的View在布局文件中不是全类名 TextView
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
// ........
}
}
// 创建View
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 做一些反射的性能优化
try {
// 先从缓存中拿,这是没拿到的情况
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
// 加载 clazz
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 创建View的构造函数
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 加入缓存集合集合
sConstructorMap.put(name, constructor);
} else {
}
// 通过反射创建View
final View view = constructor.newInstance(args);
return view;
} catch (NoSuchMethodException e) {
// ......省略部分代码
}
}
setContentView并没有调用了控件的测量 onMeature,只是创建了decorView
Activity的启动流程: performLaunchActivity -> Activity.onCreate()。
handleResumeActivity() -> performResumeActivity() -> Activity的onResume()方法
-> wm.addView(decor, l); 才开始把我们的 DecorView 加载到 WindowManager, -> View的绘制流程在这个时候才开始 measure() layout() draw() 。