美文网首页Android开发Android开发经验谈Android技术知识
【重拾View(三)】——LayoutInflater中Fact

【重拾View(三)】——LayoutInflater中Fact

作者: 被代码淹没的小伙子 | 来源:发表于2018-11-04 15:24 被阅读5次

    1.【重拾View(一)】——setContentView()源码解析
    2.【重拾View(二)】——LayoutInflater源码解析
    3.【重拾View(三)】——LayoutInflater中Factory源码解析

    前言

    上一篇博客分析了LayoutInflater的inflate方法,可以说对inflate已经有了一个比较全面的认识,这里专门抽出一片博客专门分析一下LayoutInflater中的Factory相关的源码解析,因为这算是Google处理解析的一个小插曲,利用这个Factory我们可以完成很多自定义解析方式,有很多框架实质都是基于这个原理。

    源码解析

    View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
                boolean ignoreThemeAttr) {
            ...
    
            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;
                }
    
                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;
                    }
                }
    
                return view;
            }
    ...
    

    这里放上了上一篇博客分析的解析创建View的关键方法createViewFromTag,可以看到在创建的一开始,首先有三处判断条件,分辨判断了mFactory2mFactorymPrivateFactoryonCreateView方法来创建View。如果这三个创建出的View!=null ,则直接返回了这个View.
    这里其实三个都是LayoutInflater的成员变量,都有对应的setget方法。所以这里我们关注一下这三个的定义。

    private Factory mFactory;
        private Factory2 mFactory2;
        private Factory2 mPrivateFactory;
    
    public interface Factory {
            public View onCreateView(String name, Context context, AttributeSet attrs);
        }
    
    public interface Factory2 extends Factory {
            public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
        }
    

    其实可以看到就是简单的两个接口,并且Factory2继承了Factory。其实两者也有一定的区别:

      1. LayoutInflater.Factory2 是API 11 被加进来的;
      1. 可以对创建 View 的 Parent 进行控制,方法参数中多了parent

    为什么说是是API 11 被加进来的呢?既然说到了API11,那么我们来看一下API11对应的AppCompatActivity的源码

    AppCompatActivity源码分析

    public class AppCompatActivity extends FragmentActivity implements AppCompatCallback, SupportParentable, DelegateProvider {
        protected void onCreate(@Nullable Bundle savedInstanceState) {
            AppCompatDelegate delegate = this.getDelegate();、
            //设置Factory工厂
            delegate.installViewFactory();
            delegate.onCreate(savedInstanceState);
            if(delegate.applyDayNight() && this.mThemeId != 0) {
                if(VERSION.SDK_INT >= 23) {
                    this.onApplyThemeResource(this.getTheme(), this.mThemeId, false);
                } else {
                    this.setTheme(this.mThemeId);
                }
            }
    
            super.onCreate(savedInstanceState);
        }
    

    这里可以看到,在AppCompatActivityonCreate方法中,调用了delegate.installViewFactory();方法,这里就利用LayoutInflater的Factory进行了设置。

    public static AppCompatDelegate create(Activity activity, AppCompatCallback callback) {
            return create(activity, activity.getWindow(), callback);
        }
    
    private static AppCompatDelegate create(Context context, Window window, AppCompatCallback callback) {
            return (AppCompatDelegate)(VERSION.SDK_INT >= 24?new AppCompatDelegateImplN(context, window, callback):(VERSION.SDK_INT >= 23?new AppCompatDelegateImplV23(context, window, callback):(VERSION.SDK_INT >= 14?new AppCompatDelegateImplV14(context, window, callback):(VERSION.SDK_INT >= 11?new AppCompatDelegateImplV11(context, window, callback):new AppCompatDelegateImplV9(context, window, callback)))));
        }
    

    关于AppCompatDelegate可以看到就是不同的版本判断得到不同的类,经常看源码的应该都清楚,这里一般不同版本都是继承关系,所以这里我们就来看一下AppCompatDelegateImplV9,不出意外installViewFactory方法在这里面就有定义。

    class AppCompatDelegateImplV9 extends AppCompatDelegateImplBase implements Callback, Factory2 {
    public void installViewFactory() {
            LayoutInflater layoutInflater = LayoutInflater.from(this.mContext);
            if(layoutInflater.getFactory() == null) {
                LayoutInflaterCompat.setFactory2(layoutInflater, this);
            } else if(!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
                Log.i("AppCompatDelegate", "The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's");
            }
        }
    
    public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            View view = this.callActivityOnCreateView(parent, name, context, attrs);
            return view != null?view:this.createView(parent, name, context, attrs);
        }
    
    public View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs) {
            if(this.mAppCompatViewInflater == null) {
                this.mAppCompatViewInflater = new AppCompatViewInflater();
            }
    
            boolean inheritContext = false;
            if(IS_PRE_LOLLIPOP) {
                inheritContext = attrs instanceof XmlPullParser?((XmlPullParser)attrs).getDepth() > 1:this.shouldInheritContext((ViewParent)parent);
            }
    
            return this.mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext, IS_PRE_LOLLIPOP, true, VectorEnabledTintResources.shouldBeUsed());
        }
    }
    

    可以看到果不其然,这里利用LayoutInflater设置了一个Factory、可以看到最终会调用createView方法,并且创建了一个AppCompatViewInflater,并调用了其createView方法。

    public final View createView(View parent, String name, @NonNull Context context, @NonNull AttributeSet attrs, boolean inheritContext, boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
            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;
            byte var12 = -1;
            switch(name.hashCode()) {
            case -1946472170:
                if(name.equals("RatingBar")) {
                    var12 = 11;
                }
                break;
            case -1455429095:
                if(name.equals("CheckedTextView")) {
                    var12 = 8;
                }
                break;
            case -1346021293:
                if(name.equals("MultiAutoCompleteTextView")) {
                    var12 = 10;
                }
                break;
            case -938935918:
                if(name.equals("TextView")) {
                    var12 = 0;
                }
                break;
            case -937446323:
                if(name.equals("ImageButton")) {
                    var12 = 5;
                }
                break;
            case -658531749:
                if(name.equals("SeekBar")) {
                    var12 = 12;
                }
                break;
            case -339785223:
                if(name.equals("Spinner")) {
                    var12 = 4;
                }
                break;
            case 776382189:
                if(name.equals("RadioButton")) {
                    var12 = 7;
                }
                break;
            case 1125864064:
                if(name.equals("ImageView")) {
                    var12 = 1;
                }
                break;
            case 1413872058:
                if(name.equals("AutoCompleteTextView")) {
                    var12 = 9;
                }
                break;
            case 1601505219:
                if(name.equals("CheckBox")) {
                    var12 = 6;
                }
                break;
            case 1666676343:
                if(name.equals("EditText")) {
                    var12 = 3;
                }
                break;
            case 2001146706:
                if(name.equals("Button")) {
                    var12 = 2;
                }
            }
    
            switch(var12) {
            case 0:
                view = new AppCompatTextView(context, attrs);
                break;
            case 1:
                view = new AppCompatImageView(context, attrs);
                break;
            case 2:
                view = new AppCompatButton(context, attrs);
                break;
            case 3:
                view = new AppCompatEditText(context, attrs);
                break;
            case 4:
                view = new AppCompatSpinner(context, attrs);
                break;
            case 5:
                view = new AppCompatImageButton(context, attrs);
                break;
            case 6:
                view = new AppCompatCheckBox(context, attrs);
                break;
            case 7:
                view = new AppCompatRadioButton(context, attrs);
                break;
            case 8:
                view = new AppCompatCheckedTextView(context, attrs);
                break;
            case 9:
                view = new AppCompatAutoCompleteTextView(context, attrs);
                break;
            case 10:
                view = new AppCompatMultiAutoCompleteTextView(context, attrs);
                break;
            case 11:
                view = new AppCompatRatingBar(context, attrs);
                break;
            case 12:
                view = new AppCompatSeekBar(context, attrs);
            }
    
            if(view == null && context != context) {
                view = this.createViewFromTag(context, name, attrs);
            }
    
            if(view != null) {
                this.checkOnClickListener((View)view, attrs);
            }
    
            return (View)view;
        }
    

    会发现这里将其实就是将一些低版本的Widget自动变成兼容的Widget(例如将 TextView 变成 AppCompatTextView)以便于向下兼容新版本中的效果,在高版本中的一些Widget新特性就是这样在老版本中也能展示的。
    综上,我们完全也可以仿照这种实现方式,将我们需要转变的View转换成我们自定义的View。但是这里我们需要注意一点。

    public void setFactory2(Factory2 factory) {
            //如果设置过,再设置会抛异常
            if (mFactorySet) {
                throw new IllegalStateException("A factory has already been set on this LayoutInflater");
            }
            if (factory == null) {
                throw new NullPointerException("Given factory can not be null");
            }
            //一旦设置过,就会置true
            mFactorySet = true;
            if (mFactory == null) {
                mFactory = mFactory2 = factory;
            } else {
                mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
            }
        }
    

    可以看到这里的set方法,原则上是只允许设置一次的,设置过后,就会将mFactorySet变量设置为true,如果再次设置就会抛异常。所以这里当我们的Activity继承的是刚才提到的AppCompatActivity。通过刚才的源码我们发现,在AppCompatActivity的onCreate方法里源码会自动设置一个Factory用于替换Widget向下兼容。所以当我们这时需要自己定义Factory的时候,我们就需要注意需要在super.onCreate()方法之前设置,不然会报异常(反射想干啥干啥...)

    FragmentActivity的源码解析

    这里还要分析一下FragmentActivity,使用过Fragment的都知道,我们需要将Activity继承FragmentActivity,这是为什么呢?待我们来慢慢分析。

    <fragment
        android:layout_width="match_parent"
        android:layout_height="match_parent"
        />
    

    首先我们知道,Fragment是可以使用标签在XML中定义的,所以我们可以从这个角度考虑一下,看过上一篇博客的应该清楚,在解析XML的时候,特殊的标签都会有专门的条件判断进行解析,例如merge,但是却没有Fragment标签,结合前面的分析,我们这时候应该就该明白,其实也是这个Factory捣的鬼,所以对应于需要继承FragmentActivity,我们这时候就需要去看一下FragmentActivity的源码。通过继承关系我们会发现最终在父类Activity实现了Factory2接口。

    继承关系
    final FragmentController mFragments = FragmentController.createController(new HostCallbacks());
    
    @Nullable
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return null;
        }
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            if (!"fragment".equals(name)) {
                return onCreateView(name, context, attrs);
            }
    
            return mFragments.onCreateView(parent, name, context, attrs);
        }
    

    可以看到这里对fragment进行了特殊的判断,当时Fragment标签时,会交给FragmentController进行创建,而这个FragmentController可以看到在声明变量的时候已经创建了。

    private FragmentController(FragmentHostCallback<?> callbacks) {
            mHost = callbacks;
        }
    
    public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            return mHost.mFragmentManager.onCreateView(parent, name, context, attrs);
        }
    

    而且可以看到这里最终会交给一个叫mFragmentManager的onCreateView方法,这里其实已经很熟悉了,但是我们还是来看一看,可以看到这里mHost变量是在构造函数的时候传入的,而前面构造函数可以看到直接new了一个HostCallbacks(),而HostCallbacks是Activity的内部类,继承于FragmentHostCallback

    public abstract class FragmentHostCallback<E> extends FragmentContainer {
    ...
        final FragmentManagerImpl mFragmentManager = new FragmentManagerImpl();
    
    inal class FragmentManagerImpl extends FragmentManager implements LayoutInflater.Factory2 {
    @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            if (!"fragment".equals(name)) {
                return null;
            }
    
            String fname = attrs.getAttributeValue(null, "class");
            TypedArray a =
                    context.obtainStyledAttributes(attrs, com.android.internal.R.styleable.Fragment);
            if (fname == null) {
                fname = a.getString(com.android.internal.R.styleable.Fragment_name);
            }
            int id = a.getResourceId(com.android.internal.R.styleable.Fragment_id, View.NO_ID);
            String tag = a.getString(com.android.internal.R.styleable.Fragment_tag);
            a.recycle();
    
            int containerId = parent != null ? parent.getId() : 0;
            if (containerId == View.NO_ID && id == View.NO_ID && tag == null) {
                throw new IllegalArgumentException(attrs.getPositionDescription()
                        + ": Must specify unique android:id, android:tag, or have a parent with"
                        + " an id for " + fname);
            }
    
            // If we restored from a previous state, we may already have
            // instantiated this fragment from the state and should use
            // that instance instead of making a new one.
            //先查找已存在的Fragment
            Fragment fragment = id != View.NO_ID ? findFragmentById(id) : null;
            if (fragment == null && tag != null) {
                fragment = findFragmentByTag(tag);
            }
            if (fragment == null && containerId != View.NO_ID) {
                fragment = findFragmentById(containerId);
            }
    
            if (FragmentManagerImpl.DEBUG) Log.v(TAG, "onCreateView: id=0x"
                    + Integer.toHexString(id) + " fname=" + fname
                    + " existing=" + fragment);
            if (fragment == null) {
                //反射创建
                fragment = mContainer.instantiate(context, fname, null);
                fragment.mFromLayout = true;
                fragment.mFragmentId = id != 0 ? id : containerId;
                fragment.mContainerId = containerId;
                fragment.mTag = tag;
                fragment.mInLayout = true;
                fragment.mFragmentManager = this;
                fragment.mHost = mHost;
                fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
                addFragment(fragment, true);
            } else if (fragment.mInLayout) {
                // A fragment already exists and it is not one we restored from
                // previous state.
                throw new IllegalArgumentException(attrs.getPositionDescription()
                        + ": Duplicate id 0x" + Integer.toHexString(id)
                        + ", tag " + tag + ", or parent id 0x" + Integer.toHexString(containerId)
                        + " with another fragment for " + fname);
            } else {
                // This fragment was retained from a previous instance; get it
                // going now.
                fragment.mInLayout = true;
                fragment.mHost = mHost;
                // If this fragment is newly instantiated (either right now, or
                // from last saved state), then give it the attributes to
                // initialize itself.
                if (!fragment.mRetaining) {
                    fragment.onInflate(mHost.getContext(), attrs, fragment.mSavedFragmentState);
                }
            }
    
            // If we haven't finished entering the CREATED state ourselves yet,
            // push the inflated child fragment along. This will ensureInflatedFragmentView
            // at the right phase of the lifecycle so that we will have mView populated
            // for compliant fragments below.
            if (mCurState < Fragment.CREATED && fragment.mFromLayout) {
                //回调Fragment的onCreateView生命周期
                moveToState(fragment, Fragment.CREATED, 0, 0, false);
            } else {
                moveToState(fragment);
            }
    
            if (fragment.mView == null) {
                throw new IllegalStateException("Fragment " + fname
                        + " did not create a view.");
            }
            if (id != 0) {
                fragment.mView.setId(id);
            }
            if (fragment.mView.getTag() == null) {
                fragment.mView.setTag(tag);
            }
            return fragment.mView;
        }
    }
    }
    

    到这里就很清楚了,这里首先查找已存在的Fragment,如果没有找到就利用反射创建Fragment的实例。

    void moveToState(Fragment f, int newState, int transit, int transitionStyle,
                boolean keepActive) {
        switch (f.mState) {
             case Fragment.CREATED:
                f.mView = f.performCreateView(f.performGetLayoutInflater(
                                        f.mSavedFragmentState), container, f.mSavedFragmentState);
    ...
        }
    }
    
    public class Fragment implements Parcelable{
        View performCreateView(LayoutInflater inflater, ViewGroup container,
                Bundle savedInstanceState) {
            if (mChildFragmentManager != null) {
                mChildFragmentManager.noteStateNotSaved();
            }
            mPerformedCreateView = true;
            //回调生命周期
            return onCreateView(inflater, container, savedInstanceState);
        }
    }
    

    总结

    通过上面的分析我们其实会发现Factory的强大之处,利用Factory我们可以做许多的操作,例如更换主题等。经过这两篇博客的分析,对于LayoutInflater我们算是有了一个比较全面的了解,只能说对于View,我们需要了解的还有很多。

    相关博客推荐

    LayoutInflater 源码分析系列

    LayoutInflater的源码分析和拓展

    相关文章

      网友评论

        本文标题:【重拾View(三)】——LayoutInflater中Fact

        本文链接:https://www.haomeiwen.com/subject/zpnrxqtx.html