页面的加载中,必不可少的是setcontentview,如果是通过资源r.layout来进行布局加载,那么一定绕不开inflate 资源文件。那么setcontentview中到底进行了哪些操作?官方推出的asyncinflatelayout到底优化哪些地方?哪些地方是能再进行一次优化的?
activity中inflate操作简化流程图:
image
从setcontent来看,分为几个步骤
1.将docview,theme级别配置设置到docview和window中
2.docview removeall所有的子view
3.开始从xml中、view 来addview都docview中;
getDelegate().setContentView();
public void setContentView(int resId) {
ensureSubDecor();//theme设置到content,
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
在ensuresubdocor方法中,有值得注意的点:
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
mSubDecor = createSubDecor(); //创建docview同时添加到window中;
...
applyFixedSizeWindow();//contentviewtheme设置;
....
}
}
关键点:
1.createSubDecor()创建docview, 判断类型创建相应的docview set到window中;
之后到xml解析和view生成的逻辑中:
inflate 调用到 android.view.LayoutInflater#inflate(int, android.view.ViewGroup)
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) + ")");
}
//从resource拿到目标资源文件
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
//从resource拿到目标资源文件 具体解析
public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
return loadXmlResourceParser(id, "layout");
}
//指定文件的xml解析器
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
....报错
} finally {
releaseTempTypedValue(value);
}
}
//loadXmlResourceParser 处理类;
XmlResourceParser loadXmlResourceParser(@NonNull String file, @AnyRes int id, int assetCookie,
@NonNull String type)
throws NotFoundException {
if (id != 0) {
try {
synchronized (mCachedXmlBlocks) {
final int[] cachedXmlBlockCookies = mCachedXmlBlockCookies; //缓存块为4
final String[] cachedXmlBlockFiles = mCachedXmlBlockFiles; //缓存块为4
final XmlBlock[] cachedXmlBlocks = mCachedXmlBlocks;//缓存块为4
// 首先看是否在缓存中,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;//有限的块缓存只在4块内进行缓存
mLastCachedXmlBlockIndex = pos;
final XmlBlock oldBlock = cachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();//释放掉之前占位的块;
}
cachedXmlBlockCookies[pos] = assetCookie;//缓存type
cachedXmlBlockFiles[pos] = file;//缓存filename
cachedXmlBlocks[pos] = block; //这个地方注意一下,极限优化中可以用到;
return block.newParser();//返回块中的解析结果
}
}
} catch (Exception e) {
...抛出错误
}
}
...抛出错误
}
//xml parser之后成为拥有start-end-attrs-name的基本数据结构,再通过inflate转化成view实体
//从parser中的基本数据,构建view
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;
try {
// Look for the root node. 查找root节点
int type;
.....查找root节点
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//merge标签验证是否有attachroot
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml 通过tag生成view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
...
params = root.generateLayoutParams(attrs);//生成params,布局参数
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.
rInflateChildren(parser, temp, attrs, true);
...
// We are supposed to attach all the views we found (int temp)to root. Do that now.
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.
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
....exception deal
} finally {
// Don't retain static reference on context.上下文中不要保留静态引用
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
以上的流程中,window是在activity中new出来,docview是根据设置的theme来进行inflate,setcontentview res方式也是通过inflate方法;通过inflate就不可避免的走到读取文件--->格式化解析---->反射出view实体类 这几步;
如果做极限优化,那么我们根据inflate相应的代码可以得出一个优化点,可以在子线程尝试对xml进行加载,只要inflate执行到存储到block缓存中,那么就可以再下次使用相同文件的时候加快加载速度;同时在抖音的优化策略中,有对class的提前load来减小主页上加载速度的优化;
还有公司开发组另辟蹊径,把xml 转view的其中几个步骤进行优化,例如读取xml文件的io耗时,那么我们直接再编译期间将xml文件读取转化成class文件,setcontentview变成代码动态生成布局越过xml的读取解析两部分,直接到inflate的convert步骤中;
asyncinflatelayout是什么框架?其中做了哪些事能异步加载布局?
顾名思义是一个异步加载布局的框架,官方出品。从源码来看不是很重,只是一个thread inflate布局的框架,主要解决inflate中io文件操作,和反射操作阻塞主线程的问题,让infalte在子线程进行,成功之后回调给主线程进行后续处理;
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
//构造函数中的实体类就是整个asyncinflate核心,handler进行回调,thread负责子线程infalte,infalte负责具体加载;
可能是个轻量且优化不是很大,不能作为kpi来进行,所以在asyncinflate的封装上没有考虑得特别完善。从子线程未用线程池,异步加载未考虑调用infalte和加载view不同类的情况,都会不太细腻有限制;
网友评论