- setContentView中做了什么?
在onCreate中,调用 setContentView,setContentView中只是创建 DecorView对象,然后把 R.layout.activity_main布局文件加载到 DecorView,没有调用 onMeasure,所以不能在 onCreate中直接 获取 mTextView高度,这里的高度为0;
- Activity启动流程
Activity启动流程,就是分析 ActivityThread源码:
handleLaunchActivity__>performLaunchActivity__>onCreate
handleResumeActivity__>performResumeActivity__>onResume
wm.addView(decor , 1)
这个时候才把 我们的 DecorView 加载到 WindowManager 中,View 绘制流程在这个时候才开始的,也就是说 onMeasure、onLayout、onDraw、才开始的,所以说 View的绘制流程在 onResume之后开始的,所以说 在onResume中不能获取宽高,需要在 onResume执行完毕后获取宽高;
也就是说 Activity启动流程:只是把 DecorView添加到 WindowManager中
setContentView源码分析
第一:Activity源码中的setContentView:
public void setContentView(@LayoutRes int layoutResID) {
// 获取 getWindow的 setContentView方法,但是setContentView是抽象方法,
// 需要找 实现类 PhoneWindow
getWindow().setContentView(layoutResID);
}
// mWindow是 PhoneWindow,如下创建系统的 PhoneWindow 对象,说明第一层是 PhoneWindow
public Window getWindow() {
return mWindow;
}
mWindow = new PhoneWindow(this, window);
所以需要在 PhoneWindow中 查找 setContentView:
// PhoneWindow的 setContentView
@Override
public void setContentView(int layoutResID) {
// 如果 mContentParent 为空,调用 installDecor()
if (mContentParent == null) {
installDecor();
}
// 把我们自己的 setContentView 设置到 mContentParent
mLayoutInflater.inflate(layoutResID, mContentParent);
}
第二:对于installDecor:这里有2个方法:分别是:generateDecor 和 generateLayout
private DecorView mDecor;
private void installDecor() {
if (mDecor == null) {
// 第二层:DecorView,创建 DecorView对象
mDecor = generateDecor(-1);
}
if (mContentParent == null) {
// 第三层:mContentParent,把 setContentView 加载到 mContentParent
mContentParent = generateLayout(mDecor);
}
// generateDecor:创建DecorView对象, DecorView 继承 FrameLayout,所以 第二层是 DecorView
protected DecorView generateDecor(int featureId) {
return new DecorView(context, featureId, this, getAttributes());
}
public class DecorView extends FrameLayout implements RootViewSurfaceTaker{}
protected ViewGroup generateLayout(DecorView decor) {
int layoutResource;
// 通过一系列的 if...else... 判断,最终会调用到 R.layout.screen_simple,说明它是第三层
// 它的最外层是 LinearLayout,
layoutResource = R.layout.screen_simple;
// 把 screen_simple 布局文件添加到 DecorView
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ID_ANDROID_CONTENT:是screen_simple布局文件中的 一个 控件,是 FrameLayout,
// id 为 android.R.id.content,这个 id 把 我们的 setContentView 加载到 mContentParent 中
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 返回
return contentParent;
}
对于 screen_simple:
// android.R.id.content
- LayoutInflater源码分析
LayoutInflater的源码我们分三个步骤去看相对来说会更加的系统:
4. 1 如何获取LayoutInflater?
4. 2 如何使用LayoutInflater?
4. 3 布局的View是如何被实例化的?
先来看看我们平时都是怎么去获取LayoutInflater的,这个我们其实并不陌生LayoutInflater.from(context):
/**
* Obtains the LayoutInflater from the given context.
*/
// 是一个静态的方法
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);
}
/**
* Gets a system service from a given context.
*/
// 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());
}});
}
// 注册很多的其他服务......
}
接下来大致的整理一下获取LayoutInflater的思路,通过Context的实现类ContextImpl获取的,最终是通过SystemServiceRegistry.getSystemService()方法,而SYSTEM_SERVICE_FETCHERS是一个静态的HashMap,初始化是在静态代码块中通过registerService注册了很多服务。所以到目前为止我们有两个思想对于我们后面插件化的皮肤框架有很大的关系,第一LayoutInflater其实是一个系统的服务,第二每次通过LayoutInflater.form(context)是一个静态的单例类无论在哪里获取都是同一个对象。接下来我们来看一下加载布局的三种方式:
1.View.inflate(context,layoutId,parent);
2.LayoutInflater.from(context).inflate(layoutId,parent);
3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);
1.View.inflate(context,layoutId,parent);
// 其实就是调用的 LayoutInflater.from(context).inflate(layoutId,parent);
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
2.LayoutInflater.from(context).inflate(layoutId,parent);
// 其实就是调用的 LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot); 其实最终都是调用的该方法,我们关键是要弄清楚这个参数的概念,尤其是attachToRoot:
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
View result = root;
try {
// Look for the root node.
int type;
//在这里找到root标签
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!");
}
//获取这个root标签的名字
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");
}
//这里直接加载页面,忽略merge标签,直接传root进rInflate进行加载子view
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//通过标签来获取view
//先获取加载资源文件中的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//布局参数
ViewGroup.LayoutParams params = null;
//关键代码A
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//temp设置布局参数
temp.setLayoutParams(params);
}
}
......
//关键代码B
//在这里,先获取到了temp,再把temp当做root传进去rInflateChildren
//进行加载temp后面的子view
rInflateChildren(parser, temp, attrs, true);
......
//关键代码C
if (root != null && attachToRoot) {
//把view添加到root中并设置布局参数
root.addView(temp, params);
}
//关键代码D
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) {
// ......省略部分代码
}
}
这里有两个思想比较重要第一个View的创建是通过当前View的全类名反射实例化的View,第二个View的创建首先会走mFactory2,然后会走mFactory,只要不为空先会去执行Factory的onCreateView方法,最后才会走系统的LayoutInflater里面的createView()方法,所以我们完全可以自己去实例化View,这对于我们的插件化换肤很有帮助。
总结
1>:Activity中调用 setContentView,布局显示在 PhoneWindow中,在PhoneWindow中实例化 DecorView;
2>:然后 做一系列判断,解析系统资源文件,把 android.R.layout.screen_simple 加载到 DecorView中,screen_simple布局文件最外层是一个 LinearLayout,里边有一个 id是 android.R.id.content,是一个 FrameLayout;
3>:我们自己的 setContentView 布局文件 加载到 mContentParent中,就是 android.R.id.content 的 FrameLayout;