1. 概述
內(nèi)涵段子架構(gòu)第一階段已經(jīng)更新完了嗜浮,后面我們主要是以google源碼為主李滴,今天我?guī)Т蠹襾砜匆幌聅etContentView的源碼,請先看一下如果繼承自Activity去打印一個TextView與繼承自AppCompatActivity去打印一個TextView分別是這樣的:
繼承自Activity:
android.widget.TextView{ac5cd17 V.ED..... ......ID 0,0-0,0 #7f0b002c app:id/text_view}
繼承自AppCompatActivity:
android.support.v7.widget.AppCompatTextView{392562b V.ED..... ......ID 0,0-0,0 #7f0b0055 app:id/text_view}
誰能告訴我這到底是怎么啦再悼?我布局里面明明是TextView為什么繼承自AppCompatActivity就變成了AppCompatTextView缎除,那么接下來我們就來看一下源碼到底是怎么把我的TextView給拐走的。
所有分享大綱:2017Android進(jìn)階之路與你同行
視頻講解地址:https://pan.baidu.com/s/1qYl2AOO
2. Activity的setContentView源碼閱讀
2.1 很多人都問過我怎么看源碼荒辕,我只想說怎么看汗销?當(dāng)然是坐著點進(jìn)去看懊⒋狻!
public void setContentView(@LayoutRes int layoutResID) {
// 獲取Window 調(diào)用window的setContentView方法大溜,發(fā)現(xiàn)是抽象類化漆,所以需要找具體的實現(xiàn)類PhoneWindow
getWindow().setContentView(layoutResID);
}
// PhoneWindow 中的 setContentView方法
@Override
public void setContentView(int layoutResID) {
// 如果mContentParent 等于空,調(diào)用installDecor();
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
// 把我們自己的布局layoutId加入到mContentParent钦奋,我們set進(jìn)來的布局原來是放在這里面的Soga
mLayoutInflater.inflate(layoutResID, mContentParent)座云;
}
2.2 installDecor(),這個之前已經(jīng)帶大家看過一遍了付材,不過沒辦法再進(jìn)來看看吧:
// This is the top-level view of the window, containing the window decor.
// 看到這解釋木有朦拖?
private DecorView mDecor;
private void installDecor() {
if (mDecor == null) {
// 先去創(chuàng)建一個 DecorView
mDecor = generateDecor(-1);
}
// ......
// 省略調(diào)一些代碼,看著暈厌衔,不過這也太省了璧帝。
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
}
}
// generateDecor 方法
protected DecorView generateDecor(int featureId) {
// 就是new一個DecorView ,DecorView extends FrameLayout 不同版本的源碼有稍微的區(qū)別富寿,
// 低版本DecorView 是PhoneWindow的內(nèi)部類睬隶,高版本是一個單獨的類,不過這不影響页徐。
return new DecorView(context, featureId, this, getAttributes());
}
protected ViewGroup generateLayout(DecorView decor) {
// Inflate the window decor.
// 我看你到底怎么啦
int layoutResource;
// 都是一些判斷苏潜,發(fā)現(xiàn) layoutResource = 系統(tǒng)的一個資源文件,
if(){}else if(){}else if(){
// Embedded, so no decoration is needed.
layoutResource = R.layout.screen_simple;
// System.out.println("Simple!");
}
mDecor.startChanging();
// 把布局解析加載到 DecorView 而加載的布局是一個系統(tǒng)提供的布局,不同版本不一樣
// 某些源碼是 addView() 其實是一樣的
mDecor.onResourcesLoaded(mLayoutInflater, layoutResource);
// ID_ANDROID_CONTENT 是 android.R.id.content变勇,這個View是從DecorView里面去找的恤左,
// 也就是 從系統(tǒng)的layoutResource里面找一個id是android.R.id.content的一個FrameLayout
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
// 返回
return contentParent;
}
其實看源碼一定要帶著最初的出發(fā)點來看,要不然里面太多了根本找不到方向搀绣,如果帶著思想來看那么就算跑偏了也可以從新再回來飞袋,我目前就是想弄清楚我們的 setContentView() 系統(tǒng)到底把我們的布局加到哪里去了。我先用文字總結(jié)一下链患,然后去畫一張圖:
- Activity里面設(shè)置setContentView()巧鸭,我們的布局顯示主要是通過PhoneWindow,PhoneWindow獲取實例化一個DecorView锣险。
- 實例化DecorView蹄皱,然后做一系列的判斷然后去解析系統(tǒng)的資源layoutId文件览闰,至于解析哪一個資源文件會做判斷比如有沒有頭部等等芯肤,把它解析加載到DecorView,資源layout里面有一個View的id是android.R.id.content压鉴。
- 我們自己通過setContentView設(shè)置的布局id其實是解析到mParentContent里面的崖咨,也就是那個id叫做android.R.id.content的FarmeLayout,好了就這么多了油吭。
3. AppCompatActivity的setContentView
@Override
public void setContentView(@LayoutRes int layoutResID) {
// 跟我在網(wǎng)上看的完全不一樣
getDelegate().setContentView(layoutResID);
}
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}
// window 還是那個window 击蹲,留意一下就行 , 不同的版本返回 AppCompatDelegateImpl署拟,但是都是相互繼承
// 最終繼承都是繼承 AppCompatDelegateImplV9 有的版本V7有的V9 好麻煩 嗨!
private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
final int sdk = Build.VERSION.SDK_INT;
if (BuildCompat.isAtLeastN()) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (sdk >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (sdk >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (sdk >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}
// 下面其實就沒啥好看的了歌豺,一個一個點進(jìn)去推穷,仔細(xì)看看就好了。與Activity沒啥區(qū)別了
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
private void ensureSubDecor() {
mSubDecor = createSubDecor();
}
4. AppCompatViewInflater源碼分析
看到這里還是不知道為什么我的TextView變成了AppCompatTextView类咧,找啊找啊就找了這么個方法:
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
// 把LayoutInflater 的 Factory設(shè)置為了this馒铃,也就說待會創(chuàng)建View就會走自己的onCreateView方法
// 如果看不懂還需要看一下 LayoutInflater 的源碼,我們的LayoutInflater.from(mContext)其實是一個單例
// 如果設(shè)置了Factory那么每次創(chuàng)建View都會先執(zhí)行Factory的onCreateView方法
LayoutInflaterCompat.setFactory(layoutInflater, this);
} else {
if (!(LayoutInflaterCompat.getFactory(layoutInflater)
instanceof AppCompatDelegateImplV7)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
// 看一下是不是 5.0 痕惋,5.0 都自帶什么效果我就不說了
final boolean isPre21 = Build.VERSION.SDK_INT < 21;
if (mAppCompatViewInflater == null) {
mAppCompatViewInflater = new AppCompatViewInflater();
}
// We only want the View to inherit its context if we're running pre-v21
final boolean inheritContext = isPre21 && shouldInheritContext((ViewParent) parent);
// 通過 AppCompatViewInflater 去創(chuàng)建View
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
isPre21, /* Only read android:theme pre-L (L+ handles this anyway) */
true, /* Read read app:theme as a fallback at all times for legacy reasons */
VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
);
}
其實在講數(shù)據(jù)庫優(yōu)化我們已經(jīng)看過一次AppCompatViewInflater的源碼了区宇,創(chuàng)建View都是用的反射,只不過做了緩存和優(yōu)化而已值戳,我們寫代碼其實可以仿照源碼來议谷,給我們很好的思路。
public final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
View view = null;
// 果真找到你了堕虹,哈哈 卧晓,做了替換
// We need to 'inject' our tint aware Views in place of the standard framework versions
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
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.
view = createViewFromTag(context, name, attrs);
}
return view;
}
private View createView(Context context, String name, String prefix)
throws ClassNotFoundException, InflateException {
// 先從構(gòu)造緩存里面獲取
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 = context.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 利用反射創(chuàng)建一個構(gòu)造函數(shù)
constructor = clazz.getConstructor(sConstructorSignature);
sConstructorMap.put(name, constructor);
}
constructor.setAccessible(true);
// 利用反射創(chuàng)建View的實例
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;
}
}
5. LayoutInflater源碼分析
LayoutInflater的源碼我們分三個步驟去看相對來說會更加的系統(tǒng):
4. 1 如何獲取LayoutInflater?
4. 2 如何使用LayoutInflater赴捞?
4. 3 布局的View是如何被實例化的禀崖?
先來看看我們平時都是怎么去獲取LayoutInflater的,這個我們其實并不陌生LayoutInflater.from(context):
/**
* Obtains the LayoutInflater from the given context.
*/
// 是一個靜態(tài)的方法
public static LayoutInflater from(Context context) {
// 通過context獲取系統(tǒng)的服務(wù)
LayoutInflater LayoutInflater =
// context.getSystemService()是一個抽象類螟炫,所以我們必須找到實現(xiàn)類ContextImpl
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
// ContextImpl 里面的實現(xiàn)方法
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
/**
* Gets a system service from a given context.
*/
// SystemServiceRegistry 里面的getSystemService方法
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
// 這是一個靜態(tài)的HashMap集合
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
// 靜態(tài)的代碼塊中
static{
// 注冊LayoutInflater服務(wù)
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
}
// 注冊很多的其他服務(wù)......
}
接下來大致的整理一下獲取LayoutInflater的思路波附,通過Context的實現(xiàn)類ContextImpl獲取的,最終是通過SystemServiceRegistry.getSystemService()方法昼钻,而SYSTEM_SERVICE_FETCHERS是一個靜態(tài)的HashMap掸屡,初始化是在靜態(tài)代碼塊中通過registerService注冊了很多服務(wù)。所以到目前為止我們有兩個思想對于我們后面插件化的皮膚框架有很大的關(guān)系然评,第一LayoutInflater其實是一個系統(tǒng)的服務(wù)仅财,第二每次通過LayoutInflater.form(context)是一個靜態(tài)的單例類無論在哪里獲取都是同一個對象。接下來我們來看一下加載布局的三種方式:
1.View.inflate(context,layoutId,parent);
2.LayoutInflater.from(context).inflate(layoutId,parent);
3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);
1.View.inflate(context,layoutId,parent);
// 其實就是調(diào)用的 LayoutInflater.from(context).inflate(layoutId,parent);
public static View inflate(Context context, @LayoutRes int resource, ViewGroup root) {
LayoutInflater factory = LayoutInflater.from(context);
return factory.inflate(resource, root);
}
2.LayoutInflater.from(context).inflate(layoutId,parent);
// 其實就是調(diào)用的 LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot);
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
3.LayoutInflater.from(context).inflate(layoutId,parent,attachToRoot); 其實最終都是調(diào)用的該方法盏求,我們關(guān)鍵是要弄清楚這個參數(shù)的概念亿眠,尤其是attachToRoot:
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) + ")");
}
// 獲取一個 XmlResourceParser 解析器,這個應(yīng)該并不陌生纳像,就是待會需要去解析我們的layoutId.xml文件
// 這個到后面的插件化架構(gòu)再去詳細(xì)講解
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
......
//保存?zhèn)鬟M(jìn)來的這個view
View result = root;
try {
// Look for the root node.
int type;
//在這里找到root標(biāo)簽
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//獲取這個root標(biāo)簽的名字
final String name = parser.getName();
......
//判斷是否merge標(biāo)簽
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");
}
//這里直接加載頁面,忽略merge標(biāo)簽,直接傳root進(jìn)rInflate進(jìn)行加載子view
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//通過標(biāo)簽來獲取view
//先獲取加載資源文件中的根view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
//布局參數(shù)
ViewGroup.LayoutParams params = null;
//關(guān)鍵代碼A
if (root != null) {
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
//temp設(shè)置布局參數(shù)
temp.setLayoutParams(params);
}
}
......
//關(guān)鍵代碼B
//在這里憔购,先獲取到了temp,再把temp當(dāng)做root傳進(jìn)去rInflateChildren
//進(jìn)行加載temp后面的子view
rInflateChildren(parser, temp, attrs, true);
......
//關(guān)鍵代碼C
if (root != null && attachToRoot) {
//把view添加到root中并設(shè)置布局參數(shù)
root.addView(temp, params);
}
//關(guān)鍵代碼D
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
......
} catch (Exception e) {
......
} finally {
......
}
return result;
}
}
// 創(chuàng)建View
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
// ......
try {
// 創(chuàng)建我們的View
View view;
if (mFactory2 != null) {
// 先通過mFactory2 創(chuàng)建宫峦,其實在 AppCompatActivity里面會走這個方法,也就會去替換某些控件
// 所以我們就 看到了上面的內(nèi)容
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
// 走mFactory
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// ......省略
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 判斷是不是自定義View玫鸟,自定義View在布局文件中com.hc.BannerView是個全類名导绷,
// 而系統(tǒng)的View在布局文件中不是全類名 TextView
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
// ........
}
}
// 創(chuàng)建View
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
// 做一些反射的性能優(yōu)化
try {
// 先從緩存中拿,這是沒拿到的情況
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
// 加載 clazz
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
// 創(chuàng)建View的構(gòu)造函數(shù)
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
// 加入緩存集合集合
sConstructorMap.put(name, constructor);
} else {
}
// 通過反射創(chuàng)建View
final View view = constructor.newInstance(args);
return view;
} catch (NoSuchMethodException e) {
// ......省略部分代碼
}
}
這里有兩個思想比較重要第一個View的創(chuàng)建是通過當(dāng)前View的全類名反射實例化的View,第二個View的創(chuàng)建首先會走mFactory2屎飘,然后會走mFactory诵次,只要不為空先會去執(zhí)行Factory的onCreateView方法,最后才會走系統(tǒng)的LayoutInflater里面的createView()方法枚碗,所以我們完全可以自己去實例化View逾一,這對于我們的插件化換膚很有幫助。
基于插件式換膚框架搭建 - 資源加載源碼分析和插件式換膚框架搭建 - setContentView源碼閱讀這兩篇文章我們完全可以自己動手搭建一套換膚框架了肮雨,我們下期再見遵堵。
所有分享大綱:2017Android進(jìn)階之路與你同行