1. 程式人生 > >Android xml解析到View的過程

Android xml解析到View的過程

分析 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的建立過程

  1. AppCompatActivity onCreate 時呼叫 installViewFactory (實際為 AppCompatDelegateImplV9)
  2. AppCompatDelegateImplV9 實現介面 Factory2
  3. installViewFactory 方法將 LayoutInflater.setFactory2(AppCompatDelegateImplV9.this)
  4. 將資源 id 解析為 XmlResourceParser ,並 createViewFromTag 的方法
  5. 內部呼叫 Factory2.createView 建立View
  6. 內部使用 AppCompatViewInflater.java 專門用來根據 xml中分別的名稱,建立View