【重拾View(三)】——LayoutInflater中Factory源码解析

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的源码分析和拓展

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 218,284评论 6 506
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 93,115评论 3 395
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 164,614评论 0 354
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,671评论 1 293
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,699评论 6 392
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,562评论 1 305
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,309评论 3 418
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 39,223评论 0 276
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,668评论 1 314
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,859评论 3 336
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,981评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,705评论 5 347
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,310评论 3 330
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,904评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 33,023评论 1 270
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 48,146评论 3 370
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,933评论 2 355

推荐阅读更多精彩内容