前言
setContentView應該是我們剛開始使用Android 就使用的Api了 來看一下setContentView具體實現(xiàn)
先看一下setContentView時序圖

解釋一下幾個類的作用
-
AppCompatDelegateImpl
AppCompatActivity的代理實現(xiàn)類,AppCompatActivity的具體實現(xiàn)會交由它實現(xiàn) -
LayoutInflater
我用google翻譯了一下
布局充氣機?? 感覺有點gaygay的 這個類的作用就是解析xml 遍歷創(chuàng)建view -
Factory2
這個接口只有一個方法
onCreateView顧名思義 就是創(chuàng)建viewAppCompatDelegateImpl就繼承了這個接口 我們可以實現(xiàn)這個接口來創(chuàng)建我們需要的view 比如AppCompatDelegateImpl就會將所有的TextView轉換為AppCompatTextView一會可以看一下代碼
接下來上一下源碼?? 全都以AppCompatActivity為例哦
onCreate
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
final AppCompatDelegate delegate = getDelegate();
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
super.onCreate(savedInstanceState);
}
我們看到 AppCompatActivity的操作都是交由代理類來實現(xià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方法中 會默認設置一個Factory2對象 所以我們需要在Activity.OnCreate之前設置Factory2對象 否則就會出現(xiàn)注釋2的報錯
setContentView
今天的重頭戲 我們看一下上面的時序圖 大致的流程其實就是解析xml 然后反射生成view 具體根據(jù)時序圖 我們來看一下源碼分析
我們看到 setContentView 完全都是交由delegate實現(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();
}
之前的時序圖有說明 delegate會通過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();
}
}
上面這段代碼會將xml文件進行解析 然后通過inflate方法創(chuàng)建view 并返回 我們看一下下面的部分精簡代碼
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
.......
try {
//將parser前進到第一個START_TAG
advanceToRootNode(parser);
final String name = parser.getName();
//如果是merger標簽
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的帶包名的標簽 比如TextView
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
//設置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.
// 遞歸實例化子View 這里也會根據(jù)include等標簽 調(diào)用不同方法 大家可以自己看一下
rInflateChildren(parser, temp, attrs, true);
//setContentView的話 會將View 添加到android.R.id.Content中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
}
......
return result;
}
}
上面的代碼我稍微精簡了一下 流程主要分為3步
- 前進到第一個
START_TAG解析xml 生成View,但是ViewGroup都有子View - 遞歸生成所有子View
- 因為是
setContentView所以attachToRoot時鐘為tree 將View 添加到android.R.id.content中
我們關注的重點主要還是createViewFromTag 看下面的代碼 發(fā)現(xiàn)createViewFromTag是交由Factory2實現(xiàn)
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
......
try {
//這里會交由Factory2實現(xiàn) 如果Factory沒有處理這個Tag 那么會交由系統(tǒng)實現(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;
}
.......
}
我們重點還是關注tryCreateView,onCreateView等方法大家可以自己看一下 就是反射生成view
tryCreateView會通過Factory2接口實現(xiàn) 還記得我們之前說 AppDelegateImpl繼承了Factory2這就是AppCompatActivity對一些Tag進行了攔截創(chuàng)建 我們也可以自己實現(xiàn)Factory2來進行攔截 實現(xiàn)一些像換膚的功能 大家可以看一下我之前寫的文章手擼動態(tài)換膚框架(一)
感覺有收獲的同學點點贊吶??
扯遠了 我們看一下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點 我們以AppCompatActivity為例 看一下AppCompatActivity的實現(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來實現(xiàn) 我們以AppCompatActivity為例 看一下AppCompatActivity Factory的實現(xiàn)
我們上面說過 AppCompatActivity的實現(xiàn)都交由AppCompatDelegate實現(xiàn) 具體實現(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());
}
感覺有點繞 但其實邏輯又非常清楚?? 符合單一職責 創(chuàng)建View都是通過LayoutInflate來實現(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對TextView等做了兼容處理 重點看一下注釋1的地方 里面通過反射獲取View 但是眾所周知 反射是一個比較耗時的操作 所以我在布局優(yōu)化的文章中寫過 可以通過一些X2C等框架 來解決反射問題 但是可能會有一些兼容問題 需要處理一下
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)通過反射生成了 再看一次時序圖 來回顧一下整體的流程

總結
在學習setContentView的過程中 可以參考上面的那個時序圖來分析 我們需要了解其中的幾個類的職責是什么 分析清楚之后其實邏輯也就相當清楚了