AppCompatActivity通过layoutInflater的setFactory2替换View的过程

1,849 阅读6分钟

学习阅读源码笔记。

LayoutInflater 的主要作用是将layout布局文件实例化为相应的 View 对象。


 经常的用法是一下三种,第一种是调用的第二种,第二种调用的第三种。他们的区别主要是区别在第二个参数parent参数上。 

java View.inflate(this, R.layout.activity_main, null); LayoutInflater.from(this).inflate(R.layout.activity_main, null); LayoutInflater.from(this).inflate(R.layout.activity_main, null, false); 

Activity和AppCompatActivity的onCreate中的setContentView方法的执行流程是不一样的。在继承AppCompatActivity的情况下,如果xml中使用了例如TextView,那么会自动的替换成AppCompatTextView。而且创建的方式是通过反射。 是通过LayoutInflater的setFactory2的方法进行了拦截。

Activity的setContentView过程

 添加view的过程 

1,在activity创建后进行,创建phoneWindow实列。 

2,初始化DectorView 

3,找到DectorView中的contentId对象添加view 

   public void setContentView(int layoutResID) {
       if (mContentParent == null) {
            installDecor();  // 在该处初始化了decor
        } 
        mLayoutInflater.inflate(layoutResID, mContentParent);
    }

初始化decor的过程,在generateDecor()方法中生成了decorview。在generateLayout中根据app配置的样式进行各种判断,加载不同的系统准备好的layout文件。例如有titlebar或者全空白的layout。

private void installDecor() {        
if (mDecor == null) {
            mDecor = generateDecor();
            mDecor.setDescendantFocusability(ViewGroup.FOCUS_AFTER_DESCENDANTS);
            mDecor.setIsRootNamespace(true);
            if (!mInvalidatePanelMenuPosted && mInvalidatePanelMenuFeatures != 0) {
                mDecor.postOnAnimation(mInvalidatePanelMenuRunnable);
            }
        }
        if (mContentParent == null) {
            mContentParent = generateLayout(mDecor); // 将decor传过去
            }
        }
    }

protected ViewGroup generateLayout(DecorView decor) {
        TypedArray a = getWindowStyle();
        mIsFloating = a.getBoolean(R.styleable.Window_windowIsFloating, false);
         if (mIsFloating) {
            setLayout(WRAP_CONTENT, WRAP_CONTENT);
            setFlags(0, flagsToUpdate);
        } else {
            setFlags(FLAG_LAYOUT_IN_SCREEN|FLAG_LAYOUT_INSET_DECOR, flagsToUpdate);
        }
// notitle  根据样式中的属性配置界面
        if (a.getBoolean(R.styleable.Window_windowNoTitle, false)) {
            requestFeature(FEATURE_NO_TITLE);
        } else if (a.getBoolean(R.styleable.Window_windowActionBar, false)) {
         
            requestFeature(FEATURE_ACTION_BAR);
        }
        if (a.getBoolean(R.styleable.Window_windowActionBarOverlay, false)) {
            requestFeature(FEATURE_ACTION_BAR_OVERLAY);
        }
        if (a.getBoolean(R.styleable.Window_windowActionModeOverlay, false)) {
            requestFeature(FEATURE_ACTION_MODE_OVERLAY);
        }
        if (a.getBoolean(R.styleable.Window_windowSwipeToDismiss, false)) {
            requestFeature(FEATURE_SWIPE_TO_DISMISS);
        }
        WindowManager.LayoutParams params = getAttributes();
        if (params.windowAnimations == 0) {
            params.windowAnimations = a.getResourceId(
                    R.styleable.Window_windowAnimationStyle, 0);
        }
         // Inflate the window decor.  填充decor
        int layoutResource;  // 进行各种判断来进行对该值的赋值 ,找到一个合适的资源添加到dector中        
int features = getLocalFeatures();
         if ((features & (1 << FEATURE_SWIPE_TO_DISMISS)) != 0) {
            layoutResource = R.layout.screen_swipe_dismiss; //  系统布局
        } else if ((features & (1 << FEATURE_ACTION_BAR)) == 0) {
            layoutResource = R.layout.screen_progress;
        } else if ((features & (1 << FEATURE_ACTION_MODE_OVERLAY)) != 0) {
            layoutResource = R.layout.screen_simple_overlay_action_mode;
        } else { 
           // Embedded, so no decoration is needed.
            layoutResource = R.layout.screen_simple; //
             // System.out.println("Simple!");
        }
        mDecor.startChanging();//加载系统资源,实列化出来。添加到decor中
          View in = mLayoutInflater.inflate(layoutResource, null); 
       decor.addView(in, new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));

        mContentRoot = (ViewGroup) in;// ID_ANDROID_CONTENT  该id为系统资源layout中的framelayout的id,用来包含自己的布局。
        ViewGroup contentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);// findViewById 是调用的decorviewview的findviewbyId,所以改view是在decorview中
        if (contentParent == null) {
            throw new RuntimeException("Window couldn't find content container view");
        }        return contentParent;
    }

