源碼分析android 系統framework(二)之view的佈局加載流程

源碼分析android 系統framework(二)之view的佈局加載流程

我們基本寫android UI都是採用xml的方式來寫的,那麼我們寫的xml到底是怎麼展示到屏幕上的,今天我們就來看下這個流程。
上一篇文章中介紹了window,view和activity的關係。知道最後我們在activity中的setContentView最終是調用到PhoneWindow 的setContentView。我們今天就從這看起:

1. PhoneWindow.java
@Override
    public void setContentView(int layoutResID) {
        // 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)) {
            final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
                    getContext());
            transitionTo(newScene);
        } else {
            mLayoutInflater.inflate(layoutResID, mContentParent);
            // 這裏這個方法就是將資源文件,轉換爲view,這個方法大家應該比較熟悉,在fragment中應該經常用到。
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
        mContentParentExplicitlySet = true;
    }
2. LayoutInflater.java
   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();
        if (DEBUG) {
            Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
                    + Integer.toHexString(resource) + ")");
        }
        final XmlResourceParser parser = res.getLayout(resource);  // 1 
        try {
            return inflate(parser, root, attachToRoot);// 2
        } finally {
            parser.close();
        }
    }

以上1處,我們看到是在解析xml,2處纔是真正加載創建加載view

3. Resource.java
   @NonNull
    public XmlResourceParser getLayout(@LayoutRes int id) throws NotFoundException {
        return loadXmlResourceParser(id, "layout");
    }
    @NonNull
    XmlResourceParser loadXmlResourceParser(@AnyRes int id, @NonNull String type)
            throws NotFoundException {
        final TypedValue value = obtainTempTypedValue();
        try {
            final ResourcesImpl impl = mResourcesImpl;
            impl.getValue(id, value, true);
            if (value.type == TypedValue.TYPE_STRING) {
                return impl.loadXmlResourceParser(value.string.toString(), id,
                        value.assetCookie, type);
            }
            throw new NotFoundException("Resource ID #0x" + Integer.toHexString(id)
                    + " type #0x" + Integer.toHexString(value.type) + " is not valid");
        } finally {
            releaseTempTypedValue(value);
        }
    }
4.回到LayoutInflater.java
 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
		...............
                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
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

      ............

  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")) {
            name = attrs.getAttributeValue(null, "class");
        }

        // Apply a theme wrapper, if allowed and one is specified.
        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)) {
            // Let's party like it's 1995!
            return new BlinkLayout(context, attrs);
        }

        try {
            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }
// 這裏這兩個mFactory2和Factory比較重要,可以看到加載時首先是會通過它們兩來創建view,
//如果他們兩都會空的話,則纔會執行下面的onCreateView。這兩個比較重要,
//我們可以通過他兩來搞一些事情,這個後面再說
            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('.')) {
                        view = onCreateView(parent, name, attrs);
                    } else {
                        view = createView(name, null, attrs);
                    }
                } finally {
                    mConstructorArgs[0] = lastContext;
                }
            }
  public interface Factory {
        /**
         * Hook you can supply that is called when inflating from a LayoutInflater.
         * You can use this to customize the tag names available in your XML
         * layout files.
         *這裏的註釋,hook這個單詞已經道破了所有
         * <p>
         * Note that it is good practice to prefix these custom names with your
         * package (i.e., com.coolcompany.apps) to avoid conflicts with system
         * names.
         *
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         *
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        public View onCreateView(String name, Context context, AttributeSet attrs);
    }

    public interface Factory2 extends Factory {
        /**
         * Version of {@link #onCreateView(String, Context, AttributeSet)}
         * that also supplies the parent that the view created view will be
         * placed in.
         *
         * @param parent The parent that the created view will be placed
         * in; <em>note that this may be null</em>.
         * @param name Tag name to be inflated.
         * @param context The context the view is being created in.
         * @param attrs Inflation attributes as specified in XML file.
         *
         * @return View Newly created view. Return null for the default
         *         behavior.
         */
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
    }
     */
    protected View onCreateView(View parent, String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return onCreateView(name, attrs);
    }
    protected View onCreateView(String name, AttributeSet attrs)
            throws ClassNotFoundException {
        return createView(name, "android.view.", attrs);
    }
