LayoutInflater学习记录

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()(当然如果要问为什么是这里,那么这个流程也是比较复杂,涉及较多)

为了自己记忆更深刻来一个回顾吧!

  1. 点击桌面应用A图标(这个进程应该就是我们的Launcher应用所在的进程)
  2. 那么A肯定还没有进程,即需要给A创建一个进程,怎么创?
  3. Launcher所在进程通过Binder向AMS所在进程发送消息,“给A创建一个进程”
  4. AMS所在的进程为system_server进程,我们知道能fork产生新进程的只有zygote进程。
  5. 所以现在system_server进程要告诉zygote进程,帮我为A创建一个进程(他们俩时通过socket通信的并不是Binder,原因:自己查查)
  6. 所以这时zygote进程就为A应用fork出一个新的进程
  7. zygote进程随后通过反射拿到A的这个进程的主线程的main函数:ActivityThread.main() 并且调用它
  8. 所以就是这样吧!(大概这样吧,应该没错,暂时只理解到这个地步,还需加强)

我们要明白 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方法,后面会用到

作用

  1. 该方法根据传进来的View名字,前都加上“android.widget”或者“android.webkit”
  2. 前缀用以得到内置的View类(TextView和Button类等都在android.widget包下)
  3. 根据完整的路径,调用父类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. 参数1:要解析布局资源解析器,
  2. 参数2:要解析的布局的根布局
  3. 参数3:是否将解析结果添加到root中

代码有点多,将流程走向的分支简单梳理

  1. 先解析找出xml资源中的根标签
  2. 如果跟标签是merge标记,表面要将merge标签下的所有View直接添加到跟标签中(使用rInflate完成)-2.3.6
  3. 如果标签是普通元素,通过xml的tag解析layout的根视图,那么为要解析的视图类的名字-createViewFromTag(root, name, inflaterContext, attrs)-2.3.5
  4. 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. 情况1:type == XmlPullParser.START_TAG:跳过
  2. 情况2:name == TAG_REQUEST_FOCUS–“requestFocus”
  3. 情况3:name == TAG_TAG–“tag”
  4. 情况4:name == TAG_INCLUDE–“include”
  5. 情况5:name == TAG_MERGE–“merge”
  6. 情况6:真正进行解析,我们将该xml文件想象为一棵树,那么对应的就是深度优先遍历
  7. 直到整个树构建完成完成
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.总结

  1. 梳理了LayoutInflater的初始化,注册流程
  2. 以及我们在Activity中的setContentView()中LayoutInflater做了什么情况进行梳理

主要
完成xml文件的解析,构建了一整棵view对象树,当然构建完可能还没有显示出来,后面还需继续研究如何显示出来

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值