public static final int ID_ANDROID_CONTENT = com.android.internal.R.id.content;

installDecor 对decor进行了创建(new),DecorView继承自FrameLayout,通过findviewById找到ID_ANDROID_CONTENT实例
添加自己的View对象。    

public void setContentView(View view, ViewGroup.LayoutParams params) {
        // 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)) {
            view.setLayoutParams(params);
            final Scene newScene = new Scene(mContentParent, view);
            transitionTo(newScene);
        } else {
            mContentParent.addView(view, params);
        }
        mContentParent.requestApplyInsets();
        final Callback cb = getCallback();
        if (cb != null && !isDestroyed()) {
            cb.onContentChanged();
        }
    }


至此放回了decor中需要包含我们自己写的布局viewgroup。至此形成了以下视图结构。

AppCompatActivity的setContentView的过程




调用AppCompatActivity的setContentView方法

   @Override
    public void setContentView(View view, ViewGroup.LayoutParams params) {
        getDelegate().setContentView(view, params);
    }

    public AppCompatDelegate getDelegate() {
        if (mDelegate == null) {
            mDelegate = AppCompatDelegate.create(this, this);
        }
        return mDelegate;
    }


通过简单工厂方法根据版本创建对应的实例。

    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
        return create(activity, activity.getWindow(), callback);
    }

    private static AppCompatDelegate create(Context context, Window window,
            AppCompatCallback callback) {
        final int sdk = Build.VERSION.SDK_INT;
// 这几个类都是AppCompatDelegate的子类
        if (sdk >= 23) {
            return new AppCompatDelegateImplV23(context, window, callback);
        } else if (sdk >= 14) {
            return new AppCompatDelegateImplV14(context, window, callback);
        } else if (sdk >= 11) {
            return new AppCompatDelegateImplV11(context, window, callback);
        } else {
            return new AppCompatDelegateImplV7(context, window, callback);
        }
    }

 AppCompatDelegateImplV7是实现了setContentView的具体类


    public void setContentView(View v, ViewGroup.LayoutParams lp) {
        ensureSubDecor();
        ViewGroup contentParent = (ViewGroup) mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        contentParent.addView(v, lp);
        mOriginalWindowCallback.onContentChanged();
    }


