我的编程空间,编程开发者的网络收藏夹
学习永远不晚

Android布局填充器--深入LayoutInflater一探究竟

短信预约 -IT技能 免费直播动态提醒
省份

北京

  • 北京
  • 上海
  • 天津
  • 重庆
  • 河北
  • 山东
  • 辽宁
  • 黑龙江
  • 吉林
  • 甘肃
  • 青海
  • 河南
  • 江苏
  • 湖北
  • 湖南
  • 江西
  • 浙江
  • 广东
  • 云南
  • 福建
  • 海南
  • 山西
  • 四川
  • 陕西
  • 贵州
  • 安徽
  • 广西
  • 内蒙
  • 西藏
  • 新疆
  • 宁夏
  • 兵团
手机号立即预约

请填写图片验证码后获取短信验证码

看不清楚,换张图片

免费获取短信验证码

Android布局填充器--深入LayoutInflater一探究竟

文章目录

Android源码–深入LayoutInflater一探究竟(基于android-12.0.0_r34分析)

前文:

单例设计模式在Android开发实际应用场景解析–activity的管理

https://blog.csdn.net/weixin_46039528/article/details/132287718?spm=1001.2014.3001.5501

Android源码设计模式–单例模式分析,系统服务开机注册单例模式源码解析

https://blog.csdn.net/weixin_46039528/article/details/132309733?spm=1001.2014.3001.5501

LayoutInflater 是一个抽象类,找到它具体的实现类

@SystemService(Context.LAYOUT_INFLATER_SERVICE)public abstract class LayoutInflater {......}

系统开机注册

registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,                new CachedServiceFetcher<LayoutInflater>() {            @Override            public LayoutInflater createService(ContextImpl ctx) {                return new PhoneLayoutInflater(ctx.getOuterContext());}});

跟进PhoneLayoutInflater,这里看到PhoneLayoutInflater是LayoutInflater的一个实现类。

