LayoutInflater
1、介绍:
看一下类的翻译:
1.1、作用:
1. 将XML对象转换为View对象,将XML转换为View对象。
2.哇,这一看就特别重要,用到XML布局文件的地方都需要用到它
(Activity,Fragment,等等各种组件中都使用到它)
3. 所以掌握它的流程就特别重要了
1.2、使用:
1. android.app.Activity#getLayoutInflater()
2. Context#getSystemService
2、研究学习
我们看到它是通过getSystemService获取得到,那么它肯定是个系统服务了。应用进程与实现该服务的进程之间的通信就是Binder通信了。
为了理解方便,我们拿Activity的流程中的LayoutInflater作为了解。
在此之前,我们先简单了解一下这个Context。
2.1 Context
在android系统中,我们经常使用Context获取系统级别的服务,如WMS,AMS等。这些服务常常在合适的时候注册在系统中。我们需要之时,就通过Context的getSystemService(String name)来获取。
问题:那么我们的LayoutInflater在什么合适的时候完成注册的呢?
2.1.1、简单了解
其实在我们的Application,Activity,Service中都会存在一个Context对象,Context的总个数=Activity个数+Service个数+1
那么我们查看代码就发现这个Context是个抽象类,那么可想而知应该有一个实现它的类:ContextImp。
2.2 Activity中的LayoutInflater
2.2.1 Activity的入口
ActivityThread.main()(当然如果要问为什么是这里,那么这个流程也是比较复杂,涉及较多)
为了自己记忆更深刻来一个回顾吧!
- 点击桌面应用A图标(这个进程应该就是我们的Launcher应用所在的进程)
- 那么A肯定还没有进程,即需要给A创建一个进程,怎么创?
- Launcher所在进程通过Binder向AMS所在进程发送消息,“给A创建一个进程”
- AMS所在的进程为system_server进程,我们知道能fork产生新进程的只有zygote进程。
- 所以现在system_server进程要告诉zygote进程,帮我为A创建一个进程(他们俩时通过socket通信的并不是Binder,原因:自己查查)
- 所以这时zygote进程就为A应用fork出一个新的进程
- zygote进程随后通过反射拿到A的这个进程的主线程的main函数:ActivityThread.main() 并且调用它
- 所以就是这样吧!(大概这样吧,应该没错,暂时只理解到这个地步,还需加强)
我们要明白 ActivityTread 是 应用进程的主线程就行
2.2.2 ActivityThread.main()
创建一个ActivityThread对象,并且启动消息循环(UI线程),创建新的Context对象,然后将Context对象传给Activity
public static void main(String[] args) {
//代码略
Process.setArgV0("<pre-initialized>");
Looper.prepareMainLooper();
ActivityThread thread = new ActivityThread();
thread.attach(false, startSeq);//进入2.2.3
}
2.2.3thread.attach()
两种情况,一种是系统应用的情况,一种是非系统应用的情况
非系统应用的情况:
private void attach(boolean system, long startSeq) {
if (!system) {
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {//和AMS所在进程进行通信,调用AMS相关函数-进入2.2.4
mgr.attachApplication(mAppThread, startSeq);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
//代码略
}
}
2.2.4 要AMS帮忙
当然我们要显示界面出来,这个忙需要AMS来帮。调用AMS流程太多最后还是回到我们的应用进程的ActivityThread线程中调用handleLaunchActivity()
public Activity handleLaunchActivity(){
//...
Activity a = performLaunchActivity(r, customIntent);
//...
}
//->performLaunchActivity
performLaunchActivity{
//这里就是我们的Context对象创建
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
try {//然后反射创建Activity对象,并将Context对象给它
java.lang.ClassLoader cl = appContext.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
StrictMode.incrementExpectedActivityCount(activity.getClass());
r.intent.setExtrasClassLoader(cl);
r.intent.prepareToEnterProcess();
if (r.state != null) {
r.state.setClassLoader(cl);
}
} catch (Exception e) {
//...
}
}
但是到这里,我们还是没有看到LayoutInflater出现啊?-_-
2.2.5我们再看一下Context对象的创建
createBaseContextForActivity()
|
createActivityContext()
|
createActivityContext{
//...
//调用它的构造函数创建Context对象
ContextImpl context = new ContextImpl(null, mainThread, packageInfo,
activityInfo.splitName,
activityToken, null, 0, classLoader, null);
//...
}
我们可以看一下ContextImpl对象中有什么?
注意到ContextImpl中有一个成员:mServiceCache,当我上一步new ContextImpl之后,他这里就会被初始化
// The system service cache for the system services
//that are cached per-ContextImpl.
@UnsupportedAppUsage
final Object[] mServiceCache =
SystemServiceRegistry.createServiceCache();
我们可以进入SystemServiceRegistry类看一看:里面有一个静态块static{},所以第一次调用SystemServiceRegistry时候static就会被执行
它注册了很多很多的服务:
当然它这里的注册只是将对应类的实例进行保存而已,
可供后面使用直接调用,方便管理,且实例唯一
--这里使用了-容器来设计单例
static {
//...
//目标服务被注册
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
//...
}
2.2.6 LayoutInfater的实现类PhoneLayoutInflater
我们看一下LayoutInfater的实现类PhoneLayoutInflater做了什么。
主要从重写了这个onCreateView方法,后面会用到
作用:
- 该方法根据传进来的View名字,前都加上“android.widget”或者“android.webkit”
- 前缀用以得到内置的View类(TextView和Button类等都在android.widget包下)
- 根据完整的路径,调用父类LayoutInflater的onCreateView构建View对象
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
//...
@Override 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);
}
}
当然我们走到这,只是梳理了LayoutInflater的实现类初始化相关的内容,
问题:具体怎么使用?它的原理我们还没有研究
2.3 layoutInflater做了什么?
当然只说了初始化,具体调用我们还没有调用,那么以Activity为例看一下,Activity是如何使用它的。
既然说它是解析XML布局文件的,那么我们就要看我们Activty的setContView
2.3.1Activity 中setContentView()
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);//进入2.3.2
initWindowDecorActionBar();
}
这里调用getWindow获得Window的对象,因为Window是抽象类,它的实现类是PhoneWindow
2.3.2PhoneWindow的setContentView()
我们看到mDecor会加重一个系统定义好的布局,这个布局包裹这个mContentParent,mContentParent就是我们定义的布局,并将它添加到parent区域
public void setContentView(int layoutResID) {
//FEATURE_CONTENT_TRANSITIONS有没有渐入渐出动画
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 {
//进入2.3.3
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}
2.3.3 mLayoutInflater.inflate
如何将布局的视图添加到mContentParent中?
我们可以看一下这个mLayoutInflater从哪里得到的,可以看到这个mLayoutInflater就是我们之前初始化的PhoneLayoutInflater对象
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
继续看mLayoutInflater.inflate
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) + ")");
}
// 这里应该是如果以前解析过就直接返回
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//创建解析器XmlResourceParser
XmlResourceParser parser = res.getLayout(resource);
try {
//进入2.3.4
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
2.3.4 inflate(parser, root, attachToRoot);
- 参数1:要解析布局资源解析器,
- 参数2:要解析的布局的根布局
- 参数3:是否将解析结果添加到root中
代码有点多,将流程走向的分支简单梳理
- 先解析找出xml资源中的根标签
- 如果跟标签是merge标记,表面要将merge标签下的所有View直接添加到跟标签中(使用rInflate完成)-2.3.6
- 如果标签是普通元素,通过xml的tag解析layout的根视图,那么为要解析的视图类的名字-createViewFromTag(root, name, inflaterContext, attrs)-2.3.5
- rInflateChildren解析temp根元素的所有子View,最终也会调用rInflate
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
//...
try {
//这一步要找到XML的根标签(有START_TAG标记)
advanceToRootNode(parser);
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
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);
ViewGroup.LayoutParams params = null;
if (root != null) {
//生成布局参数
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
temp.setLayoutParams(params);
}
}
//解析temp下所有子实体
rInflateChildren(parser, temp, attrs, true);
//...
//如果root不为空,如果attachToroot为true,那么将temp添加到父视图中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
// 如果root为null,attachToRoot为false,返回temp
// top view found in xml.
if (root == null || !attachToRoot) {
result = temp;
}
}
}
//...
return result;
}
2.3.5LayoutInflater.createViewFromTag()
这里可以了解一下View的创建
1.情况1:view的名字没有“ . ”–系统内置View
那么就要使用前面PhoneLayoutInflater.onCreateView()补全完整路径使用
createView()拿到View类的构造函数反射创建view对象
2.情况2:view的名字有“ . ”的情况–自定义View
直接调用createView反射创建view对象
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
//...
try {
//之前已经创建过直接返回
View view = tryCreateView(parent, name, context, attrs);
//调用creatView创建
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {//情况1
view = onCreateView(context, parent, name, attrs);//前面2.2.6
} else {//情况2
view = createView(context, name, null, attrs);//略
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
//异常处理省略
}
2.3.6LayoutInflater.rInflate()
递归方法用于向下归纳 xml 层次结构并实例化视图,实例化其子级,然后调用FinishInflate()。
毫无疑问这里要使用递归分别处理XML中的各个节点的情况
- 情况1:type == XmlPullParser.START_TAG:跳过
- 情况2:name == TAG_REQUEST_FOCUS–“requestFocus”
- 情况3:name == TAG_TAG–“tag”
- 情况4:name == TAG_INCLUDE–“include”
- 情况5:name == TAG_MERGE–“merge”
- 情况6:真正进行解析,我们将该xml文件想象为一棵树,那么对应的就是深度优先遍历
- 直到整个树构建完成完成
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) {
//.START_TAG标记的标签跳过情况1
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {//情况2
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {//情况3
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {//情况4
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {//情况5
throw new InflateException("<merge /> must be the root element");
} else {//情况6
//2.3.5
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();
}
}
3.总结
- 梳理了LayoutInflater的初始化,注册流程
- 以及我们在Activity中的setContentView()中LayoutInflater做了什么情况进行梳理
主要
完成xml文件的解析,构建了一整棵view对象树,当然构建完可能还没有显示出来,后面还需继续研究如何显示出来