根据样式的需要用inflater创建DectorView的实例,在通过android.R.id.content id 找到对应的viewgroup对象添加我们自己的View,然后通知改变。


    private void ensureSubDecor() {
        if (!mSubDecorInstalled) {
            mSubDecor = createSubDecor();
  
        }
    }
   private ViewGroup createSubDecor() {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.Theme);
        ViewGroup subDecor = null;
        if (!mWindowNoTitle) {
            if (mIsFloating) {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_dialog_title_material, null);
                mHasActionBar = mOverlayActionBar = false;
            } else if (mHasActionBar) {
                TypedValue outValue = new TypedValue();
                mContext.getTheme().resolveAttribute(R.attr.actionBarTheme, outValue, true);

                Context themedContext;
                if (outValue.resourceId != 0) {
                    themedContext = new ContextThemeWrapper(mContext, outValue.resourceId);
                } else {
                    themedContext = mContext;
                }

                subDecor = (ViewGroup) LayoutInflater.from(themedContext).inflate(R.layout.abc_screen_toolbar, null);

                mDecorContentParent = (DecorContentParent) subDecor.findViewById(R.id.decor_content_parent);
                mDecorContentParent.setWindowCallback(getWindowCallback());
 
                if (mOverlayActionBar) {
                    mDecorContentParent.initFeature(FEATURE_SUPPORT_ACTION_BAR_OVERLAY);
                }
                if (mFeatureProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_PROGRESS);
                }
                if (mFeatureIndeterminateProgress) {
                    mDecorContentParent.initFeature(Window.FEATURE_INDETERMINATE_PROGRESS);
                }
            }
        } else {
            if (mOverlayActionMode) {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple_overlay_action_mode, null);
            } else {
                subDecor = (ViewGroup) inflater.inflate(R.layout.abc_screen_simple, null);
            }

         
        }

        if (mDecorContentParent == null) {
            mTitleView = (TextView) subDecor.findViewById(R.id.title);
        }
    
        return subDecor;
    }




AppCompatActivity 替换view 的过程




在AppCompatActivity的onCreate方法中对LayoutInfalter进行了Factory的初始化。

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);// 如下onCreate方法进行的初始化
}

@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    delegate.installViewFactory();// 在onCreate方法创建时进行的factory的初始化
}
@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this);  // 该this,为实现factory的oncreteView
    } else {
    }}
public static void setFactory2(@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
    inflater.setFactory2(factory);
     }
}

然后在该方法中的LayoutInflater的inflate方法进行了替换。

   @Override   
 public void setContentView(int resId) {
        ensureSubDecor();
        ViewGroup contentParent = mSubDecor.findViewById(android.R.id.content);
        contentParent.removeAllViews();
        LayoutInflater.from(mContext).inflate(resId, contentParent);
        mAppCompatWindowCallback.getWrapped().onContentChanged();
    }

 


   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) {
   try {
            return inflate(parser, root, attachToRoot);
        } finally {
            parser.close();
        }
    }


这这一块,进行了判断,当 (root != null && attachToRoot) 的时候将创建的View添加到parent中,当 if (root == null || !attachToRoot)的时候直接返回view。所以当第二个参数为空,第三个参数为true的时候,依然返回创建的View。

 public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
        synchronized (mConstructorArgs) {
            try {

                if (TAG_MERGE.equals(name)) {
                    rInflate(parser, root, inflaterContext, attrs, false);
                } else {
                    final View temp = createViewFromTag(root, name, inflaterContext, attrs);

                    ViewGroup.LayoutParams params = null;
                    if (root != null) {
                        params = root.generateLayoutParams(attrs);
                        if (!attachToRoot) {
                            temp.setLayoutParams(params);
                        }
                    }
                    if (root != null && attachToRoot) {
                        root.addView(temp, params);
                    }
                    if (root == null || !attachToRoot) {
                        result = temp;
                    }
                }

            } catch (XmlPullParserException e) {
                throw ie;
            } 
            return result;
        }
    }



  View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,boolean ignoreThemeAttr) {
        if (name.equals("view")) {
            name = attrs.getAttributeValue(null, "class");
        }

        try {
            View view = tryCreateView(parent, name, context, attrs);
            return view;
        } catch (InflateException e) {
            throw e;

        } 
    }

在AppCompatActivity中的onCreate方法中,初始花了mFactory2 ,所以在onCreateView方法会调用到AppCompatDelegateImpl类的onCreateView。