public class PhoneLayoutInflater extends LayoutInflater {    //TextView 的完整路径是 android.widget.TextView,一些view的前缀字符串    private static final String[] sClassPrefixList = {        "android.widget.",        "android.webkit.",        "android.app."    };......        @Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {    //在View名字的前面添加前缀来构造 View 的完整路径,android.widget.TextView        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.            }        }......}

核心就是onCreateView通过传进来的名字,name,prefix前缀构造出对应的View对象,比如TextView。

接下来我们康康AppCompatActivity的setContentView,它的核心就是通过LayoutInflater来加载我们的布局。AppCompatActivity 的 setContentView 方法和 Activity的 setContentView 方法是有区别的。因为我们都是继承AppCompatActivity开发较多,此处只对AppCompatActivity分析。

@Overridepublic void setContentView(@LayoutRes int layoutResID) {    initViewTreeOwners();    //getDelegate返回的是一个对象。    getDelegate().setContentView(layoutResID);}

跟进getDelegate,看到返回的是AppCompatDelegate,这是一个抽象类。

@NonNullpublic AppCompatDelegate getDelegate() {    if (mDelegate == null) {        mDelegate = AppCompatDelegate.create(this, this);    }    return mDelegate;}

我们来看 AppCompatDelegate 的 create(Activity activity, AppCompatCallback callback) 方法,

        @NonNull    public static AppCompatDelegate create(@NonNull Dialog dialog,            @Nullable AppCompatCallback callback) {        //AppCompatDelegateImpl是它的一个实现类,就是它调用了setContentView        return new AppCompatDelegateImpl(dialog, callback);    }

跟进到AppCompatDelegateImpl中的,有三个重载方法

  ......    @Override    public void setContentView(int resId) {        //初始化DecorView        ensureSubDecor();        //设置setContentView 要的父布局        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);        //加载之前移除所有的布局        contentParent.removeAllViews();        //完成布局的加载        LayoutInflater.from(mContext).inflate(resId, contentParent);        mAppCompatWindowCallback.getWrapped().onContentChanged();    }......

我们看到会加载一个系统定义好的布局contentParent,通过 LayoutInflater 的 inflate 函数将指定布局的视图添加到mContentParent 中。

 public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {        return inflate(resource, root, root != null);    } public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {      ......    // 获取xm1资源解析器        XmlResourceParser parser = res.getLayout(resource);        try {            return inflate(parser, root, attachToRoot);        } finally {            parser.close();        }    }// 参数1为xmL 解析器,参数2为要解析布局的父视图,参数3为是否将要解析的视图添加到父视图中 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {        synchronized (mConstructorArgs) {            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");            final Context inflaterContext = mContext;            final AttributeSet attrs = Xml.asAttributeSet(parser);            Context lastContext = (Context) mConstructorArgs[0];            mConstructorArgs[0] = inflaterContext;            //存储父视图            View result = root;           ......   //解析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");                    }                    rInflate(parser, root, inflaterContext, attrs, false);                } else {                    // Temp is the root view that was found in the xml                    //不是merge 标签那么直接解析布局中的视图                    //这里就是通过 xml的 tag 来解析 layout 根视图                    //name 就是要解析的视图的类名,如 RelativeLayout                    //createViewFromTag会将该元素的parent 及名字传递过来                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);                    ViewGroup.LayoutParams params = null;                    if (root != null) {                        if (DEBUG) {System.out.println("Creating params from root: " +        root);                        }                        // Create layout params that match root, if supplied                        //生成布局参数                        params = root.generateLayoutParams(attrs);                        //如果attachToRoot 为 false,给 temp 设置布局参数                        if (!attachToRoot) {// Set the layout params for temp if we are not// attaching. (If we are, we use addView, below)temp.setLayoutParams(params);                        }                    }                    ......                    // Inflate all children under temp against its context.                    //解析 temp 下的所有子view                    rInflateChildren(parser, temp, attrs, true);             ......                    // We are supposed to attach all the views we found (int temp)                    // to root. Do that now.                    //如果Root 不为空,attachToRoot 为true,将 temp 添加到父视图中                    if (root != null && attachToRoot) {                        root.addView(temp, params);                    }                    // Decide whether to return the root that was passed in or the                    // top view found in xml.                    //如果root 为空或者attachToRoot 为 false,结果就是 temp                    if (root == null || !attachToRoot) {                        result = temp;                    }                }......            return result;        }    }

大致流程:解xml中的根标签,最外层的xml布局元素,如果根标签是 merge,那么调用rInflate进行解析,rnflate会将 merge 标签下的所有子View直接添加到根标签中,如果标签是普通元素,调用createViewFromTag 对该元素进行解析,调用rInflate解析 temp 根元素下的所有子 View,并且将这些子 View 都添加到temp下。

解析单个元素的createViewFromTag

@UnsupportedAppUsage(maxTargetSdk = Build.VERSION_CODES.R, trackingBug = 170729553)    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);            if (view == null) {                final Object lastContext = mConstructorArgs[0];                mConstructorArgs[0] = context;                try {                    //安卓系统自带View控件的创建解析                    if (-1 == name.indexOf('.')) {                        view = onCreateView(context, parent, name, attrs);                    } else {                     //自定义控件创建解析                        view = createView(context, name, null, attrs);                    }                } finally {                    mConstructorArgs[0] = lastContext;                }            }            ......    }

跟进tryCreateView

@UnsupportedAppUsage(trackingBug = 122360734)    @Nullable    public final View tryCreateView(@Nullable View parent, @NonNull String name,        @NonNull Context context,        @NonNull AttributeSet attrs) { ......        View view;        //通过onCreateView创建View        if (mFactory2 != null) {            view = mFactory2.onCreateView(parent, name, context, attrs);        } else if (mFactory != null) {            view = mFactory.onCreateView(name, context, attrs);        } else {            view = null;        }        ......        return view;    }

最终我们的view都是通过createView方法创建的。

//通过反射机制构造 view对象@Nullablepublic final View createView(@NonNull Context viewContext, @NonNull String name,        @Nullable String prefix, @Nullable AttributeSet attrs)        throws ClassNotFoundException, InflateException {   ......    //缓存中获取构造函数    Constructor<? extends View> constructor = sConstructorMap.get(name);    if (constructor != null && !verifyClassLoader(constructor)) {        constructor = null;        sConstructorMap.remove(name);    }    Class<? extends View> clazz = null;    try {        Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);        if (constructor == null) {            // Class not found in the cache, see if it's real, and try to add it            //如果 prefix不为空,那么构造完整的 View 路径加载该类            clazz = Class.forName(prefix != null ? (prefix + name) : name, false,                    mContext.getClassLoader()).asSubclass(View.class);            ......             //从class对象中获取构造函数            constructor = clazz.getConstructor(mConstructorSignature);            constructor.setAccessible(true);            //将构造函数存入缓存中            sConstructorMap.put(name, constructor);        } else {            ......        }......        try {            //通过反射构造View            final View view = constructor.newInstance(args);  ......}

这只是单个View的xml解析,要把子View全部解析完,我们看rInflate。

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) {......            } else {                //元素名进行解析                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);            }        }......    }

来源地址:https://blog.csdn.net/weixin_46039528/article/details/132353252

免责声明:

① 本站未注明“稿件来源”的信息均来自网络整理。其文字、图片和音视频稿件的所属权归原作者所有。本站收集整理出于非商业性的教育和科研之目的,并不意味着本站赞同其观点或证实其内容的真实性。仅作为临时的测试数据,供内部测试之用。本站并未授权任何人以任何方式主动获取本站任何信息。

② 本站未注明“稿件来源”的临时测试数据将在测试完成后最终做删除处理。有问题或投稿请发送至: 邮箱/279061341@qq.com QQ/279061341

Android布局填充器--深入LayoutInflater一探究竟

下载Word文档到电脑,方便收藏和打印~

下载Word文档

编程热搜

  • Android:VolumeShaper
    VolumeShaper(支持版本改一下,minsdkversion:26,android8.0(api26)进一步学习对声音的编辑,可以让音频的声音有变化的播放 VolumeShaper.Configuration的三个参数 durati
    Android:VolumeShaper
  • Android崩溃异常捕获方法
    开发中最让人头疼的是应用突然爆炸,然后跳回到桌面。而且我们常常不知道这种状况会何时出现,在应用调试阶段还好,还可以通过调试工具的日志查看错误出现在哪里。但平时使用的时候给你闹崩溃,那你就欲哭无泪了。 那么今天主要讲一下如何去捕捉系统出现的U
    Android崩溃异常捕获方法
  • android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
    系统的设置–>电池–>使用情况中,统计的能耗的使用情况也是以power_profile.xml的value作为基础参数的1、我的手机中power_profile.xml的内容: HTC t328w代码如下:
    android开发教程之获取power_profile.xml文件的方法(android运行时能耗值)
  • Android SQLite数据库基本操作方法
    程序的最主要的功能在于对数据进行操作,通过对数据进行操作来实现某个功能。而数据库就是很重要的一个方面的,Android中内置了小巧轻便,功能却很强的一个数据库–SQLite数据库。那么就来看一下在Android程序中怎么去操作SQLite数
    Android SQLite数据库基本操作方法
  • ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
    工作的时候为了方便直接打开编辑文件,一些常用的软件或者文件我们会放在桌面,但是在ubuntu20.04下直接直接拖拽文件到桌面根本没有效果,在进入桌面后发现软件列表中的软件只能收藏到面板,无法复制到桌面使用,不知道为什么会这样,似乎并不是很
    ubuntu21.04怎么创建桌面快捷图标?ubuntu软件放到桌面的技巧
  • android获取当前手机号示例程序
    代码如下: public String getLocalNumber() { TelephonyManager tManager =
    android获取当前手机号示例程序
  • Android音视频开发(三)TextureView
    简介 TextureView与SurfaceView类似,可用于显示视频或OpenGL场景。 与SurfaceView的区别 SurfaceView不能使用变换和缩放等操作,不能叠加(Overlay)两个SurfaceView。 Textu
    Android音视频开发(三)TextureView
  • android获取屏幕高度和宽度的实现方法
    本文实例讲述了android获取屏幕高度和宽度的实现方法。分享给大家供大家参考。具体分析如下: 我们需要获取Android手机或Pad的屏幕的物理尺寸,以便于界面的设计或是其他功能的实现。下面就介绍讲一讲如何获取屏幕的物理尺寸 下面的代码即
    android获取屏幕高度和宽度的实现方法
  • Android自定义popupwindow实例代码
    先来看看效果图:一、布局
  • Android第一次实验
    一、实验原理 1.1实验目标 编程实现用户名与密码的存储与调用。 1.2实验要求 设计用户登录界面、登录成功界面、用户注册界面,用户注册时,将其用户名、密码保存到SharedPreference中,登录时输入用户名、密码,读取SharedP
    Android第一次实验

目录