如需轉載請評論或簡信,并注明出處慧脱,未經允許不得轉載
系列文章
- Android布局優(yōu)化(一)LayoutInflate — 從布局加載原理說起
- Android布局優(yōu)化(二)優(yōu)雅獲取界面布局耗時
- Android布局優(yōu)化(三)使用AsyncLayoutInflater異步加載布局
- Android布局優(yōu)化(四)X2C — 提升布局加載速度200%
- Android布局優(yōu)化(五)繪制優(yōu)化—避免過度繪制
目錄
前言
最近打算寫一些Android布局優(yōu)化相關的文章菱鸥,既然要進行布局優(yōu)化,就要先了解布局加載原理氮采,才能知道有哪些地方可以作為優(yōu)化的切入點。開發(fā)同學做任何事情最好都能夠知其所以然
布局加載源碼分析
這里主要為了分析布局加載相關的原理主到,所以省略了一些邏輯贸呢。想了解View繪制流程,可以看最全的View繪制流程(上)— Window怔鳖、DecorView固蛾、ViewRootImp的關系
我們先從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();
}
...
//加載資源文件艾凯,創(chuàng)建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));
}
我們不用非常深入這個方法的具體實現(xiàn)細節(jié)蜡感,我們只需要知道郑兴,這個方法的作用就是將我們寫的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("<merge /> 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
,這個標簽一般大家不太常用
<view
class="RelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"></view>
在使用時泉粉,相當于所有控件標簽的父類一樣,可以設置class
屬性跺撼,這個屬性會決定view
這個節(jié)點會變成什么控件
- 如果該節(jié)點與主題相關讨彼,則需要特殊處理
如果該節(jié)點與主題(Theme)相關,需要將context
與theme信息包裝至ContextWrapper
類
- 處理TAG_1995標簽
這就有意思了哩至,TAG_1995指的是blink
這個標簽,這個標簽感覺使用的很少菩貌,以至于大家根本不知道重荠。
這個標簽最后會被解析成BlinkLayout
,BlinkLayout
其實就是一個FrameLayout
尾膊,這個控件最后會將包裹內容一直閃爍(就和電腦版QQ消息提示一樣)
<blink
android:layout_width="wrap_content"
android:layout_height="wrap_content">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="這個標簽會一直閃爍"/>
</blink>
- 判斷其是否存在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
都是一個接口,需要自己實現(xiàn)帜消,而Factory2
和Factory
的區(qū)別是Factory2
繼承Factory
浓体,從而擴展出一個參數,就是增加了該節(jié)點的父View娄猫。設置Factory
和Factory2
需要通過setFactory()
或者setFactory2()
來實現(xiàn)
setFactory()
public void setFactory(Factory factory) {
//如果已經設置Factory,不可以繼續(xù)設置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的過濾狀態(tài)
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
從上面的代碼可以看出缤至,我們是通過反射的方式去創(chuàng)建View實例的
總結
經過對布局加載原理的分析,我們可以看出布局加載的主要性能瓶頸主要在兩個方面
加載xml文件是一個IO過程领斥,如果xml文件過大,就會比較耗時
View實例是通過反射進行創(chuàng)建的,通過反射創(chuàng)建對象相對會更耗費性能