android布局优化过程,Android布局优化(一)LayoutInflate — 从布局加载原理说起

如需转载请评论或简信,并注明出处,未经允许不得转载

8ca35e86d476

系列文章

目录

8ca35e86d476

前言

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

布局加载源码分析

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

Activity.setContentView(@LayoutRes int layoutResID)

public void setContentView(@LayoutRes int layoutResID) {

getWindow().setContentView(layoutResID);

initWindowDecorActionBar();

}

PhoneWIndow.setContentView(int layoutResID)

@Override

public 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分为三个流程:

对一些特殊标签,做分别处理,例如:view,TAG_1995(blink)

进行对Factory、Factory2的设置判断,如果设置那么就会通过设置Factory、Factory2进行生成View

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

createViewFromTag过程分析:

处理view标签

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

class="RelativeLayout"

android:layout_width="match_parent"

android:layout_height="match_parent">

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

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

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

处理TAG_1995标签

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

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

android:layout_width="wrap_content"

android:layout_height="wrap_content">

android:layout_width="wrap_content"

android:layout_height="wrap_content"

android:text="这个标签会一直闪烁"/>

判断其是否存在Factory或者Factory2

在这里先对Factory进行判空,这里不管Factory还是Factory2(mPrivateFactory 就是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);

}

从这里可以看出,Factory2和Factory都是一个接口,需要自己实现,而Factory2和Factory的区别是Factory2继承Factory,从而扩展出一个参数,就是增加了该节点的父View。设置Factory和Factory2需要通过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);

}

}

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

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实例的

总结

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

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

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

8ca35e86d476

android布局加载过程

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值