xml 中 View 的加載流程 從 setContentView 提及

有些應用開發了一段時間有換主題的需求,或者須要總體添加點擊樣式,文字樣式等。若是大規模改代碼恐怕費很多時間,而且後期很差維護,能不能在 xml 加載的時候給 view 設置屬性或者替換 view 呢?分析一下 view 的加載流程,問題就迎刃而解了。

//調用了 window 的 setContentView,在 attach 中給 mWindow=new PhoneWindow(this);
    public void setContentView(@LayoutRes int layoutResID) {
        getWindow().setContentView(layoutResID);
        initWindowDecorActionBar();
    }
複製代碼

PhoneWindow主要處理了 activity 和界面相關的邏輯

/**@methord installDecor() 爲 activity 添加根佈局DecorView(mDecor),全部的事件都是經過它從 activity 傳到 view 上的。並解析 activity 的theme初始化 titleview 和過場動畫。 *@field mContentParent 根據 theme 的不一樣inflate 幾種佈局加到DecorView上,在上一個方法中同時初始化mDecor和mContentParent @field FEATURE_CONTENT_TRANSITIONS判斷是否有5.0共享元素過場動畫,沒有直接調用了 inflate **/
    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);//處理makeSceneTransitionAnimation用的
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
        }
        mContentParent.requestApplyInsets();//調用了requestFitSystemWindows
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {//調用設置 contentChange 的回調
            cb.onContentChanged();
        }
    }
複製代碼

這裏直接調用了LayoutInflater.inflate,然而LayoutInflater又是經過getSystemService(LAYOUT_INFLATER_SERVICE)得到的,在 activity 中的 mBase 又是從哪裏來的呢,在 startActivity 後啓動了新的Activity,在 Instrumentation的execStartActivity中ActivityManagerNative.getDefault().startActivity()使用ActivityManagerProxy.startActivity經過IBinder調用了系統進程。並無找到 activity 是怎麼 new 出來的java

換個套路,因爲 activity歷來不復寫構造方法,mBase 賦值的地方只有一個,attachBaseContext();複寫這個方法並在裏面拋出異常。在一層層的去分析,log代表在ActivityThread中收到了 handler,並在performLaunchActivity直接調用了 attach,在 attach 中第一行就是設置 context 的方法android

public ContextThemeWrapper() {
        super(null);//咱們繼承 activity 的時候並無調用 super 的構造,確定不是在這裏初始化的
    }
複製代碼
at android.app.Activity.attach(Activity.java:6641)
  at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2629)
  at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:2768) 
  at android.app.ActivityThread.-wrap12(ActivityThread.java) 
  at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1481) 
  at android.os.Handler.dispatchMessage(Handler.java:102) 
  at android.os.Looper.loop(Looper.java:154) 
  at android.app.ActivityThread.main(ActivityThread.java:6153) 
  at java.lang.reflect.Method.invoke(Native Method) 
  at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:868) 
  at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:758) 
複製代碼
//activity.attach 的 context 參數是這樣初始化的
  private Context createBaseContextForActivity(ActivityClientRecord r, final Activity activity) {
        int displayId = Display.DEFAULT_DISPLAY;
        try {
            displayId = ActivityManagerNative.getDefault().getActivityDisplayId(r.token);
        } catch (RemoteException e) {
        }
//就是這裏 new ContextImpl()。這個類中找 getSystemService 
        ContextImpl appContext = ContextImpl.createActivityContext( this, r.packageInfo, displayId, r.overrideConfig);
        appContext.setOuterContext(activity);
        Context baseContext = appContext;
        ......
        return baseContext;
    }
複製代碼
/**在 contextImpl 中只有一行代碼 SystemServiceRegistry.getSystemService(),進入到SystemServiceRegistry是這樣實現的 *@params SYSTEM_SERVICE_FETCHERS 這個參數中保存了 getSystemService的全部能夠用的 service,在這個類中有一個靜態方法塊,添加service *@params ServiceFetcher 是一個接口 **/
   public static Object getSystemService(ContextImpl ctx, String name) {
        ServiceFetcher<?> fetcher = SYSTEM_SERVICE_FETCHERS.get(name);
        return fetcher != null ? fetcher.getService(ctx) : null;
    }
    //靜態代碼塊中,是這樣註冊的,PhoneLayoutInflater就是實際調用的 view 解析器。
     registerService(Context.LAYOUT_INFLATER_SERVICE, LayoutInflater.class,
                new CachedServiceFetcher<LayoutInflater>() {
            @Override
            public LayoutInflater createService(ContextImpl ctx) {
                return new PhoneLayoutInflater(ctx.getOuterContext());
            }});
複製代碼