public final View createView(String name, String prefix, AttributeSet attrs)
            throws ClassNotFoundException, InflateException {
        Constructor<? extends View> constructor = sConstructorMap.get(name);
        if (constructor != null && !verifyClassLoader(constructor)) {
            constructor = null;
            sConstructorMap.remove(name);
        }
        Class<? extends View> clazz = null;

        try {
            Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);

            if (constructor == null) {
                // Class not found in the cache, see if it's real, and try to add it
                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 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;

            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;
           ..................
    }

構造函數一路往下。我們從上面的createView來看,最後的 view的由來是通過反射的方法拿到的,然後拿到具體的view後,就是view自己ondraw ,onlayout來畫了。這部分就不說了。到此view的加載流程我們就清楚了。這裏有幾點思考:

性能優化的思考:

我們從上面看到加載xml佈局文件時,有幾個耗時操作:

  1. 解析xml IO耗時

  2. 反射獲取view 對象耗時

故如果想要在view的加載上提升下性能,可以使用直接new 的方式,例如,new TextView()。我們公司的大部分UI代碼都是new 出來,我起初看到一個android 工程中沒有xml 佈局文件。差點都要崩潰了。

雖然我們追求了性能,但是對於代碼的可讀性,以及我們的維護來說又反而太差了。所以就又了下面牛逼的一個 庫,我們通過xml寫佈局,然後這個庫默認幫我們轉成new 的方式。真是叼炸天了。

推薦x2c框架

全局替換UI使用

如下,還是上面中那個代碼。我們可以看到,我們佈局中的每一個view,都需要先經過Factory2,然後再經過factory,最後再是默認的CreatView,但是前兩個默認是空的。所以呢,我們就可以自己去實現這個方法。然後每次當加載系統默認的ui 組件的時候,都會先走我們自己的creatView.

            View view;
            if (mFactory2 != null) {
                view = mFactory2.onCreateView(parent, name, context, attrs);
            } else if (mFactory != null) {
                view = mFactory.onCreateView(name, context, attrs);
            } else {
                view = null;
            }

所以我們可以如下:

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
    //這裏說一下,我們爲什麼要用LayoutInflaterCompat,因爲後面加Compat的基本都是兼容包,考慮到兼容,我們就用這個,然後我們這裏用Factory2。另外,一定要在super.onCreate() 上面
        LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
                Log.d("niubi", "s:" + name);
                int n = attrs.getAttributeCount();
                for (int i=0;i<n;i++){
                    Log.d("niubi",attrs.getAttributeName(i)+attrs.getAttributeValue(i));
                }
                return null;
            }

            @Override
            public View onCreateView(String name, Context context, AttributeSet attrs) {
                return null;
            }
        });
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_appcompart);
    }
}

我們隨便寫了個佈局文件activity_appcompart.xml。然後從從上面的打印如下:

D/niubi: s:LinearLayout
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: orientation1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: fitsSystemWindowstrue
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: s:ViewStub
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: theme?16843825
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: id@16909288
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout@17367070
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: inflatedId@16909289
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: s:FrameLayout
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: id@16908290
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foreground?16842841
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foregroundGravity0x37
2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foregroundInsidePaddingfalse
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarOverlayLayout
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: id@2131165243
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: fitsSystemWindowstrue
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ContentFrameLayout
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: id@2131165191
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: layout_height-1
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: foreground?16842841
2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: foregroundGravity0x37
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarContainer
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: gravity0x30
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: id@2131165192
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_alignParentToptrue
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: touchscreenBlocksFocustrue
2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: style?2130837509
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.Toolbar
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: id@2131165190
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: navigationContentDescription@2131427329
2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: style?2130837813
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarContextView
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: theme?2130837513
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: id@2131165198
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: visibility2
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: layout_width-1
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: layout_height-2
2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: style?2130837531
2020-05-29 22:50:40.574 2170-2170/com.xct.codebase D/niubi: s:LinearLayout
2020-05-29 22:50:40.574 2170-2170/com.xct.codebase D/niubi: layout_width-1

可以看到每一個view 控件都會傳到這個回調方法裏來。所以我們可以在這個方法寫去重寫想要替換的view,例如,如果想要將自己UI中所有的textView 全局替換成自己的自定義view。則可以直接在這個方法裏面去new 自己定義的view。

public class TestActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
            @Override
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
              switch(name){
				case "TextView" :
				return new CustomTextView(context,attrs)
			}  
            }
        });
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_appcompart);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章