页面加载流程、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不同类的情况,都会不太细腻有限制;

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 219,366评论 6 508
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,521评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 165,689评论 0 356
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,925评论 1 295
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,942评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,727评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,447评论 3 420
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,349评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,820评论 1 317
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,990评论 3 337
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 40,127评论 1 351
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,812评论 5 346
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,471评论 3 331
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 32,017评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,142评论 1 272
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,388评论 3 373
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 45,066评论 2 355