Android xml解析到View的過程
阿新 • • 發佈:2018-12-11
分析 View 系列
原始碼分析
前言,一些必須知道的知識
API 版本 27
我們先看看 AppCompatActivity 的跟這次主題相關的 重要方法
public class AppCompatActivity ...{ @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); // 這個方法我們稍微記一下,待會要 delegate.installViewFactory(); // .... super.onCreate(savedInstanceState); } @NonNull public AppCompatDelegate getDelegate() { if (mDelegate == null) { mDelegate = AppCompatDelegate.create(this, this); } return mDelegate; } @Override public void setContentView(@LayoutRes int layoutResID) { getDelegate().setContentView(layoutResID); } }
AppCompatDelegate 是什麼呢,我們進入繼續觀看
public abstract class AppCompatDelegate { .... public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) { return create(activity, activity.getWindow(), callback); } // 根據不同的系統版本 建立不同的 Imp private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) { if (Build.VERSION.SDK_INT >= 24) { return new AppCompatDelegateImplN(context, window, callback); } else if (Build.VERSION.SDK_INT >= 23) { return new AppCompatDelegateImplV23(context, window, callback); } else { return new AppCompatDelegateImplV14(context, window, callback); } } }
我們 檢視一下繼承關係
對應的類點選檢視發現
public class AppCompatActivity ...{ @Override protected void onCreate(@Nullable Bundle savedInstanceState) { final AppCompatDelegate delegate = getDelegate(); // 這個方法我們稍微記一下,待會要進入主題 delegate.installViewFactory(); } // 重點關注下 LayoutInflater.Factory2 class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase implements MenuBuilder.Callback, LayoutInflater.Factory2 { @Override public void installViewFactory() { LayoutInflater layoutInflater = LayoutInflater.from(mContext); if (layoutInflater.getFactory() == null) { // 重點方法,第二個引數是 LayoutInflater.Factory2 也就是自己實現了 LayoutInflaterCompat.setFactory2(layoutInflater, this); } else { if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) { Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed" + " so we can not install AppCompat's"); } } } } // 而 Factory2 介面就只有一個方法 public interface Factory2 extends Factory { public View onCreateView(View parent, String name, Context context, AttributeSet attrs); }
public abstract class LayoutInflater {
public void setFactory2(Factory2 factory) {
// 可以看到只能設定一次,而上面說明的開源庫,是通過反射修改這個值
// 替換自己的 Factory2
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);
}
}
}
進入主題
那麼上面講了這麼多跟 xml解析有什麼關係呢,我們進入主題後,你就明白前面的意思了 ···
// 1
public class AppCompatActivity ...{
@Override
public void setContentView(@LayoutRes int layoutResID) {
getDelegate().setContentView(layoutResID);
}
}
// 2
class AppCompatDelegateImplV9 {
@Override
public void setContentView(int resId) {
ensureSubDecor();
ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
contentParent.removeAllViews();
// 通過 LayoutInflater 解析,我們跟進去看下
LayoutInflater.from(mContext).inflate(resId, contentParent);
mOriginalWindowCallback.onContentChanged();
}
}
// 3
public abstract class LayoutInflater {
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
final Resources res = getContext().getResources();
// 通過 資源ID 獲取 XmlResourceParser
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) {
....
if (TAG_MERGE.equals(name)) {
.. 其實內部最終也會呼叫 createViewFromTag
rInflate(parser, root, inflaterContext, attrs, false);
} else {
// 關鍵方法 傳入
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
}
}
// 4
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 (mFactory2 != null) {
view = mFactory2.onCreateView(parent, name, context, attrs);
} else if (mFactory != null) {
view = mFactory.onCreateView(name, context, attrs);
} else {
view = null;
}
}
總結前面分析
關鍵方法 | 描述 |
---|---|
LayoutInflater.from(mContext).inflate(resId, contentParent) | 傳入資源ID ,父容器,可空 |
XmlResourceParser parser = res.getLayout(resource); | 通過資源ID 獲取 xml解析後的類 |
mFactory2.onCreateView(parent, name, context, attrs) | 而後呼叫 mFactory2.onCreateView 建立對應view |
mFactory2 在上面的 AppCompatDelegateImplV9 的 installViewFactory() 中已設定好了的, 其實 mFactory2 就是 AppCompatDelegateImplV9.
檢視 View的建立流程
public class AppCompatDelegateImplV9{
@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);
}
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
....
mAppCompatViewInflater = new AppCompatViewInflater();
} else {
....
}
...
return mAppCompatViewInflater.createView(....);
}
}
下面我們檢視 系統控制元件的建立過程
public AppCompatViewInflater {
/**
* 可以發現,如果是TextView,就直接返回V7包中的AppCompatTextView類,
* 所以這就是為什麼當我們使 用AppCompatActivity的時候,TextView變成AppCompatTextView的原因。
*/
@NonNull
protected AppCompatTextView createTextView(Context context, AttributeSet attrs) {
return new AppCompatTextView(context, attrs);
}
// 建立 View
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;
....
switch (name) {
case "TextView":
view = createTextView(context, attrs);
verifyNotNull(view, name);
break;
default:
view = createView(context, name, attrs);
}
if (view == null && originalContext != context) {
// 使用名字 inflate,主題將會無效
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// 如果我們嘗試建立一個 view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}
}
那麼自定義控制元件,還有哪些需要 v7 包名的元件怎麼建立的呢?
public AppCompatViewInflater {
// 預設字首包名列表
private static final String[] sClassPrefixList = {
"android.widget.",
"android.view.",
"android.webkit."
};
private View createViewFromTag(Context context, String name, AttributeSet attrs) {
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
try {
mConstructorArgs[0] = context;
mConstructorArgs[1] = attrs;
// 嘗試從 預設字首包名列表 中查詢
if (-1 == name.indexOf('.')) {
for (int i = 0; i < sClassPrefixList.length; i++) {
final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
if (view != null) {
return view;
}
}
return null;
} else {
// 載入自定義View,也就是完整名 比如 com.xm.xbutton
return createViewByPrefix(context, name, null);
}
} catch (Exception e) {
return null;
} finally {
mConstructorArgs[0] = null;
mConstructorArgs[1] = null;
}
}
}
總結
public interface Factory2 extends Factory {
pulic View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}
}
簡單幾句話講解下 xml的建立過程
- AppCompatActivity onCreate 時呼叫 installViewFactory (實際為 AppCompatDelegateImplV9)
- AppCompatDelegateImplV9 實現介面 Factory2
- installViewFactory 方法將 LayoutInflater.setFactory2(AppCompatDelegateImplV9.this)
- 將資源 id 解析為 XmlResourceParser ,並 createViewFromTag 的方法
- 內部呼叫 Factory2.createView 建立View
- 內部使用 AppCompatViewInflater.java 專門用來根據 xml中分別的名稱,建立View