文章

LayoutInflater.Factory&Factory2

LayoutInflater.Factory&Factory2

LayoutInflater.Factory&Factory2

LayoutInflater 中的 Factory/Factory2

在 LayoutInflater 创建 View 时,会调用 tryCreateView() 方法,会通过我们设置的工厂来创建对象。如果我们设置了 LayoutInflater.Factory2 会优先通过 Factory2,否则通过 LayoutInflater.Factory;如果都没有设置,就会通过反射来创建 View 对象。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public final View tryCreateView(@Nullable View parent, @NonNull String name,
    @NonNull Context context,
    @NonNull AttributeSet attrs) {
    if (name.equals(TAG_1995)) {
        // Let's party like it's 1995!
        return new BlinkLayout(context, attrs);
    }
    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);
    }
    return view;
}

注意点

  1. LayoutInflater#tryCreateView 中也可以看出,mFactory2 优先于 mFactory。上面源码可以看出来
  2. setFactory 和 setFactory2 默认只能设置其中一个,设置多个会抛出异常
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
public void setFactory(Factory 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");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = factory;
    } else {
        mFactory = new FactoryMerger(factory, null, mFactory, mFactory2);
    }
}
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");
    }
    mFactorySet = true;
    if (mFactory == null) {
        mFactory = mFactory2 = factory;
    } else {
        mFactory = mFactory2 = new FactoryMerger(factory, factory, mFactory, mFactory2);
    }
}
  1. setFactory 要在 onCreate 之前调用
  2. AppCompatActivity 默认设置一个 Factory2,如果我们设置 Factory2,会提示
1
The Activity's LayoutInflater already has a Factory installed so we can not install AppCompat's

AppCompatActivity 的 Factory2

而我们的 App 一般都是继承自 AppCompatActivity,而 AppCompatActivity 给我们默认设置了 Factory2,这个实现就是 AppCompatDelegateImpl。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
// AppCompatActivity
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
    final AppCompatDelegate delegate = getDelegate();
    delegate.installViewFactory();
    delegate.onCreate(savedInstanceState);
    super.onCreate(savedInstanceState);
}

// AppCompatDelegateImpl实现了LayoutInflater.Factory2接口
@Override
public void installViewFactory() {
    LayoutInflater layoutInflater = LayoutInflater.from(mContext);
    if (layoutInflater.getFactory() == null) {
        LayoutInflaterCompat.setFactory2(layoutInflater, this); // 设置setFactory2为AppCompatDelegateImpl实例
    } else {
        if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImpl)) {
            Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
                    + " so we can not install AppCompat's");
        }
    }
}

现在看看 AppCompatDelegateImpl 实现接口 Factory2 的 onCreateView 方法

1
2
3
4
5
6
7
8
9
// AppCompatDelegateImpl
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
    return createView(parent, name, context, attrs);
}
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
    return onCreateView(null, name, context, attrs);
}

最后都调用了:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
@Override
public View createView(View parent, final String name, @NonNull Context context,
        @NonNull AttributeSet attrs) {
    if (mAppCompatViewInflater == null) {
        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);
        String viewInflaterClassName =
                a.getString(R.styleable.AppCompatTheme_viewInflaterClass); // 从主题设置中找viewInflaterClass的配置
        if (viewInflaterClassName == null) {
            // Set to null (the default in all AppCompat themes). Create the base inflater
            // (no reflection) 
            mAppCompatViewInflater = new AppCompatViewInflater(); // 如果主题中没有设置,那么创建默认值
        } else {
            try {
                Class<?> viewInflaterClass = Class.forName(viewInflaterClassName);
                mAppCompatViewInflater =
                        (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()
                                .newInstance(); // 反射创建AppCompatViewInflater对象
            } catch (Throwable t) {
                Log.i(TAG, "Failed to instantiate custom view inflater "
                        + viewInflaterClassName + ". Falling back to default.", t);
                mAppCompatViewInflater = new AppCompatViewInflater();
            }
        }
    }

    boolean inheritContext = false;
    if (IS_PRE_LOLLIPOP) {
        inheritContext = (attrs instanceof XmlPullParser)
                // If we have a XmlPullParser, we can detect where we are in the layout
                ? ((XmlPullParser) attrs).getDepth() > 1
                // Otherwise we have to use the old heuristic
                : shouldInheritContext((ViewParent) parent);
    }

    return mAppCompatViewInflater.createView(parent, name, context, attrs, inheritContext,
            IS_PRE_LOLLIPOP, /* Only read android:theme pre-L (L+ handles this anyway) */
            true, /* Read read app:theme as a fallback at all times for legacy reasons */
            VectorEnabledTintResources.shouldBeUsed() /* Only tint wrap the context if enabled */
    );
}

