源码分析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(" 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这个单词已经道破了所有         * 

* 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; note that this may be null. * @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:LinearLayout2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: orientation12020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: fitsSystemWindowstrue2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-12020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: s:ViewStub2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: theme?168438252020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: id@169092882020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout@173670702020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: inflatedId@169092892020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-22020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: s:FrameLayout2020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: id@169082902020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: layout_height-12020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foreground?168428412020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foregroundGravity0x372020-05-29 22:50:40.566 2170-2170/com.xct.codebase D/niubi: foregroundInsidePaddingfalse2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarOverlayLayout2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: id@21311652432020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: fitsSystemWindowstrue2020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.569 2170-2170/com.xct.codebase D/niubi: layout_height-12020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ContentFrameLayout2020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: id@21311651912020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: layout_height-12020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: foreground?168428412020-05-29 22:50:40.570 2170-2170/com.xct.codebase D/niubi: foregroundGravity0x372020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarContainer2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: gravity0x302020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: id@21311651922020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_height-22020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: layout_alignParentToptrue2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: touchscreenBlocksFocustrue2020-05-29 22:50:40.571 2170-2170/com.xct.codebase D/niubi: style?21308375092020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.Toolbar2020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: id@21311651902020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: layout_height-22020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: navigationContentDescription@21314273292020-05-29 22:50:40.572 2170-2170/com.xct.codebase D/niubi: style?21308378132020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: s:android.support.v7.widget.ActionBarContextView2020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: theme?21308375132020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: id@21311651982020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: visibility22020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: layout_width-12020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: layout_height-22020-05-29 22:50:40.573 2170-2170/com.xct.codebase D/niubi: style?21308375312020-05-29 22:50:40.574 2170-2170/com.xct.codebase D/niubi: s:LinearLayout2020-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);    }}

更多相关文章

  1. android 4.4 JS 和 java 交互失效的解决方法
  2. Android Support库百分比布局
  3. 相对布局属性详解
  4. Android中的WebView进行直接加载网页(要注意解决权限问题)
  5. Android设备中几种YUV420p转rgb视频帧方法效率比较

随机推荐

  1. android 6.0 ConnectionService
  2. 移植android ndk c++各种兼容性问题
  3. Android 定时器真机测试
  4. Android错误之HAX is not installed on t
  5. Android Menu 用法
  6. NanoHttpd 构建android 手机端的HttpServ
  7. AlarmManagerService(一)
  8. 使用jenkins自动化构建Android(安卓)app
  9. Android(安卓)网易云音乐图片高斯模糊
  10. IPC与Binder,Binder线程池,AIDL demo - And