LayoutInflater 是我們在 Android 開發中經常用到的一個類
為什么要說是經常用到的呢?因為我們寫的 xml 布局配置文件都是通過 LayoutInflater 來 inflate 成具體的 View 對象的,剛接觸 Android 開發的同學可能會疑惑,我沒有用過這個東西啊,它是什么鬼?那是因為我們在 Activity 中加載布局是通過 setContentView() 函數完成的
public void setContentView(int layoutResID) {
// 這里調用 getWindow() 拿到 window 對象
// 然后調用 mWindow.setContentView() 函數
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}
Window.java 是一個抽象類,繼承并實現它的是 PhoneWindow.java , mWindow 是在 Activity 的 attach() 函數中被初始化的,這個函數是在 ActivityThread 中在創建 Activity 時被調用,我們來看一下 attach() 的源碼
// 一堆參數忽略不用管他
final void attach(Context context, ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
NonConfigurationInstances lastNonConfigurationInstances,
Configuration config,
String referrer, IVoiceInteractor voiceInteractor) {
// 設置 baseContext
attachBaseContext(context);
// 重點在這里,new 了一個 PhoneWindow 對象
mWindow = new PhoneWindow(this);
// 這里先不說,后面會講到
mWindow.getLayoutInflater().setPrivateFactory(this);
// 此后省略部分無關代碼
}
接下來,我們再進一步深入到 PhoneWindow 中看看構造函數都做了什么
public PhoneWindow(Context context) {
super(context);
mLayoutInflater = LayoutInflater.from(context);
}
PhoneWindow 的構造函數除了調用 super() 外就只是初始化了一個 LayoutInflater 對象,mWindow.getLayoutInflater() 返回的就是 mLayoutInflater 對象,那么 mLayoutInflater 是到底如何被創建的?我們繼續跟代碼
public static LayoutInflater from(Context context) {
LayoutInflater LayoutInflater =
(LayoutInflater) context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
if (LayoutInflater == null) {
throw new AssertionError("LayoutInflater not found.");
}
return LayoutInflater;
}
這里的 context 是 Activity 對象,以下是 Activity 的 getSystemService() 函數代碼
@Override
public Object getSystemService(@ServiceName @NonNull String name) {
if (getBaseContext() == null) {
throw new IllegalStateException(
"System services not available to Activities before onCreate()");
}
if (WINDOW_SERVICE.equals(name)) {
return mWindowManager;
} else if (SEARCH_SERVICE.equals(name)) {
ensureSearchManager();
return mSearchManager;
}
return super.getSystemService(name);
}
最終調用了 super.getSystemService(name)
@Override
public Object getSystemService(String name) {
if (LAYOUT_INFLATER_SERVICE.equals(name)) {
if (mInflater == null) {
// 實例化 mInflater
mInflater = LayoutInflater.from(getBaseContext()).cloneInContext(this);
}
return mInflater;
}
return getBaseContext().getSystemService(name);
}
這里是調用了 baseContext 的 getSystemService() 函數,獲取一個實例,然后調用 cloneInContext() 復制一個新的實例
getBaseContext() 是 ContextWrapper 的函數, Activity 繼承 ContextThemeWrapper, ContextThemeWrapper 繼承了 ContextWrapper,它是在 Activity 的 attach() 函數中通過 attachBaseContext() 進行的賦值,那我們就有必要知道這里的 baseContext 到底是什么,getSystemService() 做了些什么
我們在 ActivityThread 中找到對應的代碼如下
private Activity performLaunchActivity(ActivityClientRecord r, Intent customIntent) {
Activity activity = null;
try { // 通過反射 new 一個 Activity 對象
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
activity = mInstrumentation.newActivity(
cl, component.getClassName(), r.intent);
// ...
} catch (Exception e) {
// ...
}
Context appContext = createBaseContextForActivity(r, activity);
// ...
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);
// ... 各種狀態設置以及異常處理
return activity;
}
private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
// ...
// 通過 ContextImpl 為 Activity 創建一個 Context 對象
ContextImpl appContext = ContextImpl.createActivityContext(
this, r.packageInfo, displayId, r.overrideConfig);
appContext.setOuterContext(activity);
Context baseContext = appContext;
// ...
return baseContext;
}
距離真相又近了一步
static ContextImpl createActivityContext(ActivityThread mainThread,
LoadedApk packageInfo, int displayId, Configuration overrideConfiguration) {
if (packageInfo == null) throw new IllegalArgumentException("packageInfo");
// 直接返回新的 ContextImpl 對象
return new ContextImpl(null, mainThread, packageInfo, null, null, false,
null, overrideConfiguration, displayId);
}
ContextImpl.createActivityContext() 只是 new 了一個 ContextImpl 對象,構造函數我們不再分析,直接看 ContextImpl 的 getSystemService() 函數
@Override
public Object getSystemService(String name) {
return SystemServiceRegistry.getSystemService(this, name);
}
接著看 SystemServiceRegistry 的部分代碼
static {
// ... 省略大量代碼
registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
new CachedServiceFetcher<LayoutInflater>() {
@Override
public LayoutInflater createService(ContextImpl ctx) {
return new PhoneLayoutInflater(ctx.getOuterContext());
}});
// ... 省略大量代碼
}
/**
* 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;
}
我們最終的結論是在 Activity 中通過 getSystemService(LAYOUT_INFLATER_SERVICE) 得到的是一個 PhoneLayoutInflater 對象,他的代碼十分簡單
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};
public PhoneLayoutInflater(Context context) {
super(context);
}
protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}
/** Override onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@Override
protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}
return super.onCreateView(name, attrs);
}
public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}
PhoneLayoutInflater 是用來解析系統控件的,比如 TextView 、Button 、ImageView 等,通過 name 與 prefix 拼接得到類全名,如果解析失敗,就調用 super
前面扯了一大堆沒用的,我們回過頭來看 mWindow.setContentView() 里面做了什么事
@Override
public void setContentView(int layoutResID) {
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
mLayoutInflater.inflate(layoutResID, mContentParent);
}
}
在 Activity 第一次被創建的時候 mContentParent 一定是空的,所以走 installDecor() 函數,我們簡單的認為此 Activity 沒有 transition 動畫,所以會調用 mLayoutInflater.inflate(layoutResID, mContentParent) 去加載布局文件
在分析 LayoutInflater.inflate() 函數之前,我們出于好奇,看一下 installDecor() 是干什么的
private void installDecor() {
if (mDecor == null) {
mDecor = generateDecor();
// 省略部分代碼
}
if (mContentParent == null) {
mContentParent = generateLayout(mDecor);
// 省略代碼...
}
}
protected DecorView generateDecor() {
return new DecorView(getContext(), -1);
}
// DecorView 是繼承自 FrameLayout 的一個內部 View
private final class DecorView extends FrameLayout implements RootViewSurfaceTaker {
public DecorView(Context context, int featureId) {
super(context);
// ...
}
// ...
}
protected ViewGroup generateLayout(DecorView decor) {
// Apply data from current theme.
TypedArray a = getWindowStyle();
// ... 根據主題設置各種屬性
a.recycle();
int layoutResource;
// 這里主要是根據 theme 不同選擇不同的容器布局
if (xxx) {
layoutResource = R.layout.xxx;
} else { // 不止兩個條件,此處簡寫
layoutResource = R.layout.xxx;
}
// 實例化容器布局
View in = mLayoutInflater.inflate(layoutResource, null);
// 添加到 decor 中,并且是充滿 decor
decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
// ID_ANDROID_CONTENT 的值為 com.android.internal.R.id.content
ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
if (contentParent == null) {
throw new RuntimeException("Window couldn't find content container view");
}
// ..
return contentParent;
}
通過上面的代碼我們可以看到,在 Activity 中通過 setContentView() 設置的布局,實際上最后是被添加到了 mContentParent 中,通過查看源碼發現,mContentParent 其實也是一個 FrameLayout
別的不多扯,下面進入正題,分析 PhoneLayoutInflater.java 前面已經貼過了它的源碼,它的構造函數只是調用了 super
protected LayoutInflater(Context context) {
mContext = context;
}
protected LayoutInflater(LayoutInflater original, Context newContext) {
mContext = newContext;
mFactory = original.mFactory;
mFactory2 = original.mFactory2;
mPrivateFactory = original.mPrivateFactory;
setFilter(original.mFilter);
}
也很簡單,一個參數的就是存儲一下 context 對象,兩個參數的,還把 original 的三個回調以及過濾器賦值給自己
Filter
public interface Filter {
// 作用是是否允實例化 clazz
boolean onLoadClass(Class clazz);
}
Factory
public interface Factory {
// 用來解析 xml 中自定義的 tag ,建議自定義 tag 加入自己的包名以區別于系統的
public View onCreateView(String name, Context context, AttributeSet attrs);
}
Factory2
public interface Factory2 extends Factory {
// 多了一個父布局 parent
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
和 setFactory 相關的函數一共有三個
public void setFactory(LayoutInflater.Factory factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = factory;
} else {
mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
}
}
public void setFactory2(Factory2 factory) {
if (mFactorySet) {
throw new IllegalStateException("A factory has already been set on this LayoutInflater");
}
if (factory == null) {
throw new NullPointerException("Given factory can not be null");
}
mFactorySet = true;
if (mFactory == null) {
mFactory = mFactory2 = factory;
} else {
mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
}
}
/**
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}
通過上面的代碼,我們可以知道,Factory 和 Factory2 兩個只能設置其中一個,setPrivateFactory() 是隱藏 api ,setFactory() 或者 setFactory2() 函數要求在調用之前,最好用 cloneInContext() 獲取一個新的實例,而且原來的 Factory 依然存在,會和自己的 Factory 合并
FactoryMerger
private static class FactoryMerger implements Factory2 {
private final Factory mF1, mF2;
private final Factory2 mF12, mF22;
FactoryMerger(Factory f1, Factory2 f12, Factory f2, Factory2 f22) {
mF1 = f1;
mF2 = f2;
mF12 = f12;
mF22 = f22;
}
public View onCreateView(String name, Context context, AttributeSet attrs) {
View v = mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF2.onCreateView(name, context, attrs);
}
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
View v = mF12 != null ? mF12.onCreateView(parent, name, context, attrs)
: mF1.onCreateView(name, context, attrs);
if (v != null) return v;
return mF22 != null ? mF22.onCreateView(parent, name, context, attrs)
: mF2.onCreateView(name, context, attrs);
}
}
FactoryMerger 實現了 Factory2 接口,構造函數有四個參數,前兩個是新的 Factory ,后兩個參數是舊的 Factory ,回調中先調用新的 Factory ,如果返回為空,則調用舊的 Factory
主角 inflate() 函數登場,相關函數一共四個,但最終都是調用的同一個
// 解析 xml 資源 如果 root 不為空,resource 生成的 view 會被添加到 root 中
public View inflate(int resource, ViewGroup root) {
return inflate(resource, root, root != null);
}
// 解析一個 xml 文件
public View inflate(XmlPullParser parser, ViewGroup root) {
return inflate(parser, root, root != null);
}
// 解析 xml 資源 第三個參數用來指定 view 是否應該 add 到 root 里
// 如果 root 為空 則地三個參數沒有意義
public View inflate(int resource, ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 通過 Resources 獲取一個 XmlResourceParser
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
// 上面三個函數最終都會調用這個函數,他是重點
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
// 重點,下面單獨貼
}
從上面的代碼,我們知道,inflate() 函數有四個重載函數,最后都只會調用一個函數,下面是代碼
public View inflate(XmlPullParser parser, ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
// mConstructorArgs 是長度為 2 的 Object 數組
// 第 0 個是 context 對象 第 1 個 是 AttributeSet 對象
mConstructorArgs[0] = inflaterContext;
View result = root;
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
}
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
final String name = parser.getName();
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 {
// 創建 view
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
// 生成 LayoutParams
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
temp.setLayoutParams(params);
}
}
// 解析 temp 的子 view
rInflateChildren(parser, temp, attrs, true);
if (root != null && attachToRoot) {
// 如果 root 不為空并且 attachToRoot 為 true
// 則把 xml 解析完成的 view 添加到 root 中
root.addView(temp, params);
}
if (root == null || !attachToRoot) {
result = temp;
}
}
// 如果 view 被添加到了 root 則返回 root
// 否則返回 xml 解析出來的 view
return result;
}
}
其實 LayoutInflater 是使用的 pull 解析方式來解析的 xml ,關于 xml 解析方式,可以去網上搜,不止 pull 一種解析方式
可以看出,如果 xml 的根標簽是 merge 那么 root 不能為空并且 attachToRoot 必須為 true ,否則會拋異常,后面調用了 createViewFromTag() ,就是根據 tag 節點創建 View
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
if (name.equals("view")) {
// 如果是 view 標簽就獲取 class 的值
name = attrs.getAttributeValue(null, "class");
}
if (!ignoreThemeAttr) {
// 如果設置了主題
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
// 則生成一個帶有主題的 context
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
if (name.equals(TAG_1995)) {
// 被 blink 包住的 view 會一閃一閃的,可以看 BlinkLayout 源碼
return new BlinkLayout(context, attrs);
}
View view;
if (mFactory2 != null) {
// 如果 mFactory2 不為空則調用 mFactory2.onCreateView()
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
// 如果經過上面的步驟 view 還是空,并且 mPrivateFactory 不為空
// 則回調 mPrivateFactory.onCreateView()
// 我們前面分析了,Activity 的 LayoutInflater 是設置了 PrivateFactory 的
if (view == null && mPrivateFactory != null) {
view = mPrivateFactory.onCreateView(parent, name, context, attrs);
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) { // 如果 tag 中沒有 .
// 如果子類沒有重寫 onCreateView() 則默認拼接 android.view. 作為類名
// PhoneLayoutInflater 重寫了 onCreateView() 上面提到過
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
}
createViewFromTag() 中調用了 createView() 函數,第二個參數代表要拼接到 name 前的前綴
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
Class<? extends View> clazz = null;
if (constructor == null) {
// 使用 ClassLoader 加載類
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);
}
}
// 拿到兩參的構造器,存起來
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
if (mFilter != null) {
// 獲取之前保存過的狀態
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
// 把是否允許狀態保存起來
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
Object[] args = mConstructorArgs;
args[1] = attrs;
// 實例化 view
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) { // ViewStub 懶加載
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
return view;
}
通過上面的步驟,只是解析完成了 xml 的根 view ,接下來要循環解析 xml 中的 子 view
final void rInflateChildren(XmlPullParser parser, View parent, AttributeSet attrs,
boolean finishInflate) throws XmlPullParserException, IOException {
rInflate(parser, parent, parent.getContext(), attrs, finishInflate);
}
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
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)) {
parseRequestFocus(parser, parent);
} else if (TAG_TAG.equals(name)) {
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
throw new InflateException("<merge /> must be the root element");
} else {
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);
viewGroup.addView(view, params);
}
}
if (finishInflate) {
parent.onFinishInflate();
}
}
上面的代碼就是根據不同的 tag 做不同的解析,如果不是 requestFocus 、tag 、include 或者 merge 就調用 createViewFromTag() 然后循環 rInflateChildren(),直到解析出所有 View ,限于篇幅原因,LayoutInflater 對各種 tag 的解析,留做后續講解