AppCompatActivity 的 Factory2 总结

  • 首先 AppCompatActivity 将自己的生命周期委托给 AppCompatDelegate
  • AppCompatDelegate 的实现类 AppCompatDelegateImpl 实现 LayoutInfaler.Factory2 接口
  • 给当前 context 的 LayoutInfalter 设定 Factory2 工厂
  • LayoutInfalter.infalte 方法调用 Factory2.onCreateView
  • Factory2.onCreateView 间接调用 AppCompatViewInflater.createView
  • AppCompatViewInflater 对象来自 AppCompatTheme 的 viewInflaterClass 属性
  • AppCompatViewInflater.createView 方法会通过 name 对控件进行创建

LayoutInflater.Factory/Factory2 应用

  1. 使用自定义 View 替换系统中的 View
  2. 高效的引入外部字体的完成 app 中统一所有字体的
  3. 动态换肤 https://blog.csdn.net/lmj623565791/article/details/51503977
  4. 无需编写 shape、selector,直接在 xml 设置值 无需自定义View,彻底解放shape,selector吧
  5. View 拦截操作
  6. View inflate 耗时统计

view inflate 耗时(只能统计 view 创建的时间,不能统计解析 xml 的时间)

  1. 继承自 AppCompatActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
//MainActivity extends AppCompatActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    //Hook 每一个控件加载耗时
    LayoutInflaterCompat.setFactory2(getLayoutInflater(), new LayoutInflater.Factory2() {
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            //①
            long startTime = System.currentTimeMillis();
            //②
            View view = getDelegate().createView(parent, name, context, attrs);
            //③
            long cost = System.currentTimeMillis() - startTime;
            Log.d(TAG, "加载控件:" + name + "耗时:" + cost);
            return view;
        }
        @Override
        public View onCreateView(String name, Context context, AttributeSet attrs) {
            return null;
        }
    });
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
}
  1. 继承自 FragmentActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
//MainActivity extends FragmentActivity
@Override
protected void onCreate(Bundle savedInstanceState) {
    LayoutInflaterCompat.setFactory(getLayoutInflater(), new LayoutInflaterFactory() {
        @Override
        public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
            long startTime = System.currentTimeMillis();
            View view = null;
            try {
                view = getLayoutInflater().createView(name, null, attrs);
            } catch (ClassNotFoundException e) {
                e.printStackTrace();
            }
            long cost = System.currentTimeMillis() - startTime;
            map.put(parent,new Hodler(parent,view,name,cost));
            Log.d("=========", "加载布局:" + name + "耗时:" + cost);
            return view;
        }
    });
    super.onCreate(savedInstanceState);
}

相关库

https://github.com/chrisjenx/Calligraphy

https://github.com/JcMinarro/Philology

ViewPump

AppCompatViewInflater

可用于减少反射创建 View

