Android布局优化(一)LayoutInflate — 从布局加载原理说起_第1张图片

系列文章

  • Android布局优化(一)LayoutInflate — 从布局加载原理说起
  • Android布局优化(二)优雅获取界面布局耗时
  • Android布局优化(三)使用AsyncLayoutInflater异步加载布局
  • Android布局优化(四)X2C — 提升布局加载速度200%
  • Android布局优化(五)绘制优化—避免过度绘制

目录

Android布局优化(一)LayoutInflate — 从布局加载原理说起_第2张图片

前言

最近打算写一些Android布局优化相关的文章,既然要进行布局优化,就要先了解布局加载原理,才能知道有哪些地方可以作为优化的切入点。开发同学做任何事情最好都能够知其所以然

布局加载源码分析

这里主要为了分析布局加载相关的原理,所以省略了一些逻辑。想了解View绘制流程,可以看最全的View绘制流程(上)— Window、DecorView、ViewRootImp的关系

我们先从Activity.setContentView开始分析布局是如何被加载的

Activity.setContentView(@LayoutRes int layoutResID)

public void setContentView(@LayoutRes int layoutResID) {    getWindow().setContentView(layoutResID);    initWindowDecorActionBar();}

PhoneWIndow.setContentView(int layoutResID)

@Overridepublic void setContentView(int layoutResID) {    if (mContentParent == null) {        //初始化DecorView和mContentParent        installDecor();    }    ...        //加载资源文件,创建view树装载到mContentParent        mLayoutInflater.inflate(layoutResID, mContentParent);    ...}

LayoutInflate.inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot)

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {    final Resources res = getContext().getResources();    //1.加载解析xml文件    final XmlResourceParser parser = res.getLayout(resource);    try {        //2.填充View树        return inflate(parser, root, attachToRoot);    } finally {        parser.close();    }}

可以看出布局加载流程主要分为加载解析xml文件填充View树两部分

加载解析xml文件

Resources.getLayout(@LayoutRes int id)

 public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {        return loadXmlResourceParser(id, "layout"); }

Resources.loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,@NonNul l String type)

XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,@NonNul l String type) throws NotFoundException {        if (id != 0) {            try {                synchronized (mCachedXmlBlocks) {                    final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies;                    final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles;                    final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;                    // First see if this block is in our cache.                    final int num = cachedXmlBlockFiles.length;                    for (int i = 0; i < num; i++) {                        if (cachedXmlBlockCookies[i] == assetCookie && cachedXmlBlockFiles[i] != null                                && cachedXmlBlockFiles[i].equals(file)) {                            return cachedXmlBlocks[i].newParser();                        }                    }                    // Not in the cache, create a new block and put it at                    // the next slot in the cache.                    final XmlBlock block = mAssets.openXmlBlockAsset(assetCookie, file);                    if (block != null) {                        final int pos = (mLastCachedXmlBlockIndex + 1) % num;                        mLastCachedXmlBlockIndex = pos;                        final XmlBlock oldBlock = cachedXmlBlocks[pos];                        if (oldBlock != null) {                            oldBlock.close();                        }                        cachedXmlBlockCookies[pos] = assetCookie;                        cachedXmlBlockFiles[pos] = file;                        cachedXmlBlocks[pos] = block;                        return block.newParser();                    }                }            } catch (Exception e) {                final NotFoundException rnf = new NotFoundException("File " + file                        + " from xml type " + type + " resource ID #0x" + Integer.toHexString(id));                rnf.initCause(e);                throw rnf;            }        }        throw new NotFoundException("File " + file + " from xml type " + type + " resource ID #0x"                + Integer.toHexString(id));    }

我们不用非常深入这个方法的具体实现细节,我们只需要知道,这个方法的作用就是将我们写的xml文件读取到内存中,并进行一些数据解析和封装。所以这个方法本质上就是一个IO操作,我们知道,IO操作往往是比较耗费性能的

填充View树

LayoutInflate.inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot)

public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {    synchronized (mConstructorArgs) {        final Context inflaterContext = mContext;        final AttributeSet attrs = Xml.asAttributeSet(parser);        Context lastContext = (Context) mConstructorArgs[0];        mConstructorArgs[0] = inflaterContext;        View result = root;            int type;            final String name = parser.getName();            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                final View temp = createViewFromTag(root, name, inflaterContext, attrs);                ViewGroup.LayoutParams params = null;                if (root != null) {                    params = root.generateLayoutParams(attrs);                    if (!attachToRoot) {                        temp.setLayoutParams(params);                    }                }                rInflateChildren(parser, temp, attrs, true);                              if (root != null && attachToRoot) {                    root.addView(temp, params);                }                if (root == null || !attachToRoot) {                    result = temp;                }            }        return result;    }}

上面这个方法中我们最主要关注createViewFromTag(View parent, String name, Context context, AttributeSet attrs)

View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,        boolean ignoreThemeAttr) {      //解析view标签    if (name.equals("view")) {        name = attrs.getAttributeValue(null, "class");    }    //如果需要该标签与主题相关,需要对context进行包装,将主题信息加入context包装类ContextWrapper    if (!ignoreThemeAttr) {        final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);        final int themeResId = ta.getResourceId(0, 0);        if (themeResId != 0) {            context = new ContextThemeWrapper(context, themeResId);        }        ta.recycle();    }    if (name.equals(TAG_1995)) {       //BlinkLayout是一种闪烁的FrameLayout,它包裹的内容会一直闪烁,类似QQ提示消息那种。        return new BlinkLayout(context, attrs);    }            //设置Factory,来对View做额外的拓展,这块属于可定制的内容        View view;        if (mFactory2 != null) {            view = mFactory2.onCreateView(parent, name, context, attrs);        } else if (mFactory != null) {            view = mFactory.onCreateView(name, context, attrs);        } else {            view = null;        }        if (view == null && mPrivateFactory != null) {            view = mPrivateFactory.onCreateView(parent, name, context, attrs);        }          //如果此时不存在Factory,不管Factory还是Factory2,还是mPrivateFactory都不存在,            //那么会直接对name直接进行解析        if (view == null) {            final Object lastContext = mConstructorArgs[0];            mConstructorArgs[0] = context;            try {                //如果name中包含"."即为自定义View,否则为原生的View控件                if (-1 == name.indexOf('.')) {                    view = onCreateView(parent, name, attrs);                } else {                    view = createView(name, null, attrs);                }            } finally {                mConstructorArgs[0] = lastContext;            }        }        return view;}

根据源码可以将createViewFromTag分为三个流程:

  1. 对一些特殊标签,做分别处理,例如:viewTAG_1995(blink)

  2. 进行对FactoryFactory2的设置判断,如果设置那么就会通过设置FactoryFactory2进行生成View

  3. 如果没有设置FactoryFactory2,那么就会使用LayoutInflater默认的生成方式,进行View的生成

createViewFromTag过程分析:

  1. 处理view标签

如果标签的名称是view,注意是小写的view,这个标签一般大家不太常用

在使用时,相当于所有控件标签的父类一样,可以设置class属性,这个属性会决定view这个节点会变成什么控件

  1. 如果该节点与主题相关,则需要特殊处理

如果该节点与主题(Theme)相关,需要将context与theme信息包装至ContextWrapper

  1. 处理TAG_1995标签

这就有意思了,TAG_1995指的是blink这个标签,这个标签感觉使用的很少,以至于大家根本不知道。

这个标签最后会被解析成BlinkLayoutBlinkLayout其实就是一个FrameLayout,这个控件最后会将包裹内容一直闪烁(就和电脑版QQ消息提示一样)

        
  1. 判断其是否存在Factory或者Factory2

在这里先对Factory进行判空,这里不管Factory还是Factory2mPrivateFactory 就是Factory2),本质上都是一种扩展操作,提前解析name,然后直接将解析后的View返回

Factory

public interface Factory {    public View onCreateView(String name, Context context, AttributeSet attrs);}

Factory2

public interface Factory2 extends Factory {    public View onCreateView(View parent, String name, Context context, AttributeSet attrs);}

从这里可以看出,Factory2Factory都是一个接口,需要自己实现,而Factory2Factory的区别是Factory2继承Factory,从而扩展出一个参数,就是增加了该节点的父View。设置FactoryFactory2需要通过setFactory()或者setFactory2()来实现

setFactory()

public void setFactory(Factory factory) {    //如果已经设置Factory,不可以继续设置Factory    if (mFactorySet) {        throw new IllegalStateException("A factory has already been set on this LayoutInflater");    }    if (factory == null) {        throw new NullPointerException("Given factory can not be null");    }    //设置Factory会添加一个标记    mFactorySet = true;    if (mFactory == null) {        mFactory = factory;    } else {        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);    }}

setFactory2()

public void setFactory2(Factory2 factory) {    if (mFactorySet) {        throw new IllegalStateException("A factory has already been set on this LayoutInflater");    }    if (factory == null) {        throw new NullPointerException("Given factory can not be null");    }    //注意设置Factory和Factory2的标记是共用的    mFactorySet = true;    if (mFactory == null) {        mFactory = mFactory2 = factory;    } else {        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);    }}

通过上面代码可以看出,FactoryFactory2只能够设置一次,并且FactoryFactory2二者互斥,只能存在一个。所以一般setFactory()或者setFactory2(),一般在cloneInContext()之后设置,这样生成一个新的LayoutInflater,标记默认是false,才能够设置

  1. createView(String name, String prefix, AttributeSet attrs)
 public final View createView(String name, String prefix, 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 {        //如果构造器不存在,这个就相当于Class之前是否被加载过,sConstructorMap就是缓存这些Class的Map            if (constructor == null) {                //通过前缀+name的方式去加载                clazz = mContext.getClassLoader().loadClass(                        prefix != null ? (prefix + name) : name).asSubclass(View.class);                //通过过滤去设置一些不需要加载的对象                if (mFilter != null && clazz != null) {                    boolean allowed = mFilter.onLoadClass(clazz);                    if (!allowed) {                        failNotAllowed(name, prefix, attrs);                    }                }                constructor = clazz.getConstructor(mConstructorSignature);                constructor.setAccessible(true);                //缓存Class                sConstructorMap.put(name, constructor);            } else {            //如果Class存在,并且加载Class的ClassLoader合法                //这里先判断该Class是否应该被过滤                if (mFilter != null) {                    //过滤器也有缓存之前的Class是否被允许加载,判断这个Class的过滤状态                    Boolean allowedState = mFilterMap.get(name);                    if (allowedState == null) {                        //加载Class对象操作                        clazz = mContext.getClassLoader().loadClass(                                prefix != null ? (prefix + name) : name).asSubclass(View.class);                        //判断Class是否可被加载                        boolean allowed = clazz != null && mFilter.onLoadClass(clazz);                        mFilterMap.put(name, allowed);                        if (!allowed) {                            failNotAllowed(name, prefix, attrs);                        }                    } else if (allowedState.equals(Boolean.FALSE)) {                        failNotAllowed(name, prefix, attrs);                    }                }            }            Object[] args = mConstructorArgs;            args[1] = attrs;                                //如果过滤器不存在,直接实例化该View            final View view = constructor.newInstance(args);            //如果View属于ViewStub那么需要给ViewStub设置一个克隆过的LayoutInflater            if (view instanceof ViewStub) {                final ViewStub viewStub = (ViewStub) view;                viewStub.setLayoutInflater(cloneInContext((Context) args[0]));            }            return view

从上面的代码可以看出,我们是通过反射的方式去创建View实例的

总结

经过对布局加载原理的分析,我们可以看出布局加载的主要性能瓶颈主要在两个方面

  1. 加载xml文件是一个IO过程,如果xml文件过大,就会比较耗时

  2. View实例是通过反射进行创建的,通过反射创建对象相对会更耗费性能

Android布局优化(一)LayoutInflate — 从布局加载原理说起_第3张图片 android布局加载过程

更多相关文章

  1. 相对布局(RelativeLayout)
  2. Android 常用布局整理
  3. RelativeLayout布局用到的主要属性
  4. android 控件位置常用布局
  5. Android界面布局——视图/容器易混淆点总结
  6. Android View布局xml常用 属性详解
  7. android 相对布局属性说明
  8. android布局控件的用法
  9. android studio 加载.proto不能生成xxxDrpc的问题

随机推荐

  1. android日记-
  2. Android无视屏幕解锁保护界面
  3. Android(安卓)中TextView中跑马灯效果的
  4. Android(安卓)display架构分析(三)
  5. 关于Android(安卓)列表多布局的那些事
  6. Android(安卓)纯代码加入点击效果
  7. 四大组件_Service_AIDL_1
  8. Manifest.xml中的属性
  9. Android相机对焦问题
  10. Android类参考---Fragment