Android LayoutInflater & Theme

  1. LayoutInflater创建View的流程:

    • createViewFromTag(View parent, String name(View在xml中的名称), AttributeSet attrs):
      • 如果name就是”view”, 那么会从attrs中找出一个名称为”class”的xml属性的value作为name.
      • 如果定义了mFactory2/mFactory/mPrivateFactory, 那么会使用这3者的onCreateView(…)来从xml属性中构造出一个View, 优先级从高到低排列.
      • 如果上面三个都没有设定或者没有构造出View, 那么使用默认的create view的方法:
        • 如果name中没有”.”, 那么调用 onCreateView(parent, name, attrs),该函数其实就是在name前补充一个”android.view.”的prefix -> createView(name, “android.view.这就解释了为何对Android的原生View在xml布局文件中名字不需要写全的原因”, attrs)
        • 如果有”.”, 直接调用createView(name, null, attrs)
      • 在这个过程中, 如果出现了InflateException,直接throw, 如果出现了ClassNotFoundException/其他的Exception,都会被包装为一个InflateException 再throw出去.
  2. createView(String name, String prefix, AttributeSet attrs):

    • Constructor <? extends View> constructor = sConstructorMap.get(name): 先根据传入的name在静态全局的构造函数缓存中找到相应的构造函数.
      • 如果在缓存中没有找到:
        • 首先调用context的getClassLoader获得类加载器, 然后loadClass(综合考虑prefix和name)load相应的View的Class对象, 对得到的Class对象还会调用asSubclass(View.class), 其意思是试图将其转型为一个View.class的子类型, 如果该类不能成功的话,会抛出ClassCastException.
        • 如果load得到了一个Class对象clazz并且设置了mFilter, 那么会调用mFilter.onLoadClass(clazz), 如果返回是false,则表示这个Class是不允许被load进来的. 会调用failNotAllowed(…)来抛出一个InflateException.
        • 如果通过了上面的filter, 那么会调用clazz.getConstructor(mConstructorSignature)得到签名形式是**mConstructorSignature的构造函数, 这里mConstructorSignature是mConstructorSignature = new Class[] {
          Context.class, AttributeSet.class}, 即有两个参数,第一个是context类型,第二个是AttributeSet类型**, 并将此构造函数放入到sConstructorMap这个缓存中.
      • 如果在缓存中找到:
        • 如果设置了mFIlter, 那么还是会试图从mFilterMap中根据name找到相应的allowedState(返回true/false), 如果在mFilterMap中没有对应name的vvalue,那么需要重新进行一次loadClass,并重新使用mFilters进行检验. 并将结果保存在mFilterMap中,同样,如果没有通过filter,那么会throw一个InflateException.
    • 将attrs填充到mConstructorArgs中并将其作为参数调用之前得到的constructor的newInstance(…)的参数来得到一个View(**构造参数形式为{
      Context.class, AttributeSet.class}的构造参数**).
    • 对于ViewStub会做特殊的处理,会将ViewStub的Inflater设置为当前的LayoutInflater.
    • 上面的分析说明,在layout xml被inflate的过程中,View被构造时,被调用的构造函数是签名为Class[] {Context.class, AttributeSet.class}的两个参数的构造函数
  3. 以TextView(Context context, AttributeSet attrs)作为例子:

    • 在被inflate出来时,TextView的构造函数调用的是TextView(Context context, AttributeSet attrs), 而其内部则是调用了this(context, attrs, com.android.internal.R.attr.textViewStyle(defStyle))这个3参数构造函数.
    • View(Context context, AttributeSet attrs, int defStyleAttr):
      • context: 在这里重要的是: 通过这个context可以知道自己当前的theme
      • attrs: 从XML文件中得到的此View的属性.
      • defStyleAttr: 在当前的theme中的一个属性, 当前的tehme包含了一个指向style资源的引用,会被作用到当前View上.
    • Theme如何影响View的attr? 就是在context.obtainStyledAttributes(attrs, **com.android.internal.R.styleable.View(这个int[] 属性类表是针对View的,对于其他的View,会有额外的自己的属性表),
      defStyleAttr, 0)这个函数中:**
      • Context中的此函数调用的其实是**getTheme().(可以看到,其实最终是调用context的Theme对象)**obtainStyledAttributes(…)
      • 而Theme类的obtainStyledAttributes(AttributeSet set,
        int[] attrs, int defStyleAttr, int defStyleRes)函数的注释则说明: 在决定某个属性的值时, 有四个方面去考虑, 优先级由高到底:
        • 已经包含在了AttributeSet中的此attr的值(如果有的话)
        • 在AttributeSet中style资源中的此attr的值(如果有的话)
        • 由defStyleAttr和defStyleRes定义的此attr的值(如果有的话)
        • Theme中的此attr的默认值, Theme就是这样影响的
      • 在其实现里,可以看到其主体调用是调用AssetManager.applyStyle(mTheme, defStyleAttr, defStyleRes, parser != null ? parser.mParseState : 0, attrs(要取得的attr列表), array.mData, array.mIndices); 后者则是native层函数.
  4. 上面用到的getTheme()一般是在ContextThemeWrapper中具体实现的:

    • 如果mTheme不是null,直接返回mTheme.
    • mThemeResource = Resources.selectDefaultTheme(mThemeResource,
      getApplicationInfo() -> mPackageInfo.getApplicationInfo() -> LoadedApk的getApplicationInfo() -> LoadedApk的mApplicationInfo(构造时传入的值) -> ActivityThread中调用getPackageManager().getApplicationInfo(packageName, PackageManager.GET_SHARED_LIBRARY_FILES, userId) -> PackageParser.generateApplicationInfo(…).targetSdkVersion);
    • 调用initializeTheme():
      • 如果mTheme还没有被创建, 那么会调用getResources().newTheme()返回一个Theme对象并赋给mTheme.
      • 然后尝试获取mBase.getTheme()(即其内部所包装的真正的ContextImpl的getTheme()函数返回值), 如果得到了,会调用mTheme,setTo(theme).
      • 最后调用onApplyThemeResource(mTheme, mThemeResource, first) -> theme.applyStyle(resid(就是mThemeResource), true)
  5. ContextImp的getTheme()实现:

    • 也是判断当前是否已经有创建过mTheme了.
    • mThemeResource = Resources.selectDefaultTheme(mThemeResource,
      getOuterContext().getApplicationInfo().targetSdkVersion);
    • mTheme = mResources.newTheme();
    • mTheme.applyStyle(mThemeResource, true);

你可能感兴趣的:(Android LayoutInflater & Theme)