默认 AppCompatViewInflater

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
// AppCompatDelegateImpl androidx.appcompat v1.6.1
class AppCompatDelegateImpl extends AppCompatDelegate
        implements MenuBuilder.Callback, LayoutInflater.Factory2 {
	@Override  
	public View createView(View parent, final String name, @NonNull Context context,  
	        @NonNull AttributeSet attrs) {  
	    if (mAppCompatViewInflater == null) {  
	        TypedArray a = mContext.obtainStyledAttributes(R.styleable.AppCompatTheme);  
	        String viewInflaterClassName =  
	                a.getString(R.styleable.AppCompatTheme_viewInflaterClass);  
	        if (viewInflaterClassName == null) {  
	            // Set to null (the default in all AppCompat themes). Create the base inflater  
	            // (no reflection)            mAppCompatViewInflater = new AppCompatViewInflater();  
	        } else {  
	            try {  
	                Class<?> viewInflaterClass =  
	                        mContext.getClassLoader().loadClass(viewInflaterClassName);  
	                mAppCompatViewInflater =  
	                        (AppCompatViewInflater) viewInflaterClass.getDeclaredConstructor()  
	                                .newInstance();  
	            } catch (Throwable t) {  
	                Log.i(TAG, "Failed to instantiate custom view inflater "  
	                        + viewInflaterClassName + ". Falling back to default.", t);  
	                mAppCompatViewInflater = new AppCompatViewInflater();  
	            }  
	        }  
	    }
	}
}
  • AppCompatDelegateImpl 继承自 AppCompatDelegate 并实现了 LayoutInflater.Factory2 接口
  • onCreateView 方法首先会获取 R.styleable.AppCompatTheme 并从中获取 R.styleable.AppCompatTheme_viewInflaterClass,并通过获取到的 viewInflaterClass 来进行对 mAppCompatViewInflater 的赋值操作,如果 viewInflaterClass 不为空或者等于 AppCompatViewInflater 的类名那么直接进行对 AppCompatViewInflater 的创建否则通过反射进行创建,发生异常的情况下也会直接进行对 AppCompatViewInflater 的创建。

AppCompatViewInflater 创建 view

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
public class AppCompatViewInflater {
	private static final String[] sClassPrefixList = {  
        "android.widget.",  
        "android.view.",  
        "android.webkit."  
	};
	@Nullable  
	public final View createView(@Nullable View parent, @NonNull final String name,  
        @NonNull Context context,  
        @NonNull AttributeSet attrs, boolean inheritContext,  
        boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
        View view = null;  
  
		// We need to 'inject' our tint aware Views in place of the standard framework versions  
		switch (name) {  
		    case "TextView":  
		        view = createTextView(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "ImageView":  
		        view = createImageView(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "Button":  
		        view = createButton(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "EditText":  
		        view = createEditText(context, attrs);  
		        verifyNotNull(view, name);  
		        break;   
			 case "Spinner":  
		        view = createSpinner(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "ImageButton":  
		        view = createImageButton(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "CheckBox":  
		        view = createCheckBox(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "RadioButton":  
		        view = createRadioButton(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "CheckedTextView":  
		        view = createCheckedTextView(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "AutoCompleteTextView":  
		        view = createAutoCompleteTextView(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "MultiAutoCompleteTextView":  
		        view = createMultiAutoCompleteTextView(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "RatingBar":  
		        view = createRatingBar(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "SeekBar":  
		        view = createSeekBar(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			case "ToggleButton":  
		        view = createToggleButton(context, attrs);  
		        verifyNotNull(view, name);  
		        break;    
			default:        
		        view = createView(context, name, attrs);  
		}  
		  
		if (view == null && originalContext != context) {  
		    // If the original context does not equal our themed context, then we need to manually  
		    // inflate it using the name so that android:theme takes effect.    view = createViewFromTag(context, name, attrs);  
		}
	}
}

AppCompatViewInflater.createView 方法中对 atgName 为 :

  • TextView
  • ImageView
  • Button
  • EditText
  • Spinner
  • ImageButton
  • CheckBox
  • RadioButton
  • CheckedTextView
  • AutoCompleteTextView
  • MultiAutoCompleteTextView
  • RatingBar
  • SeekBar
  • ToggleButton 这 14 种控件进行创建替换,并且还提供了一个 createView(Context context, String name, AttributeSet attrs) 方法支持开发者自定义扩展。

自定义 AppCompatViewInflater

  • 自定义主题,复写 viewInflaterClass 属性
1
2
3
<style name="FactoryTheme" parent="Theme.AppCompat.Light.DarkActionBar"> 
	<item name="viewInflaterClass">com.xxx.CustomViewInflater</item> 
</style>
  • 继承类 AppCompatViewInflater,实现自定义逻辑
1
2
3
4
class CustomViewInflater : AppCompatViewInflater() {
  override fun createTextView(context: Context, attrs: AttributeSet) =
    RedTextView(context, attrs)
}
  • 作用 将自定义的 View 的默认反射创建,改成 new

注意

  • 只对上面 14 种 View,不带路径的
  • 如果带了全路径的,不会转换,如 android.widget.TextView
本文由作者按照 CC BY 4.0 进行授权