--視圖布局的加載
--setContentView()
--LayoutInflater.inflate()是如何解析xml的搏讶?
--createViewFromTag() 創(chuàng)建View
------自定義View的創(chuàng)建
------系統(tǒng)View的創(chuàng)建
要分析View的創(chuàng)建過(guò)程雪隧,應(yīng)該從視圖布局的加載開始分析邪驮。
視圖布局的加載
在開發(fā)中我們一般通過(guò)setContentView()加載Activity的布局善榛,通過(guò)LayoutInflater.inflate()方法加載fragment、recyclerview里adapter加載item布局等等皮胡。
而setContentView()實(shí)際上使用的就是LayoutInflater.inflate()進(jìn)行的布局加載烫罩,所以我們從setContentView()開始分析最好不過(guò)。
setContentView()
獲取一個(gè)window實(shí)例院究,實(shí)際上是調(diào)用了PhoneWindow的setContentView()方法
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID); //重點(diǎn)
initWindowDecorActionBar();
}
public Window getWindow() {
return mWindow;
}
PhoneWindow的setContentView源碼:
其實(shí)下邊就可以看出實(shí)際上setContentView()是通過(guò)LayoutInflater.inflate()進(jìn)行的布局加載洽瞬。 也就是說(shuō),實(shí)際上加載xml布局的是LayoutInflater.inflate()方法业汰。
@Override
public void setContentView(int layoutResID) {
//···忽略
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
//···忽略
} else {
//重點(diǎn)
// 調(diào)用LayoutInflater的inflate方法解析布局文件伙窃,并生成View樹,
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets(); //mContentParent是View樹的根節(jié)點(diǎn)
//回調(diào)Activity的onContentChanged方法通知視圖發(fā)生改變
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
}
上面的mLayoutInflater是在PhoneWindow的構(gòu)造方法中被實(shí)例的样漆。
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
LayoutInflater.inflate()是如何解析xml的为障?
inflate()一共有三個(gè)重載方法,其中前兩個(gè)實(shí)際上都是調(diào)用的第三個(gè)方法放祟,在第三個(gè)方法中鳍怨,通過(guò)上下文獲取到Resource實(shí)例,再通過(guò)getLayout()方法傳入layout的布局id獲取到XmlResourceParser對(duì)象跪妥。 接著又調(diào)用了一個(gè)inflate()方法鞋喇。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root) {
return inflate(parser, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
//···省略
final XmlResourceParser parser = res.getLayout(resource); //重點(diǎn)
try {
return inflate(parser, root, attachToRoot); //重點(diǎn)
} finally {
parser.close();
}
}
LayoutInflater的實(shí)例實(shí)際上是通過(guò)getSystemService()創(chuàng)建的
深入到下一個(gè)inflate()方法中,首先遍歷整個(gè)XML尋找merge標(biāo)簽眉撵,如果查到進(jìn)行merge標(biāo)簽里的創(chuàng)建侦香,如果沒有則調(diào)用createViewFromTag()進(jìn)行view的創(chuàng)建落塑。 這段代碼比較長(zhǎng),詳細(xì)的信息寫在注釋里罐韩。
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
//···省略
try {
// 循環(huán)查找根節(jié)點(diǎn)
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
final String name = parser.getName();
//如果查找到是merge標(biāo)簽
//private static final String TAG_MERGE = "merge";
if (TAG_MERGE.equals(name)) {
//如果是merge標(biāo)簽憾赁,根節(jié)點(diǎn)不能為空attachToRoot不能為false,否則拋出異常
//因?yàn)閙erge標(biāo)簽需要依附在父布局里才能使用
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//然后調(diào)用rInflate加載布局
rInflate(parser, root, inflaterContext, attrs, false);
//如果不是merge標(biāo)簽
} else {
//重點(diǎn)
//View是通過(guò)createViewFromTag()方法創(chuàng)建出來(lái)的
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
}
//···省略
} catch (XmlPullParserException e) {
//···省略
} finally {
//···省略
}
return result;
}
}
createViewFromTag() 創(chuàng)建View
我們知道了View是createViewFromTag() 創(chuàng)建的散吵,那么看里邊的實(shí)現(xiàn)龙考,具體寫在注釋里。
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//首先會(huì)使用mFactory2矾睦,mFactory晦款,mPrivateFactory這三個(gè)對(duì)象按先后順序創(chuàng)建view。
//如果這三個(gè)對(duì)象都為空的話顷锰,則會(huì)默認(rèn)流程來(lái)創(chuàng)建View柬赐,最后返回View。
//通常來(lái)講這三個(gè)Factory都為空官紫,如果我們想要控制View的創(chuàng)建過(guò)程就可以利用這一機(jī)制來(lái)定制自己的factory。
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);
}
//判斷名字中是否有"." ,這主要是為了區(qū)分系統(tǒng)自帶View和自定義View州藕。
//因?yàn)橄到y(tǒng)View是直接使用類名不用寫全包名的束世,而自定義View在使用的時(shí)候一定要寫全包名
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//如果是自定義View則調(diào)用createView來(lái)創(chuàng)建View,否則調(diào)用onCreateView方法床玻。
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
}
從上面可以看出毁涉,mFactory2,mFactory其實(shí)都是可以hook的點(diǎn)锈死,通過(guò)這里進(jìn)行攔截贫堰,添加我們想要進(jìn)行的操作。 而view的實(shí)際創(chuàng)建待牵,系統(tǒng)進(jìn)行判斷其屏,是自定義view還是系統(tǒng)自帶view,通過(guò)是否有包名去判斷缨该。 我們?cè)偕钊肟?createView()方法偎行。
自定義View的創(chuàng)建
從下面代碼可以看出每個(gè)View都是通過(guò)反射進(jìn)行創(chuàng)建的。
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 {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
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);
sConstructorMap.put(name, constructor);
} else {
···
}
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//view的創(chuàng)建
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
} catch (NoSuchMethodException e) {
···
} finally {
···
}
}
系統(tǒng)View的創(chuàng)建
在LayoutInflater中我們找到了onCreateView()方法
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}
protected View onCreateView(String name, AttributeSet attrs)
throws ClassNotFoundException {
return createView(name, "android.view.", attrs);
}
onCreateView方法創(chuàng)建系統(tǒng)View最終的實(shí)現(xiàn)也是交給了createView方法贰拿,只是傳入了一個(gè)字符串android.view.蛤袒,這樣在創(chuàng)建構(gòu)造器時(shí)就會(huì)與View的名字拼接到一起獲取對(duì)應(yīng)的Class對(duì)象,使最終能夠成功創(chuàng)建對(duì)應(yīng)的View膨更。
在PhoneLayoutInflater里找到了createView()方法妙真。
主要完成的是遍歷一個(gè)存放了三個(gè)包名字符串的數(shù)組,然后調(diào)用createView方法創(chuàng)建View荚守,只要這三次創(chuàng)建View有一次成功珍德,那么就返回創(chuàng)建的View练般,否則最終返回的還是父類傳入"android.view."時(shí)創(chuàng)建的View。
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
//重點(diǎn)
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
}
}
return super.onCreateView(name, attrs);
}
createView的具體實(shí)現(xiàn):
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//從緩存器中獲取構(gòu)造器
Constructor<? extends View> constructor = sConstructorMap.get(name);
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
//沒有緩存的構(gòu)造器
if (constructor == null) {
//通過(guò)傳入的prefix構(gòu)造出完整的類名 并加載該類
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//···省略
//從class對(duì)象中獲取構(gòu)造器
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//存入緩存器中
sConstructorMap.put(name, constructor);
} else {
//···省略
}
//···省略
//這里可以看到菱阵,系統(tǒng)應(yīng)用也是通過(guò)反射創(chuàng)建View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
}
}
結(jié)合前面的分析踢俄,我們可以清楚的看到view的創(chuàng)建過(guò)程。
經(jīng)歷了從 加載布局 — 到遍歷xml各個(gè)節(jié)點(diǎn) — 判斷是否系統(tǒng)view — view的創(chuàng)建 的過(guò)程晴及,這對(duì)于我們以后的開發(fā)大有幫助都办。