頁面的加載中,必不可少的是setcontentview吱窝,如果是通過資源r.layout來進(jìn)行布局加載讥邻,那么一定繞不開inflate 資源文件。那么setcontentview中到底進(jìn)行了哪些操作癣诱?官方推出的asyncinflatelayout到底優(yōu)化哪些地方计维?哪些地方是能再進(jìn)行一次優(yōu)化的?
activity中inflate操作簡化流程圖:
從setcontent來看撕予,分為幾個步驟
1.將docview鲫惶,theme級別配置設(shè)置到docview和window中
2.docview removeall所有的子view
3.開始從xml中、view 來addview都docview中实抡;
getDelegate().setContentView();
public void setContentView(int resId) {
ensureSubDecor();//theme設(shè)置到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(); //創(chuàng)建docview同時添加到window中;
...
applyFixedSizeWindow();//contentviewtheme設(shè)置吆寨;
....
}
}
關(guān)鍵點:
1.createSubDecor()創(chuàng)建docview赏淌, 判斷類型創(chuàng)建相應(yīng)的docview set到window中;
之后到xml解析和view生成的邏輯中:
inflate 調(diào)用到 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拿到目標(biāo)資源文件
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
//從resource拿到目標(biāo)資源文件 具體解析
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
// 首先看是否在緩存中六水,F(xiàn)irst 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();
}
}
//不在緩存中的,創(chuàng)建一個新塊放到下一個插槽
// 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塊內(nèi)進(jìn)行緩存
mLastCachedXmlBlockIndex = pos;
final XmlBlock oldBlock = cachedXmlBlocks[pos];
if (oldBlock != null) {
oldBlock.close();//釋放掉之前占位的塊辣卒;
}
cachedXmlBlockCookies[pos] = assetCookie;//緩存type
cachedXmlBlockFiles[pos] = file;//緩存filename
cachedXmlBlocks[pos] = block; //這個地方注意一下掷贾,極限優(yōu)化中可以用到;
return block.newParser();//返回塊中的解析結(jié)果
}
}
} catch (Exception e) {
...拋出錯誤
}
}
...拋出錯誤
}
//xml parser之后成為擁有start-end-attrs-name的基本數(shù)據(jù)結(jié)構(gòu)荣茫,再通過inflate轉(zhuǎn)化成view實體
//從parser中的基本數(shù)據(jù)想帅,構(gòu)建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節(jié)點
int type;
.....查找root節(jié)點
final String name = parser.getName();
if (TAG_MERGE.equals(name)) {
//merge標(biāo)簽驗證是否有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,布局參數(shù)
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.上下文中不要保留靜態(tài)引用
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}
以上的流程中啡莉,window是在activity中new出來港准,docview是根據(jù)設(shè)置的theme來進(jìn)行inflate旨剥,setcontentview res方式也是通過inflate方法;通過inflate就不可避免的走到讀取文件--->格式化解析---->反射出view實體類 這幾步浅缸;
如果做極限優(yōu)化轨帜,那么我們根據(jù)inflate相應(yīng)的代碼可以得出一個優(yōu)化點,可以在子線程嘗試對xml進(jìn)行加載衩椒,只要inflate執(zhí)行到存儲到block緩存中阵谚,那么就可以再下次使用相同文件的時候加快加載速度;同時在抖音的優(yōu)化策略中烟具,有對class的提前l(fā)oad來減小主頁上加載速度的優(yōu)化梢什;
還有公司開發(fā)組另辟蹊徑,把xml 轉(zhuǎn)view的其中幾個步驟進(jìn)行優(yōu)化朝聋,例如讀取xml文件的io耗時嗡午,那么我們直接再編譯期間將xml文件讀取轉(zhuǎn)化成class文件,setcontentview變成代碼動態(tài)生成布局越過xml的讀取解析兩部分冀痕,直接到inflate的convert步驟中荔睹;
asyncinflatelayout是什么框架?其中做了哪些事能異步加載布局言蛇?
顧名思義是一個異步加載布局的框架僻他,官方出品。從源碼來看不是很重腊尚,只是一個thread inflate布局的框架吨拗,主要解決inflate中io文件操作,和反射操作阻塞主線程的問題婿斥,讓infalte在子線程進(jìn)行劝篷,成功之后回調(diào)給主線程進(jìn)行后續(xù)處理;
public AsyncLayoutInflater(@NonNull Context context) {
mInflater = new BasicInflater(context);
mHandler = new Handler(mHandlerCallback);
mInflateThread = InflateThread.getInstance();
}
//構(gòu)造函數(shù)中的實體類就是整個asyncinflate核心民宿,handler進(jìn)行回調(diào)娇妓,thread負(fù)責(zé)子線程infalte,infalte負(fù)責(zé)具體加載活鹰;
可能是個輕量且優(yōu)化不是很大哈恰,不能作為kpi來進(jìn)行,所以在asyncinflate的封裝上沒有考慮得特別完善志群。從子線程未用線程池着绷,異步加載未考慮調(diào)用infalte和加載view不同類的情況,都會不太細(xì)膩有限制赖舟;