在 Android 中 LayoutInflater 是扮演着很重要的角色,不少時候咱們忽略了它的重要性,由於它的重要性完 全被隱藏起來了,能夠說是直接隱藏在了Activity , Fragment 等組件的光環之下了。java
在 Android 系統中,咱們常常以 Context 獲取系統級別的服務,好比 AMS, WMS, LayoutInfoater 等,這些服務會在合適的時候註冊在系統中,在咱們須要的時候 getSS(String name) 經過系統的名字來獲取。咱們先來看一段代碼:node
這裏我就拿 Activity setContentView() 舉例android
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
複製代碼
繼續跟下去:緩存
/** * Should be called instead of {@link Activity#setContentView(int)}} */
public abstract void setContentView(@LayoutRes int resId);
複製代碼
跟下去發現是一個抽象類,咱們找它的實現類:app
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
//經過 LayoutInflater 加載 XML id
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
複製代碼
咱們在 AppCompatDelegateImpl 類找到了實現類,眼神好的是否是發現了上面的 LayoutInflater ,沒錯咱們 Activity 最後也是經過 LayoutInflater 解析 XML 加載佈局的,繼續跟 from 函數:ide
/** * Obtains the LayoutInflater from the given context. */
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater)
//經過 Context 獲取服務
context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
複製代碼
經過上面代碼能夠知道,LayoutInflater 是經過 Context 的 getSystemService(String name) 來獲取到的。context 的 getSS 函數怎麼獲取到的勒,下面咱們就來介紹下 Context 的源碼。函數
其實在 Application,Activity,Service 中都會存在一個 Context 對象,咱們叫其上下文,能夠經過這個上下文,啓動 Activity,Service, 註冊一個廣播,獲取系統服務等等操做,那麼 Context 是怎麼建立出來的勒,先來看一段代碼:oop
public abstract class Context {...}
複製代碼
Context 是一個抽象類,咱們找下它的實現類,咱們知道在啓動 Activity 的時候有一個 Context 上下文,啓動 Activity 的入口在 ActivityThread main 函數,咱們就從這裏開始找佈局
//經過反射調用執行的
public static void main(String[] args) {
...
//主線程消息循環
Looper.prepareMainLooper();
//建立 ActivityThread 對象
ActivityThread thread = new ActivityThread();
//Application,Activity 入口
thread.attach(false);
Looper.loop();
...
throw new RuntimeException("Main thread loop unexpectedly exited");
}
複製代碼
private void attach(boolean system) {
sCurrentActivityThread = this;
mSystemThread = system;
//不是系統級別的應用
if (!system) {
ViewRootImpl.addFirstDrawHandler(new Runnable() {
@Override
public void run() {
ensureJitEnabled();
}
});
android.ddm.DdmHandleAppName.setAppName("<pre-initialized>",
UserHandle.myUserId());
RuntimeInit.setApplicationObject(mAppThread.asBinder());
final IActivityManager mgr = ActivityManager.getService();
try {
//經過 IActivityManager。aidl 文件 底層經過 Binder 通訊,關聯 Application
mgr.attachApplication(mAppThread);
} catch (RemoteException ex) {
throw ex.rethrowFromSystemServer();
}
...
} else {
代碼省略
....
}
複製代碼
在 main 方法中,咱們建立了 ActivityThread 對象後,調用了其 attach 函數,而且參數爲 false。在 attach 函數中,參數爲 false 的狀況下是屬於非系統應用,會經過 Binder 機制與 AMS 通訊,而且最終調用 H 類的 LAUNCH_ACTIVITY - > handleLaunchActivity 函數,咱們看下該函數的實現:post
/** * *啓動 Activity * @param r * @param customIntent * @param reason */
private void handleLaunchActivity(ActivityClientRecord r, Intent customIntent, String reason) {
//實施 啓動 Activity 的實例
Activity a = performLaunchActivity(r, customIntent);
}
複製代碼
繼續跟:
/***啓動 Activity 代碼*/
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
....
//1. 建立 Context 對象
ContextImpl appContext = createBaseContextForActivity(r);
Activity activity = null;
.....
try {
// 2. 製做 Application 對象
Application app = r.packageInfo.makeApplication(false, mInstrumentation);
if (activity != null) {
//3. 獲取 Context 對象
CharSequence title = r.activityInfo.loadLabel(appContext.getPackageManager());
Configuration config = new Configuration(mCompatConfiguration);
appContext.setOuterContext(activity);
//4. 將 appContext 等對象依附在 Activity 的 attach 函數中
activity.attach(appContext, this, getInstrumentation(), r.token,
r.ident, app, r.intent, r.activityInfo, title, r.parent,
r.embeddedID, r.lastNonConfigurationInstances, config,
r.referrer, r.voiceInteractor, window, r.configCallback);
//是不是持久化
if (r.isPersistable()) {
mInstrumentation.callActivityOnCreate(activity, r.state, r.persistentState);
} else {
//5. 調用 Activity 的 onCreate 方法
mInstrumentation.callActivityOnCreate(activity, r.state);
}
....
return activity;
}
複製代碼
/***Context 的實現類 ContextImp*/
private ContextImpl createBaseContextForActivity(ActivityClientRecord r) {
//1.建立 Activity 的 Context 對象, 到這裏點擊 ContextImpl 是 Context 實現類
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, r.activityInfo, r.token, displayId, r.overrideConfig);
....
return appContext;
}
複製代碼
/** * Common implementation of Context API, which provides the base * context object for Activity and other application components. */
class ContextImpl extends Context {
...
}
複製代碼
經過上面代碼 1- 5 的註釋分析可知,Context 的實現類是 ContextImpl, 這裏咱們至關於又帶着你們複習了一遍 Application , Activity 啓動源碼了。
經過上面咱們得知 ContextImpl 是 Context 的實現類,咱們繼續看源碼
public class ContextImpl extends Context{
...
/** * 經過服務名稱代號 Context.XXX 拿到系統各類服務 */
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
...
}
複製代碼
這裏咱們發現返回的是 SystemServiceRegistry 類裏面的 getSystemService 函數,繼續跟:
/** * Gets a system service from a given context. */
public static Object getSystemService(ContextImpl ctx, String name) {
ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
return fetcher != null ? fetcher.getService(ctx) : null;
}
複製代碼
/** * 交給子類來實現獲取服務 * */
static abstract interface ServiceFetcher<T> {
T getService(ContextImpl ctx);
}
複製代碼
/** * 裝服務的容器 */
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
複製代碼
到了這裏咱們知道了經過容器緩存拿到了 LayInflater 服務,那麼何時註冊的?下面咱們繼續看該類源碼
final class SystemServiceRegistry {
/***裝系統各類服務的容器,這裏至關於容器單例類*/
private static final HashMap<String, ServiceFetcher<?>> SYSTEM_SERVICE_FETCHERS =
new HashMap<String, ServiceFetcher<?>>();
// Not instantiable.
private SystemServiceRegistry() { }
static {
.....
//註冊 LayoutInflater 服務
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
.....
}
}
複製代碼
static abstract class CachedServiceFetcher<T> implements ServiceFetcher<T> {
private final int mCacheIndex;
public CachedServiceFetcher() {
mCacheIndex = sServiceCacheSize++;
}
@Override
@SuppressWarnings("unchecked")
public final T getService(ContextImpl ctx) {
final Object[] cache = ctx.mServiceCache;
synchronized (cache) {
// Fetch or create the service.
Object service = cache[mCacheIndex];
if (service == null) {
try {
service = createService(ctx);
cache[mCacheIndex] = service;
} catch (ServiceNotFoundException e) {
onServiceNotFound(e);
}
}
return (T)service;
}
}
//交給抽象實現去建立服務
public abstract T createService(ContextImpl ctx) throws ServiceNotFoundException;
}
複製代碼
經過上面的代碼能夠知道抽象實現返回的是 new PhoneLayoutInflater(ctx.getOuterContext()); 那麼這個 Phone... 到底什麼了? 咱們繼續跟
public class PhoneLayoutInflater extends LayoutInflater {
}
複製代碼
真相大白啊,PhoneLayoutInflater 就是繼承的 LayoutInflater。
總結
:
經過上面的代碼可知,在虛擬機第一次加載該類時,經過 靜態代碼塊 會註冊各類 ServiceFatcher, 這其中就包含了 LayoutInflater Service, 將這些服務以鍵值對的形式存儲在 Map 中, 用戶使用時只須要根據 key 來獲取對應的 ServiceFetcher, 而後經過 ServiceFetcher 對象的 getService 來獲取具體服務對象。當第一次獲取時,會調用 ServiceFetcher 的 createService 函數建立服務,而後緩存到一個列表中,下次再取直接從緩存中獲取,從而避免了重複建立對象,從而達到了單例的效果,這不就是我以前介紹的單例模式-容器單例模式嘛,經過容器的單例模式實現方式,系統核心服務以單例形式存在,減小了資源消耗。
@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();
}
複製代碼
跟 inflate
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
// root 不爲 null , 則會從resourec 佈局解析到 View ,並添加到 root 中
return inflate(resource, root, root != null);
}
複製代碼
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) + ")");
}
//獲取 XMl 解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
複製代碼
/** * * @param parser xml 解析器 * @param root 解析佈局的父視圖 * @param attachToRoot 是否將要解析的視圖添加到父視圖中 * @return */
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
//Context 對象
mConstructorArgs[0] = inflaterContext;
//存儲父視圖
View result = root;
try {
// Look for the root node.
int type;
//找到 root 元素
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
final String name = parser.getName();
//1. 解析 Merge 佈局標籤
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");
}
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 根據 Tag 來解析layout 跟視圖
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 生成佈局參數
params = root.generateLayoutParams(attrs);
//若是 attachRoot 爲 false,那麼將給 temp 設置佈局參數
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 解析 temp 下全部子 View
rInflateChildren(parser, temp, attrs, true);
// 若是 Root 不爲空,且 attachToroot 爲 true ,那麼將 temp 添加到父佈局中
if (root != null && attachToRoot) {
root.addView(temp, params);
}
//若是 root == null 且 attachToRoot 爲 false 那麼直接返回 temp
if (root == null || !attachToRoot) {
result = temp;
}
}
} catch (XmlPullParserException e) {
...
return result;
}
}
複製代碼
上述 inflate 方法中,主要有下面幾步:
咱們在看一段代碼,先從簡單的理解:
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
//1. 用戶能夠經過設置 LayoutInflater 的 factory 來自行解析 View,默認這些 Factory 都爲空,能夠忽略這段
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);
}
//2. 沒有 Factory 的狀況下經過 onCreateView 或者 createView 建立 View
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
// 3. 內置 View 控件的解析
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//4 自定義 View 的解析
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
...
}
複製代碼
本段代碼重點就在註釋 2 處,當這個 tag 的名字包含 「.」 時,認爲這是一個內置 View, 也就是
<TextView
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
複製代碼
這裏的 TextView 就是 XMl 標籤的名字,所以,在執行 infate 時就會調用註釋 3 處的 onCreateView 來解析 TextView 標籤。那麼,當咱們自定義 View 時,就會執行註釋 4
<com.t01.TextView
android:id="@+id/list_item"
android:layout_width="match_parent"
android:layout_height="wrap_content"
tools:ignore="MissingConstraints" />
複製代碼
在上面的 PhoneLayoutInflater 重寫了 onCreateView 方法,該方法就是在 View 標籤名的前面設置了一個 「android.widget」 前綴,而後傳遞給 createView 解析。
那麼咱們來看下 createView 源碼具體實現吧
//根據完整的路徑的類名經過反射機制構造 View 對象
public final View createView(String name, String prefix, AttributeSet attrs) throws ClassNotFoundException, InflateException {
//1. 經過緩存獲取構造函數
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);
//2. 沒有緩存構造函數
if (constructor == null) {
// 若是 prefix 不爲空,那麼構造函數的 View 路徑,而且加載該類
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);
}
}
//3. 從 Class 對象中獲取構造函數
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
//4. 將構造函數存入緩存中
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;
//5. 經過反射構造 View
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
}
複製代碼
createView 至關來講還比較理解,若是有前綴,那麼就構造 View 的完整路徑,而且將該類加載到虛擬機中,而後獲取該類的構造函數而且緩存下來,在經過構造函數來建立該 View 的對象,最後將對象返回,這就是解析單個 View 的過程。而咱們的窗口中時一個視圖樹, LayoutInflater 須要解析完這棵樹,這個功能就交給 rInflateChildren 方法,看下面代碼
void rInflate(XmlPullParser parser, View parent, Context context, AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
//1. 獲取樹的深度,優先遍歷
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
//2. 挨個元素解析
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
if (type != XmlPullParser.START_TAG) {
continue;
}
final String name = parser.getName();
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) { // 解析 include 標籤
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) { //解析到 merge 標籤,拋出異常,由於 merge 標籤必須是根視圖
throw new InflateException("<merge /> must be the root element");
} else {
//3. 根據元素名進行解析,又回去了
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
//遞歸調用進行解析
rInflateChildren(parser, view, attrs, true);
//將解析到的 View 添加進 ViewGroup 中,也就是它的 parent
viewGroup.addView(view, params);
}
}
...
}
複製代碼
rInflateChildren 經過深度優先遍從來構造視圖樹,每解析到一個 View 元素就會遞歸調用 rInflateChildren ,直到這條路徑的最後一個元素,而後在回溯過來將每個 View 元素添加進 parent 中,經過 rInflateChildren 解析以後,整棵樹就構建完畢了。當回調了 onResume 以後,setContentView 設置的內容就會出如今屏幕中了。
LayoutInflater 涉及的知識源碼仍是挺多的,有 Application , Activity 的啓動,還有深度廣度遍歷,XML 節點解析,容器單例模式。這裏也至關於帶着你們溫習了一遍 Activity 啓動流程吧。
好了,到了這裏相信你們對 setContentView 以後幹了些什麼事兒,已經有必定了解了。
感謝你的閱讀,謝謝!