- 這是 Android 10 源碼分析系列的第 4 篇
- 分支:android-10.0.0_r14
- 全文閱讀大概 10 分鐘
經過這篇文章你將學習到如下內容,將在文末會給出相應的答案java
在上一篇文章 0xA02 Android 10 源碼分析:Apk加載流程之資源加載 中經過LayoutInflater.inflate方法解析xml文件,瞭解到了系統如何對merge、include標籤是如何處理的,本文主要圍繞如下兩方面內容android
系統對merge、include是如何處理的算法
merge標籤爲何能夠起到優化佈局的效果?編程
解析過程當中遇到merge標籤,會調用rInflate方法,部分代碼以下緩存
// 根據元素名解析,生成對應的view
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
// rInflateChildren方法內部調用的rInflate方法,深度優先遍歷解析全部的子view
rInflateChildren(parser, view, attrs, true);
// 添加解析的view
viewGroup.addView(view, params);
複製代碼
解析merge標籤下面的全部子view,而後添加到根佈局中
更多信息查看0xA02 Android 10 源碼分析:Apk加載流程之資源加載,接下來看一下系統對ViewStub如何處理bash
關於ViewStub的介紹,能夠點擊下方官網連接查看 官網連接https://developer.android.google.cn/reference/android...ViewStub網絡
ViewStub的繼承結構 app
簡單來講主要如下幾點:ide
爲何ViewStub是大小爲0的視圖 frameworks/base/core/java/android/view/ViewStub.java函數
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 設置視圖大小爲0
setMeasuredDimension(0, 0);
}
複製代碼
ViewStub的做用
主要用來延遲佈局的加載,例如:在Android中很是常見的佈局,用ListView來展現列表信息,當沒有數據或者網絡加載失敗時, 加載空的 ListView 會佔用一些資源,若是用ViewStub包裹ListView,當有數據時,纔會調用inflate()方法顯示ListView,起到延遲加載了佈局效果
在上篇文章0xA02 Android 10 源碼分析:Apk加載流程之資源加載中,介紹了view的建立是經過調用了LayoutInflater.createView方法根據完整的類的路徑名利用反射機制構建View對象,由於ViewStub是繼承View,因此ViewStub的建立和View的建立是相同的,來看一下LayoutInflater.createView方法 frameworks/base/core/java/android/view/LayoutInflater.java
...
try {
// 利用構造函數,建立View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// 若是是ViewStub,則設置LayoutInflater
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
...
複製代碼
根據完整的類的路徑名利用反射機制構建View對象,若是遇到ViewStub將當前LayoutInflater設置給ViewStub,當ViewStub控件設置可見,或者調用inflate(),會調用LayoutInflater的inflate方法完成佈局加載,接下來分析ViewStub的構造方法
在上面提到了根據完整的類的路徑名利用反射機制構建View對象,當view對象被建立的時候,會調用它的構造函數,來看一下ViewStub的構造方法
public ViewStub(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context);
final TypedArray a = context.obtainStyledAttributes(attrs,
R.styleable.ViewStub, defStyleAttr, defStyleRes);
saveAttributeDataForStyleable(context, R.styleable.ViewStub, attrs, a, defStyleAttr,
defStyleRes);
// 解析xml中設置的 android:inflatedId 的屬性
mInflatedId = a.getResourceId(R.styleable.ViewStub_inflatedId, NO_ID);
// 解析xml中設置的 android:layout 屬性
mLayoutResource = a.getResourceId(R.styleable.ViewStub_layout, 0);
// 解析xml中設置的 android:id 屬性
mID = a.getResourceId(R.styleable.ViewStub_id, NO_ID);
a.recycle();
// view不可見
setVisibility(GONE);
// 不會調用 onDraw 方法繪製內容
setWillNotDraw(true);
}
複製代碼
在上面提到了若是想要加載ViewStub所指定的layout資源,須要設置ViewStub控件設置可見,或者調用inflate()方法,來看一下ViewStub的setVisibility方法
setVisibility(int visibility)方法,參數visibility對應三個值分別是INVISIBLE、VISIBLE、GONE
接下里查看一下ViewStub的setVisibility方法 frameworks/base/core/java/android/view/ViewStub.java
@Override
public void setVisibility(int visibility) {
if (mInflatedViewRef != null) {
// mInflatedViewRef 是 WeakReference的實例,調用inflate方法時候初始化
View view = mInflatedViewRef.get();
if (view != null) {
// 設置View可見
view.setVisibility(visibility);
} else {
throw new IllegalStateException("setVisibility called on un-referenced view");
}
} else {
super.setVisibility(visibility);
// 當View爲空且設置視圖可見(VISIBLE、INVISIBLE),調用inflate方法
if (visibility == VISIBLE || visibility == INVISIBLE) {
inflate();
}
}
}
複製代碼
調用了ViewStub的setVisibility方法,最後都會調用ViewStub.inflate方法,來查看一下 frameworks/base/core/java/android/view/ViewStub.java
public View inflate() {
final ViewParent viewParent = getParent();
if (viewParent != null && viewParent instanceof ViewGroup) {
if (mLayoutResource != 0) {
final ViewGroup parent = (ViewGroup) viewParent;
// 解析佈局視圖
// 返回的view是android:layout指定的佈局文件最頂層的view
final View view = inflateViewNoAdd(parent);
// 移除ViewStub
// 添加view到被移除的ViewStub的位置
replaceSelfWithView(view, parent);
// 添加view到 mInflatedViewRef 中
mInflatedViewRef = new WeakReference<>(view);
if (mInflateListener != null) {
// 加載完成以後,回調onInflate 方法
mInflateListener.onInflate(this, view);
}
return view;
} else {
// 須要在xml中設置android:layout,不是layout,不然會拋出異常
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
}
} else {
// ViewStub不能做爲根佈局,它須要放在ViewGroup中, 不然會拋出異常
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
}
}
複製代碼
須要注意如下兩點:
來查看一下inflateViewNoAdd方法和replaceSelfWithView方法
調用inflateViewNoAdd方法返回android:layout指定的佈局文件最頂層的view frameworks/base/core/java/android/view/ViewStub.java
private View inflateViewNoAdd(ViewGroup parent) {
final LayoutInflater factory;
// mInflater 是View被建立的時候,若是是ViewStub, 將LayoutInflater賦值給mInflater
if (mInflater != null) {
factory = mInflater;
} else {
// 若是mInflater爲空,則建立LayoutInflater
factory = LayoutInflater.from(mContext);
}
// 從指定的 mLayoutResource 資源中解析佈局視圖
// mLayoutResource 是在xml設置的 Android:layout 指定的佈局文件
final View view = factory.inflate(mLayoutResource, parent, false);
// mInflatedId 是在xml設置的 inflateId
if (mInflatedId != NO_ID) {
// 將id複製給view
view.setId(mInflatedId);
//注意:若是指定了mInflatedId , 被inflate的layoutView的id就是mInflatedId
}
return view;
}
複製代碼
調用replaceSelfWithView方法, 移除ViewStub, 添加view到被移除的ViewStub的位置 frameworks/base/core/java/android/view/ViewStub.java
private void replaceSelfWithView(View view, ViewGroup parent) {
// 獲取ViewStub在視圖中的位置
final int index = parent.indexOfChild(this);
// 移除ViewStub
// 注意:調用removeViewInLayout方法以後,調用findViewById()是找不到該ViewStub對象
parent.removeViewInLayout(this);
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 將xml中指定的 android:layout 佈局文件中最頂層的View,添加到被移除的 ViewStub的位置
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
}
複製代碼
throw new IllegalArgumentException("ViewStub must have a valid layoutResource");
複製代碼
throw new IllegalStateException("ViewStub must have a non-null ViewGroup viewParent");
複製代碼
// 獲取ViewStub在視圖中的位置
final int index = parent.indexOfChild(this);
// 移除ViewStub
// 注意:調用removeViewInLayout方法以後,調用findViewById()是找不到該ViewStub對象
parent.removeViewInLayout(this);
複製代碼
// mInflatedId 是在xml設置的 inflateId
if (mInflatedId != NO_ID) {
// 將id複製給view
view.setId(mInflatedId);
//注意:若是指定了mInflatedId , 被inflate的layoutView的id就是mInflatedId
}
複製代碼
final ViewGroup.LayoutParams layoutParams = getLayoutParams();
// 將xml中指定的 android:layout 佈局文件中最頂層的View 也就是根view,
// 添加到被移除的 ViewStub的位置
if (layoutParams != null) {
parent.addView(view, index, layoutParams);
} else {
parent.addView(view, index);
}
複製代碼
到這裏關於ViewStub的構建、佈局的加載以及注意事項分析完了,接下來分析一下 LayoutInflater是如何被建立的
在0xA02 Android 10 源碼分析:Apk加載流程之資源加載文章中,介紹了Activity啓動的時候經過調用LayoutInflater的inflater的方法加載layout文件,那麼LayoutInflater是如何被建立的呢,先來看一段代碼,相信下面的代碼都不會很陌生
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int i) {
return new AllVh(LayoutInflater
.from(viewGroup.getContext())
.inflate(R.layout.list_item, viewGroup, false));
}
複製代碼
LayoutInflater的inflate方法的三個參數都表明什麼意思?
resource其實很好理解就是資源Id,而root 和 attachToRoot 分別表明什麼意思:
LayoutInflater是一個抽象類,經過調用了from()的靜態函數,經由系統服務LAYOUT_INFLATER_SERVICE,最終建立了一個LayoutInflater的子類對象PhoneLayoutInflater,繼承結構以下:
LayoutInflater.from(ctx) 就是根據傳遞過來的Context對象,調用getSystemService()來獲取對應的系統服務, 來看一下這個方法 frameworks/base/core/java/android/view/LayoutInflater.java
public static LayoutInflater from(Context context) {
// 獲取系統服務 LAYOUT_INFLATER_SERVICE ,並賦值給 LayoutInflater
// Context 是一個抽象類,真正的實現類是ContextImpl
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
複製代碼
而Context自己是一個抽象類,它真正的實例化對象是ContextImpl frameworks/base/core/java/android/app/ContextImpl.java
public Object getSystemService(String name) {
// SystemServiceRegistry 是管理系統服務的
// 調用getSystemService方法,經過服務名字查找對應的服務
return SystemServiceRegistry.getSystemService(this, name);
}
複製代碼
SystemServiceRegistry管理全部的系統服務,調用getSystemService方法,經過服務名字查找對應的服務 frameworks/base/core/java/android/app/SystemServiceRegistry.java
public static Object getSystemService(ContextImpl ctx, String name) {
// SYSTEM_SERVICE_FETCHERS 是一個map集合
// 從 map 集合中取出系統服務
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
複製代碼
若是有getSystemService方法來獲取服務,那麼相應的也會有添加服務的方法 frameworks/base/core/java/android/app/SystemServiceRegistry.java
private static <T> void registerService(String serviceName, Class<T> serviceClass,
ServiceFetcher<T> serviceFetcher) {
SYSTEM_SERVICE_NAMES.put(serviceClass, serviceName);
SYSTEM_SERVICE_FETCHERS.put(serviceName, serviceFetcher);
}
複製代碼
經過調用SYSTEM_SERVICE_NAMES的put方法,往map集合中添加數據,那麼registerService是何時調用的,在SystemServiceRegistry類中搜索registerService方法,知道了在類加載的時候經過靜態代碼塊中添加的,來看一下
static {
// 初始化加載全部的系統服務
...
// 省略了不少系統服務
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
...
// 省略了不少系統服務
}
複製代碼
最終是建立了一個PhoneLayoutInflater並返回的,到這裏LayoutInflater的建立流程就分析完了
View中的INVISIBLE、VISIBLE、GONE都有什麼做用?
若是想隱藏或者顯示View,能夠經過調用setVisibility(int visibility)方法來實現,參數visibility對應三個值分別是INVISIBLE、VISIBLE、GONE
爲何ViewStub是大小爲0的視圖? frameworks/base/core/java/android/view/ViewStub.java
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
// 設置視圖大小爲0
setMeasuredDimension(0, 0);
}
複製代碼
ViewStub有什麼做用?
ViewStub的做用主要用來延遲佈局的加載
ViewStub是如何建立的?
由於ViewStub是繼承View, 因此ViewStub的建立和View的建立是相同的,經過調用了LayoutInflater.createView方法根據完整的類的路徑名利用反射機制構建View對象 frameworks/base/core/java/android/view/LayoutInflater.java
...
try {
// 利用構造函數,建立View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// 若是是ViewStub,則設置LayoutInflater
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
} finally {
mConstructorArgs[0] = lastContext;
}
...
複製代碼
爲何ViewStub能作到延遲加載?
由於在解析layout文件過程當中遇到ViewStub,只是構建ViewStub的對象和初始化ViewStub的屬性,沒有真正開始解析view,因此能夠作到延遲初始化
ViewStub指定的Layout佈局文件是何時被加載的?
當ViewStub控件設置可見,或者調用inflate()方法,ViewStub所指定的layout資源就會被加載
LayoutInflater是一個抽象類它如何被建立的?
LayoutInflater是一個抽象類,經過調用了from()的靜態函數,經由系統服務LAYOUT_INFLATER_SERVICE,最終建立了一個LayoutInflater的子類對象PhoneLayoutInflater,繼承結構以下:
LayoutInflater.from(ctx) 就是根據傳遞過來的Context對象,調用getSystemService()來獲取對應的系統服務
系統服務存儲在哪裏?如何獲取和添加系統服務?
SystemServiceRegistry管理全部的系統服務,全部的系統服務都存儲在一個map集合SYSTEM_SERVICE_FETCHERS當中,調用getSystemService方法獲取系統服務,調用registerService方法添加系統服務
致力於分享一系列的Android系統源碼、逆向分析、算法相關的文章,每篇文章都會反覆檢查以後纔會發佈,若是你同我同樣喜歡研究Android源碼,一塊兒來學習,期待與你一塊兒成長
Android 10 源碼系列:
工具系列:
逆向系列: