xml 中 View 的加载流程 从 setContentView 说起

1,395 阅读7分钟

有些应用开发了一段时间有换主题的需求,或者需要整体添加点击样式,文字样式等。如果大规模改代码恐怕费不少时间,并且后期不好维护,能不能在 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 出来的

换个套路,由于 activity从来不复写构造方法,mBase 赋值的地方只有一个,attachBaseContext();复写这个方法并在里面抛出异常。在一层层的去分析,log表明在ActivityThread中收到了 handler,并在performLaunchActivity直接调用了 attach,在 attach 中第一行就是设置 context 的方法

 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。

这里解析xml 除了根标签的子标签,每一层子标签会递归一层

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个参数的构造:

    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的效果

 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