页面加载流程、setcontentview与inflate过程解析优化

页面的加载中,必不可少的是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不同类的情况,都会不太细腻有限制;