前言
setContentView
應(yīng)該是我們剛開始使用Android 就使用的Api了 來看一下setContentView
具體實(shí)現(xiàn)
先看一下setContentView時(shí)序圖
解釋一下幾個(gè)類的作用
-
AppCompatDelegateImpl
AppCompatActivity
的代理實(shí)現(xiàn)類,AppCompatActivity
的具體實(shí)現(xiàn)會(huì)交由它實(shí)現(xiàn) -
LayoutInflater
我用google翻譯了一下
布局充氣機(jī)
?? 感覺有點(diǎn)gaygay的 這個(gè)類的作用就是解析xml 遍歷創(chuàng)建view -
Factory2
這個(gè)接口只有一個(gè)方法
onCreateView
顧名思義 就是創(chuàng)建viewAppCompatDelegateImpl
就繼承了這個(gè)接口 我們可以實(shí)現(xiàn)這個(gè)接口來創(chuàng)建我們需要的view 比如AppCompatDelegateImpl
就會(huì)將所有的TextView
轉(zhuǎn)換為AppCompatTextView
一會(huì)可以看一下代碼
接下來上一下源碼?? 全都以AppCompatActivity為例哦
onCreate
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
我們看到 AppCompatActivity
的操作都是交由代理類來實(shí)現(xiàn)
重點(diǎn)看一下installViewFactory()
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
LayoutInflaterCompat.setFactory2(layoutInflater, this);//1
} else {
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");//2
}
}
}
我們看注釋1的地方 發(fā)現(xiàn)在OnCreate
方法中 會(huì)默認(rèn)設(shè)置一個(gè)Factory2
對(duì)象 所以我們需要在Activity.OnCreate
之前設(shè)置Factory2
對(duì)象 否則就會(huì)出現(xiàn)注釋2的報(bào)錯(cuò)
setContentView
今天的重頭戲 我們看一下上面的時(shí)序圖 大致的流程其實(shí)就是解析xml 然后反射生成view 具體根據(jù)時(shí)序圖 我們來看一下源碼分析
我們看到 setContentView
完全都是交由delegate實(shí)現(xiàn)
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
//delegate
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//通過LayoutInflater和resId 創(chuàng)建View
LayoutInflater.from(mContext).inflate(resId, contentParent);
mAppCompatWindowCallback.getWrapped().onContentChanged();
}
之前的時(shí)序圖有說明 delegate會(huì)通過LayoutInflater
創(chuàng)建View
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) + ")");
}
View view = tryInflatePrecompiled(resource, res, root, attachToRoot);
if (view != null) {
return view;
}
//生成xml解析器
XmlResourceParser parser = res.getLayout(resource);
try {
//1. 通過反射生成view
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
上面這段代碼會(huì)將xml文件進(jìn)行解析 然后通過inflate方法創(chuàng)建view 并返回 我們看一下下面的部分精簡(jiǎn)代碼
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
.......
try {
//將parser前進(jìn)到第一個(gè)START_TAG
advanceToRootNode(parser);
final String name = parser.getName();
//如果是merger標(biāo)簽
if (TAG_MERGE.equals(name)) {
......
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// Temp is the root view that was found in the xml
//1.根據(jù)tag生成view Tag就是我們寫在xml的帶包名的標(biāo)簽 比如TextView
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//設(shè)置LayoutParams
params = root.generateLayoutParams(attrs);
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.
// 遞歸實(shí)例化子View 這里也會(huì)根據(jù)include等標(biāo)簽 調(diào)用不同方法 大家可以自己看一下
rInflateChildren(parser, temp, attrs, true);
//setContentView的話 會(huì)將View 添加到android.R.id.Content中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
}
......
return result;
}
}
上面的代碼我稍微精簡(jiǎn)了一下 流程主要分為3步
- 前進(jìn)到第一個(gè)
START_TAG
解析xml 生成View,但是ViewGroup都有子View - 遞歸生成所有子View
- 因?yàn)槭?code>setContentView 所以
attachToRoot
時(shí)鐘為tree 將View 添加到android.R.id.content中
我們關(guān)注的重點(diǎn)主要還是createViewFromTag
看下面的代碼 發(fā)現(xiàn)createViewFromTag
是交由Factory2
實(shí)現(xiàn)
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
......
try {
//這里會(huì)交由Factory2實(shí)現(xiàn) 如果Factory沒有處理這個(gè)Tag 那么會(huì)交由系統(tǒng)實(shí)現(xiàn) 就是下面的onCreateView和createView
View view = tryCreateView(parent, name, context, attrs);
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//比如TextView等不需要包名
if (-1 == name.indexOf('.')) {
view = onCreateView(context, parent, name, attrs);
} else {
view = createView(context, name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
.......
}
我們重點(diǎn)還是關(guān)注tryCreateView
,onCreateView
等方法大家可以自己看一下 就是反射生成view
tryCreateView
會(huì)通過Factory2接口實(shí)現(xiàn) 還記得我們之前說 AppDelegateImpl
繼承了Factory2
這就是AppCompatActivity
對(duì)一些Tag進(jìn)行了攔截創(chuàng)建 我們也可以自己實(shí)現(xiàn)Factory2
來進(jìn)行攔截 實(shí)現(xiàn)一些像換膚的功能 大家可以看一下我之前寫的文章手?jǐn)]動(dòng)態(tài)換膚框架(一)
感覺有收獲的同學(xué)點(diǎn)點(diǎn)贊吶??
扯遠(yuǎn)了 我們看一下tryCreateView
方法
public final View tryCreateView(@Nullable View parent, @NonNull String name,
@NonNull Context context,
@NonNull AttributeSet attrs) {
if (name.equals(TAG_1995)) {
// 這里好像致敬了JAVA誕生
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
View view;
//這里就是我們可以做的Hook點(diǎn) 我們以AppCompatActivity為例 看一下AppCompatActivity的實(shí)現(xiàn)
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);
}
return view;
}
上面方法我們發(fā)現(xiàn) 我們?nèi)绻際ook系統(tǒng)的setContentView
方法的話 可以通過Factory2
來實(shí)現(xiàn) 我們以AppCompatActivity
為例 看一下AppCompatActivity Factory
的實(shí)現(xiàn)
我們上面說過 AppCompatActivity
的實(shí)現(xiàn)都交由AppCompatDelegate
實(shí)現(xiàn) 具體實(shí)現(xiàn)類為AppCompatDelegateImpl
而AppCompatDelegateImpl
繼承了Factory2
接口 所以我們看一下AppCompatDelegateImpl
的onCreateView
偽代碼
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
......
mAppCompatViewInflater = new AppCompatViewInflater();
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP,true, VectorEnabledTintResources.shouldBeUsed());
}
感覺有點(diǎn)繞 但其實(shí)邏輯又非常清楚?? 符合單一職責(zé) 創(chuàng)建View都是通過LayoutInflate來實(shí)現(xiàn)
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
......
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
case "ImageView":
view = createImageView(context, attrs);
verifyNotNull(view, name);
break;
case "Button":
view = createButton(context, attrs);
verifyNotNull(view, name);
break;
case "EditText":
view = createEditText(context, attrs);
verifyNotNull(view, name);
break;
......
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we need to manually
// inflate it using the name so that android:theme takes effect.
//注釋1
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
//檢查onClick 如果存在 就調(diào)用view.setonClickListener
checkOnClickListener(view, attrs);
}
return view;
}
看到AppCompatViewInflater
對(duì)TextView等做了兼容處理 重點(diǎn)看一下注釋1的地方 里面通過反射獲取View 但是眾所周知 反射是一個(gè)比較耗時(shí)的操作 所以我在布局優(yōu)化的文章中寫過 可以通過一些X2C等框架 來解決反射問題 但是可能會(huì)有一些兼容問題 需要處理一下
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
......
if (-1 == name.indexOf('.')) {
for (int i = 0; i < sClassPrefixList.length; i++) {
final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
if (view != null) {
return view;
}
}
return null;
} else {
return createViewByPrefix(context, name, null);
}
}
......
}
private View createViewByPrefix(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
//先從緩存中取 避免每次都反射獲取
Constructor<? extends View> constructor = sConstructorMap.get(name);
try {
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//反射生成
Class<? extends View> clazz = Class.forName(
prefix != null ? (prefix + name) : name,
false,
context.getClassLoader()).asSubclass(View.class);
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
return constructor.newInstance(mConstructorArgs);
} catch (Exception e) {
// We do not want to catch these, lets return null and let the actual LayoutInflater
// try
return null;
}
}
至此View已經(jīng)通過反射生成了 再看一次時(shí)序圖 來回顧一下整體的流程
總結(jié)
在學(xué)習(xí)setContentView的過程中 可以參考上面的那個(gè)時(shí)序圖來分析 我們需要了解其中的幾個(gè)類的職責(zé)是什么 分析清楚之后其實(shí)邏輯也就相當(dāng)清楚了