public final View tryCreateView(@Nullable View parent, @NonNull String name,View view;
    if (mFactory2 != null) {  // 是Androidx中适配的factory,
        view = mFactory2.onCreateView(parent, name, context, attrs);//
    } else if (mFactory != null) { // 系统的factory
        view = mFactory.onCreateView(name, context, attrs);
    } else {
        view = null;    
}
    return view;
}
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    return createView(parent, name, context, attrs);
}

@Override
public View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs) {
        if ((viewInflaterClassName == null)
                || AppCompatViewInflater.class.getName().equals(viewInflaterClassName)) {
            mAppCompatViewInflater = new AppCompatViewInflater();
        } else {
        }
    }
     return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed() );
}

在以下的方法中替换成对应的AppCompatXX控件。

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;
         if (inheritContext && parent != null) {
            context = parent.getContext();
        }
        if (readAndroidTheme || readAppTheme) {
            context = themifyContext(context, attrs, readAndroidTheme, readAppTheme);
        }
        if (wrapContext) {
            context = TintContextWrapper.wrap(context);
        }
        View view = null;
        switch (name) {
            case "TextView":
                view = createTextView(context, attrs);
                break;
            case "ImageView":
                view = createImageView(context, attrs);
                break;
            case "Button":
                view = createButton(context, attrs);
                break;
            case "EditText":
                view = createEditText(context, attrs);
                break;
            case "Spinner":
                view = createSpinner(context, attrs);
                break;
            case "ImageButton":
                view = createImageButton(context, attrs);
                break;
            case "CheckBox":
                view = createCheckBox(context, attrs);
                break;
            case "RadioButton":
                view = createRadioButton(context, attrs);
                break;
            case "CheckedTextView":
                view = createCheckedTextView(context, attrs);
                break;
            case "AutoCompleteTextView":
                view = createAutoCompleteTextView(context, attrs);
                break;
            case "MultiAutoCompleteTextView":
                view = createMultiAutoCompleteTextView(context, attrs);
                break;
            case "RatingBar":
                view = createRatingBar(context, attrs);
                break;
            case "SeekBar":
                view = createSeekBar(context, attrs);
                break;
            case "ToggleButton":
                view = createToggleButton(context, attrs);
                break;
            default:
                view = createView(context, name, attrs);
        }
        if (view == null && originalContext != context) {
            view = createViewFromTag(context, name, attrs);// 进行包名的拼接
        }
        if (view != null) {
            checkOnClickListener(view, attrs);
        }
        return view;
    }


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('.')) { // 是否是自定义的View 
           for (int i = 0; i < sClassPrefixList.length; i++) {
                final View view = createViewByPrefix(context, name, sClassPrefixList[i]);
                if (view != null) {
                    return view; 
               } 
           } 
           return null;
        } else {
            return createViewByPrefix(context, name, null);
        }
    } catch (Exception e) {
        return null;
    } finally {
        mConstructorArgs[0] = null;
        mConstructorArgs[1] = null;
    }
}

获取到构造方法,进行缓存,constructor.newInstance创建出view,并返回。


private static final Map<String, Constructor<? extends View>> sConstructorMap= new ArrayMap<>(); // 构造方法的缓存,因为反射太过于消耗
private static final Class<?>[] sConstructorSignature = new Class[]{Context.class, AttributeSet.class}; // 构造方法,该参数决定了xml中调用2个参数的构造方法

private View createViewByPrefix(Context context, String name, String prefix)throws ClassNotFoundException, InflateException {
    Constructor<? extends View> constructor = sConstructorMap.get(name);
    try {
        if (constructor == null) {
               Class<? extends View> clazz = context.getClassLoader().loadClass(
                    prefix != null ? (prefix + name) : name).asSubclass(View.class); 
           constructor = clazz.getConstructor(sConstructorSignature);// 获取类的2个参数的构造方法
            sConstructorMap.put(name, constructor);
        }        constructor.setAccessible(true);
        return constructor.newInstance(mConstructorArgs);
    } catch (Exception e) {
        // We do not want to catch these, lets return null and let the actual LayoutInflater
        // try
        return null;
    }
}




end.