若是讀者沒有閱讀過該系列博客,建議先閱讀下博文說明,這樣會對後續的閱讀博客思路上會有一個清晰的認識。java
Android 中LayoutInflater(佈局加載器)系列博文說明android
Android 中LayoutInflater(佈局加載器)系列博文說明緩存
Android 中LayoutInflater(佈局加載器)系列之介紹篇安全
Android 中LayoutInflater(佈局加載器)系列之源碼篇bash
Android 中LayoutInflater(佈局加載器)源碼篇之createViewFromTag方法app
Android 中LayoutInflater(佈局加載器)源碼篇之rInflate方法ide
Android 中LayoutInflater(佈局加載器)系列之實戰篇佈局
本篇博客,是屬於Android 中LayoutInflater(佈局加載器)源碼篇其中一個部分,專門介紹createViewFromTag方法的流程,具體有如下幾部分:ui
一些不常見的標籤的解析方法以及使用,例如:view、blinkthis
LayoutInflater中Factory,Factory2是什麼,用途是什麼?
CreateViewFromTag默認的View建立流程解析
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}複製代碼
createViewFromTag在LayoutInflater中存在重載,最終仍是會調用5個參數的createViewFromTag方法。
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();
}
//BlinkLayout是一種閃爍的FrameLayout,它包裹的內容會一直閃爍,相似QQ提示消息那種。
if (name.equals(TAG_1995)) {
return new BlinkLayout(context, attrs);
}
//設置Factory,來對View作額外的拓展,這塊屬於可定製的內容
try {
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分析過程:
(1)處理view標籤
若是標籤的名稱是view,注意是小寫的view,這個標籤通常你們不太經常使用,具體的使用狀況以下:
<view
class="RelativeLayout"
android:layout_width="match_parent"
android:layout_height="match_parent"></view>複製代碼
在使用時,至關於全部控件標籤的父類同樣,能夠設置class屬性,這個屬性會決定view這個節點會變成什麼控件。
(2)若是該節點與主題相關,則須要特殊處理
若是該節點與主題(Theme)相關,須要將context與theme信息包裝至ContextWrapper類。
(3)處理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>複製代碼
(4)判斷其是否存在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都是一個接口,須要本身實現,而Factory2和Factory的區別是Factory2繼承Factory,從而擴展出一個參數,就是增長了該節點的父View。
這裏我自定義了一個Factory,下面自定義解析View的過程:
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = null;
try {
if (-1 == name.lastIndexOf(".")) {
if (name.equals("View") || name.equals("ViewGroup")) {
view = mInflater.createView(name, "android.view.", attrs);
} else {
view = mInflater.createView(name, "android.widget.", attrs);
}
} else {
if (name.contains(".")) {
String checkName = name.substring(name.lastIndexOf("."));
String prefix = name.substring(0, name.lastIndexOf("."));
view = mInflater.createView(checkName, prefix, attrs);
}
}
} catch (ClassNotFoundException e) {
e.printStackTrace();
}
if(view != null){
//在這裏能夠對View作一些額外的操做,而且可以得到View的屬性集,能夠作一些自定義操做。
view.xxxxxx
}
return view;
}複製代碼
從上面能夠看出,Factory和Factory2其實LayoutInflater解析View時的一種擴展實現,在這裏能夠額外的對View處理,設置Factory和Factory2須要經過setFactory()或者setFactory2()來實現。
setFactory()
public void setFactory(Factory factory) {
//若是已經設置Factory,不能夠繼續設置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,纔可以設置。
(5)LayoutInflater內置的解析過程
若是Factory或者Factory2沒有設置,或者返回View爲null,纔會使用默認解析方式。
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}複製代碼
這段就是對自定義View和原生的控件進行判斷,這裏給你們說明下原生控件和自定義View的name區別:
原生 : RelativeLayout
自定義View : com.demo.guidepagedemo.customview.CustomImageView複製代碼
原生控件的解析方式 onCreateView :
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}複製代碼
而後調用的仍是2個參數的onCreateView()方法
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}複製代碼
能夠看到最終方法的指向仍是調用createView方法:
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的過濾狀態
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;複製代碼
上面代碼有點長,就直接在代碼裏面加註釋了,這裏額外說一下這個方法:
判斷ClassLoader是否安全的verifyClassLoader :
private final boolean verifyClassLoader(Constructor<? extends View> constructor) {
final ClassLoader constructorLoader = constructor.getDeclaringClass().getClassLoader();
if (constructorLoader == BOOT_CLASS_LOADER) {
//這裏注意BootClassLoader是至關於全部派生出來的ClassLoader的原始基類,全部的ClassLoader都是根據其衍生的。
return true;
}
//這裏是一個遍歷操做,一直在遍歷加載mContext的ClassLoader的繼承樹,一直在往上尋找,若是
//constructor的ClassLoader與繼承樹中某個ClassLoader相同就說明這個ClassLoader是安全的
ClassLoader cl = mContext.getClassLoader();
do {
if (constructorLoader == cl) {
return true;
}
cl = cl.getParent();
} while (cl != null);
return false;
}複製代碼
這裏簡單說明下,幾種ClassLoader的做用:
(1)BootClassLoader 加載Android FrameWork層的一些字節碼文件
(2)PathClassLoader 加載已經安裝到系統上的應用App(apk)上的字節碼文件
(3)DexClassLoader 加載指定目錄中的Class字節碼文件
(4)BaseDexClassLoader 是PathClassloader和DexClassLoader的父類複製代碼
通常的App剛啓動的時候,就會有兩個ClassLoader被加載,分別是PathClassLoader、DexClassLoader而這兩個ClassLoader都是繼承BaseDexClassLoader.
而BaseDexClassLoader繼承的是ClassLoader,可是在ClassLoader中getParent()方法賦予其Parent爲BootClassLoader,這個若是你們感興趣,能夠自行查閱ClassLoader。
由於圖有點大,建議你們查看先拷貝到本地,或者放大120%,就能夠清晰觀看了。