到這裏整個應用每次獲取的 LayoutInflate仍是同樣的。可是在每一個 activity 中把 LayoutInflate 的 hashcode 輸出缺是不同的。由於在 ContextThemeWrapper.getSystemService 還調用了cloneInContext緩存

//在PhoneLayoutInflater中是這樣實現的,全部每一個 activity 使用的inflate並非同一個
public LayoutInflater cloneInContext(Context newContext) {
        return new PhoneLayoutInflater(this, newContext);
    }
複製代碼

LayoutInflater.onCreateView

public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
        final Resources res = getContext().getResources();
        final XmlResourceParser parser = res.getLayout(resource);//建立了 xml 解析器
        try {//最終解析的方法
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }
複製代碼
//這裏解析的是 xml 的第一層
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");//保存 log 到系統中
            final Context inflaterContext = mContext;
            final AttributeSet attrs = Xml.asAttributeSet(parser);
            Context lastContext = (Context) mConstructorArgs[0];
            mConstructorArgs[0] = inflaterContext;
            View result = root;
            try {
                int type;
                while ((type = parser.next()) != XmlPullParser.START_TAG && type != XmlPullParser.END_DOCUMENT) {
                    // Empty
                }//按理說只執行一次 next,這樣寫多是防止 parser 在前面的代碼已經向後移動了,當前的 type 並無在 START 的位置

                if (type != XmlPullParser.START_TAG) {
                    throw new InflateException(parser.getPositionDescription()  + ": No start tag found!");
                }
                final String name = parser.getName();
                //判斷 是否是 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");
                    }//由於 merge 不是 view,不建立根節點必需要添加到root 上
//根據root 初始化佈局
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {//經過 string和 xml 的屬性建立view 的邏輯
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
                    if (root != null) {//若是設置了root 要建立出 layoutParams
                        params = root.generateLayoutParams(attrs);//根據 attrs 建立 layoutParams
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    rInflateChildren(parser, temp, attrs, true);//和rInflate不一樣的是,這裏是加到了createViewFromTag的 view 上了。
                    if (root != null && attachToRoot) {//把createViewFromTag加到了root 上,比 merge 多了一層view
                        root.addView(temp, params);
                    }

                    if (root == null || !attachToRoot) {
                        result = temp;//若是傳入root 而且addview 了返回 root,不然返回建立的 view
                    }
                }
            } catch (XmlPullParserException e) {
                final InflateException ie = new InflateException(e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } catch (Exception e) {
                final InflateException ie = new InflateException(parser.getPositionDescription()  + ": " + e.getMessage(), e);
                ie.setStackTrace(EMPTY_STACK_TRACE);
                throw ie;
            } finally {
                mConstructorArgs[0] = lastContext;
                mConstructorArgs[1] = null;
                Trace.traceEnd(Trace.TRACE_TAG_VIEW);
            }
            return result;
        }
    }
複製代碼

rInflateChildren 和 rInflate 方法的差別是少了一個 context 的參數,由於,merge 的 root 的 attr 不必定和 xml 裏面用同一個。全部須要單獨傳 context。bash

這裏解析xml 除了根標籤的子標籤,每一層子標籤會遞歸一層app

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) {//當解析到最後一個 tag 的時候結束
            if (type != XmlPullParser.START_TAG) {
                continue;
            }
            final String name = parser.getName();
            if (TAG_REQUEST_FOCUS.equals(name)) {//view 下有 <requestFocus /> 請求焦點
                parseRequestFocus(parser, parent);
            } else if (TAG_TAG.equals(name)) {// <tag android:id="@+id/mytag" android:value="@string/mytag_value" />給 view 添加多個 tag
                parseViewTag(parser, parent, attrs);
            } else if (TAG_INCLUDE.equals(name)) {//解析到了 include
                if (parser.getDepth() == 0) {//不能把 xml 的第一個標籤就聲明爲 include
                    throw new InflateException("<include /> cannot be the root element");
                }
                parseInclude(parser, context, parent, attrs);//只是合併 include標籤和 layout 的屬性邏輯,不作分析
            } else if (TAG_MERGE.equals(name)) {//xml 中 merge 必須是根元素
                throw new InflateException("<merge /> must be the root element");
            } else {//普通的 view
                final View view = createViewFromTag(parent, name, context, attrs);
                final ViewGroup viewGroup = (ViewGroup) parent;
                final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);//只要繼承了 ViewGroup 都會複寫這個方法。
                rInflateChildren(parser, view, attrs, true);//遞歸調用 rInflate 直到完成解析
                viewGroup.addView(view, params);
            }
        }

        if (finishInflate) {//當全部的子 view 解析完成調用
            parent.onFinishInflate();
        }
    }
