今天要學習源碼的兩個問題:
- Layout.xml佈局是怎麼加載解析的
- Layout.xml中的 view 標籤又是怎麼被轉化成對象的
針對這兩個問題引出源碼學習的流程,帶着問題去看源碼。
總體流程:
問題 1 佈局是怎麼加載的 :
首先我們最熟悉的代碼肯定是:
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
那就讓我們從 setContentView(R.layout.activity_main); 入手。進入方法發現:
@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();
}
進入到 AppCompatDelegateImplV9 這是個爲委託類,不是今天的重點,setContentView(int resId) 中地一句就是 ensureSubDecor(); 這個方法,由名字可知,創建sub decor view對象的。進入該方法 繼續~
private void ensureSubDecor() {
if (!mSubDecorInstalled) {
// 創建 sub decor
mSubDecor = createSubDecor();
applyFixedSizeWindow();
...省略...
}
}
}
首先會進入 createSubDecor() 創建對象 繼續 ~
private ViewGroup createSubDecor() {
..判斷主題..省略...
// Now let's make sure that the Window has installed its decor by retrieving it
mWindow.getDecorView();
final LayoutInflater inflater = LayoutInflater.from(mContext);
ViewGroup subDecor = null;
....省略..根據條件傳教 subdecor.......
// Now inflate the view using the themed context and set it as the content view
subDecor = (ViewGroup) LayoutInflater.from(themedContext)
.inflate(R.layout.abc_screen_toolbar, null);
mDecorContentParent = (DecorContentParent) subDecor
.findViewById(R.id.decor_content_parent);
mDecorContentParent.setWindowCallback(getWindowCallback());
// Now set the Window's content view with the decor
mWindow.setContentView(subDecor);
...省略....
return subDecor;
}
首先是設置主題,然後 mWindow.getDecorView(); 確保decor view 已經創建,接下來根據不同的條件創建不同的 sub decor ,subdecor其實是一個 toolbar + framelayout 佈局,content_parent 其實就是我們自己佈局的父佈局。 接下里就是 mWindow.setContentView(subDecor); 把subdecor傳給window中的decorview。
先看 mWindow.getDecorView();
private void installDecor() {
mForceDecorInstall = false;
if (mDecor == null) {
// 創建 decorView對象
mDecor = generateDecor(-1);
mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
mDecor.setIsRootNamespace(true);
if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
}
} else {
mDecor.setWindow(this);
}
if (mContentParent == null) {
// 創建 decorview 內部的 contentparent佈局對象
mContentParent = generateLayout(mDecor);
...省略...
到這裏首先是通過generateDecor(-1); 創建decorview的對象,然後在generateLayout(mDecor); 創建 內部的contentparent 對象。
然後decorView和contentparent對象創建完成之後,使用 mWindow.setContentView(subDecor); 把subdecor 對象addview放入contentparent中,代碼
public void setContentView(View view, ViewGroup.LayoutParams params) {
// Note: FEATURE_CONTENT_TRANSITIONS may be set in the process of installing the window
// decor, when theme attributes and the like are crystalized. Do not check the feature
// before this happens.
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
} else {
mContentParent.addView(view, params);
}
...省略..
}
setContentView(其實就是把我們創建的subdecor 對象add進入 DecorVIew中的 contentparent中去。
然後代碼又回到 setContentView(int resId)
@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();
}
這時候的mSubDecor對象已經被我們創建並且被放入window 的 decorview中去。然後把我們的佈局 給 加入到 subdecor中。
問題1 我們就有答案了
接下來看問題 2 ,我們佈局被加載到 系統的decorview中 後是怎麼被實例化的...通過上面的代碼我們能看到
LayoutInflater.from(mContext).inflate(resId, contentParent);
我們的佈局被傳進去了,接下來就跟蹤 layoutinflater。
問題 2 加載了我們的佈局xml之後, tag標籤又是怎麼被創建成對象的呢?
通過上述我們知道我們要跟蹤 LayoutInflater 的 inflate() 方法。
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 把 layoutRes 轉換成 parser 解析器
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}
首先就是根據layout id 獲取對應的parser解析器 res.getLayout(resource); 然後 inflate(parser, root, attachToRoot); 解析佈局。
先看下 res.getLayout(resource); 最後會調到 loadXmlResourceParser() 中:
XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
throws NotFoundException {
// 得到 TypedValue 實例
final TypedValue value = obtainTempTypedValue();
try {
final ResourcesImpl impl = mResourcesImpl;
// 根據 layout id 把佈局信息 賦值給 TypedValue 對象
impl.getValue(id, value, true);
if (value.type == TypedValue.TYPE_STRING) {
// 根據 layout 信息 返回該佈局的 parser 解析器
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
}
.....省略......
}
通過 impl.getValue(id, value, true); 根據 layout id 把layout 佈局信息賦值給 typedvalue 對象,最終調到 native 方法中。
final boolean getResourceValue(@AnyRes int resId, int densityDpi, @NonNull TypedValue outValue,
boolean resolveRefs) {
synchronized (this) {
final int block = loadResourceValue(resId, (short) densityDpi, outValue, resolveRefs);
........省略...........
}
最後調用下述方法把 parser 返回去
return impl.loadXmlResourceParser(value.string.toString(), id,
value.assetCookie, type);
這裏就是根據被賦值的 value 對象創建對應layout id 的 parser。
最後又回到了 LayoutInflater的 inflate() 方法中
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
........省略.............
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
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 {
// Temp is the root view that was found in the xml
// 根據 tag name 創建對應的 view 對象
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
......省略.....
return result;
}
}
通過上述代碼我們知道 <merge /> 和 <include /> 是不能作爲 root 根佈局的。
然後就是 final View temp = createViewFromTag(root, name, inflaterContext, attrs); 創建對象
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
try {
View view;
if (mFactory2 != null) {
// 回調創建 view 對象
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
if (-1 == name.indexOf('.')) {
// 創建 view 對象,如果爲空
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
..........省略......
}
然後就是通過
view = mFactory2.onCreateView(parent, name, context, attrs); 回調創建對象,這個回調其實 是 AppCompatDelegateImplV9 中的 onCreateView(parent, name, context, attrs) 方法
這個方法待會說,如果這個方法返回來的view 依然是 null 就會走到下面的判斷
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
這個方法中會根據 當前 view 是否包含 “.” 來 通過 反射創建 view 對象。
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
Constructor<? extends View> constructor = sConstructorMap.get(name);
........省略..........
} else {
// If we have a filter, apply it to cached constructor
if (mFilter != null) {
// Have we seen this name before?
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
// New class -- remember whether it is allowed
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 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;
// 反射創建 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;
}
回過頭 讓我們繼續分析 AppCompatDelegateImplV9 中的 onCreateView(parent, name, context, attrs) 方法。
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// First let the Activity's Factory try and inflate the view
final View view = callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}
這個方法最後會走到 createView(parent, name, context, attrs);
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
........省略.......
try {
Class viewInflaterClass = Class.forName(viewInflaterClassName);
mAppCompatViewInflater =
(AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
.newInstance();
} catch (Throwable t) {
Log.i(TAG, "Failed to instantiate custom view inflater "
+ viewInflaterClassName + ". Falling back to default.", t);
mAppCompatViewInflater = new AppCompatViewInflater();
}
}
}
.........省略.....
return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
IS_PRE_LOLLIPOP, /* 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 */
);
}
這個方法有兩個地方1是創建了 AppCompatViewInflater 對象,2 調用了 AppCompatViewInflater 對象的 createView 方法
final View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the view hierarchy
// by using the parent's context
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framework versions
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;
case "Spinner":
view = createSpinner(context, attrs);
verifyNotNull(view, name);
break;
case "ImageButton":
view = createImageButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckBox":
view = createCheckBox(context, attrs);
verifyNotNull(view, name);
break;
case "RadioButton":
view = createRadioButton(context, attrs);
verifyNotNull(view, name);
break;
case "CheckedTextView":
view = createCheckedTextView(context, attrs);
verifyNotNull(view, name);
break;
case "AutoCompleteTextView":
view = createAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "MultiAutoCompleteTextView":
view = createMultiAutoCompleteTextView(context, attrs);
verifyNotNull(view, name);
break;
case "RatingBar":
view = createRatingBar(context, attrs);
verifyNotNull(view, name);
break;
case "SeekBar":
view = createSeekBar(context, attrs);
verifyNotNull(view, name);
break;
default:
// The fallback that allows extending class to take over view inflation
// for other tags. Note that we don't check that the result is not-null.
// That allows the custom inflater path to fall back on the default one
// later in this method.
view = createView(context, name, attrs);
}
return view;
}
根據這個方法可以看到,他會根據 name 判斷當前是那個 view 然後 調用createxxx()方法創建 view,其中一個例子:
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
可以看到這裏是 new 出來的,AppCOmpat XXX view 是谷歌做的兼容。
基本上整個流程就結束了,我們看到了 我們的view 創建的地方。