複製代碼
createViewFromTag是經過xml標籤生成 view 的重點方法。
複製代碼
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs, boolean ignoreThemeAttr) {
        if (name.equals("view")) {//view 的 className 支持兩種傳入方式,讀取 class 屬性值
            name = attrs.getAttributeValue(null, "class");
        }
        //include設置了theme 這裏是 false,不然都會使用 context 的屬性
        if (!ignoreThemeAttr) {
            final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
            final int themeResId = ta.getResourceId(0, 0);
            if (themeResId != 0) {
                context = new ContextThemeWrapper(context, themeResId);
            }
            ta.recycle();
        }

        if (name.equals(TAG_1995)) {//繼承了 FragmentLayout,閃爍的效果,不多用
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            if (mFactory2 != null) {//設置了自定義的 view 建立方式。這個接口就是我想設置的
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {//和 Factory2相比這個裏面的onCreateView方法。少 parent 參數
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

            if (view == null && mPrivateFactory != null){//系統的註釋很到位,這個是爲手機廠家留的,for use by framework
                view = mPrivateFactory.onCreateView(parent, name, context, attrs);
            }

            if (view == null) {//若是用戶和手機廠商沒有設置 factory,就使用系統默認的解析
                final Object lastContext = mConstructorArgs[0];
                mConstructorArgs[0] = context;
                try {
                    if (-1 == name.indexOf('.')) {//TextView,ImageView這些系統控件,在調用的時候不是全 className,這個方法裏拼接了 createView(name, "android.view.", attrs)
                        view = onCreateView(parent, name, attrs);
                    } else {//自定義 view
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }

            return view;
        } catch (InflateException e) {
            throw e;
        } catch (ClassNotFoundException e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;

        } catch (Exception e) {
            final InflateException ie = new InflateException(attrs.getPositionDescription() + ": Error inflating class " + name, e);
            ie.setStackTrace(EMPTY_STACK_TRACE);
            throw ie;
        }
    }
複製代碼
若是須要自定義 View 的實例化須要調用 setFactory2或setFactory,這兩個方法差異不大,這兩個方法若是某一個調用過一次了,若是再次調用會拋出異常,`new FactoryMerger(factory, factory, mFactory, mFactory2)`由於只能設置一次,不使用反射,後面兩個參數確定是null,前面兩個都是設置的factory。若是調用setFactory2會拿到 parent。setFactory則不會。
複製代碼

createView()方法中 經過反射獲取 class,並獲取構造方法,並使用 sConstructorMap 緩存爲後面加載一樣的 View 使用,view = constructor.newInstance(args);反射構造傳入 context 和 attr 建立一個實例,這裏只使用了兩個構造參數的方法 mConstructorSignature = new Class[] {Context.class, AttributeSet.class}。在 View 的源碼中能夠看到兩個參數的構造方法調用了4個參數的構造:ide

public View(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
        this(context);
        .........
        for (int i = 0; i < N; i++) {
            int attr = a.getIndex(i);
            switch (attr) {
                case com.android.internal.R.styleable.View_background:  ...
                    break;
                case com.android.internal.R.styleable.View_padding:  ...
                    break;
                 case com.android.internal.R.styleable.View_paddingLeft:  ...
                    break;
                case com.android.internal.R.styleable.View_paddingTop:  ...
                    break;
                case com.android.internal.R.styleable.View_paddingRight:  ...
                    break;
                    .........
複製代碼

View 重 xml 中加載的過程解析完,舉個 View 偷樑換柱的例子:AppCompatActivity

點進源碼看到它繼承了 FragmentActivity 在 onCreate 方法的第一行AppCompatDelegate delegate = getDelegate(); delegate.installViewFactory();修改了 Factory,xml 中生命的 TextView 都被實例化成了 AppCompatTextView。就有了RippleDrawable的效果oop

public final View createView(View parent, final String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext,boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
 ......
        switch (name) {
            case "TextView":
                view = new AppCompatTextView(context, attrs);
                break;
            case "ImageView":
                view = new AppCompatImageView(context, attrs);
                break;
            case "Button":
                view = new AppCompatButton(context, attrs);
                break;
            case "EditText":
                view = new AppCompatEditText(context, attrs);
                break;
            case "Spinner":
                view = new AppCompatSpinner(context, attrs);
                break;
            case "ImageButton":
                view = new AppCompatImageButton(context, attrs);
                break;
            case "CheckBox":
                view = new AppCompatCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = new AppCompatRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = new AppCompatRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = new AppCompatSeekBar(context, attrs);
                break;
        }
        if (view == null && originalContext != context) {
            view = createViewFromTag(context, name, attrs);
        }
        return view;
    }
複製代碼

add by dingshaoran佈局

相關文章
相關標籤/搜索