看完这篇无需写shape原理再不懂找我

引言

在日常开发中,我们经常会碰到这样一个问题:在需要实现一些圆角或者多个状态视图切换等功能时,一般的做法都是将之前写的shape或者selector文件复制一份再修改。这样就导致类似重复的xml文件越来越多,无形中增大了apk包的大小。其实这也没什么,关键不能忍的是每次还都要为起一个新名字想半天…想想真的是繁琐之极!那么有没有一种什么办法来解决呢?

当然是有的。有大佬已经写了个开源库,可以供大家使用。本篇文章是对该库的原理全解。

原理

想要搞懂该库,首先得要搞懂LayoutInflater的内部接口FactoryFactory2。(本篇源码解析基于API26)

Factory知识

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
public interface Factory {
/**
* Hook you can supply that is called when inflating from a LayoutInflater.
* You can use this to customize the tag names available in your XML
* layout files.
*
* <p>
* Note that it is good practice to prefix these custom names with your
* package (i.e., com.coolcompany.apps) to avoid conflicts with system
* names.
*
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
public View onCreateView(String name, Context context, AttributeSet attrs);
}

从注释中,就能看出这是一个钩子函数,可以通过该函数获取布局中的View名称及属性,从而对View做一些定制操作。

再来看看Factory2是个什么东东。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
public interface Factory2 extends Factory {
/**
* Version of {@link #onCreateView(String, Context, AttributeSet)}
* that also supplies the parent that the view created view will be
* placed in.
*
* @param parent The parent that the created view will be placed
* in; <em>note that this may be null</em>.
* @param name Tag name to be inflated.
* @param context The context the view is being created in.
* @param attrs Inflation attributes as specified in XML file.
*
* @return View Newly created view. Return null for the default
* behavior.
*/
public View onCreateView(View parent, String name, Context context, AttributeSet attrs);
}

Factory2是从API11之后引入到,从上面可以看出Factory2继承自Factory,并且声明了一个重载函数,参数中多了一个当前控件的父控件。

下面来看下LayoutInflater.Factory是如何被调用的。

setContentView流程

首先,从我们最常见的ActivitysetContentView开始。

1
2
3
4
public void setContentView(@LayoutRes int layoutResID) {
getWindow().setContentView(layoutResID);
initWindowDecorActionBar();
}

调用到WindowsetContentView方法,Window是个抽象类,所以这里是到它的实现类PhoneWindow中看。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
@Override
public void setContentView(int layoutResID) {
//mContentParent为空时,创建decorview
if (mContentParent == null) {
installDecor();
} else if (!hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
mContentParent.removeAllViews();
}
if (hasFeature(FEATURE_CONTENT_TRANSITIONS)) {
final Scene newScene = Scene.getSceneForLayout(mContentParent, layoutResID,
getContext());
transitionTo(newScene);
} else {
//关键 使用LayoutInflater将我们的传入的layoutId解析成View 并添加到mContentParent中
mLayoutInflater.inflate(layoutResID, mContentParent);
}
mContentParent.requestApplyInsets();
final Callback cb = getCallback();
if (cb != null && !isDestroyed()) {
cb.onContentChanged();
}
mContentParentExplicitlySet = true;
}

再往下看就到了LayoutInflater中的inflate函数

1
2
3
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root) {
return inflate(resource, root, root != null);
}

继续

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
public View inflate(@LayoutRes int resource, @Nullable ViewGroup root, boolean attachToRoot) {
//获取Resources
final Resources res = getContext().getResources();
if (DEBUG) {
Log.d(TAG, "INFLATING from resource: \"" + res.getResourceName(resource) + "\" ("
+ Integer.toHexString(resource) + ")");
}
//将布局文件转换为XmlResourceParser对象
final XmlResourceParser parser = res.getLayout(resource);
try {
return inflate(parser, root, attachToRoot);
} finally {
parser.close();
}
}

继续

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
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
public View inflate(XmlPullParser parser, @Nullable ViewGroup root, boolean attachToRoot) {
synchronized (mConstructorArgs) {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, "inflate");
final Context inflaterContext = mContext;
final AttributeSet attrs = Xml.asAttributeSet(parser);
Context lastContext = (Context) mConstructorArgs[0];
mConstructorArgs[0] = inflaterContext;
View result = root;
try {
// Look for the root node.
int type;
while ((type = parser.next()) != XmlPullParser.START_TAG &&
type != XmlPullParser.END_DOCUMENT) {
// Empty
}
//如果一开始就是END_DOCUMENT,那说明xml文件有问题
if (type != XmlPullParser.START_TAG) {
throw new InflateException(parser.getPositionDescription()
+ ": No start tag found!");
}
//根节点
final String name = parser.getName();
if (DEBUG) {
System.out.println("**************************");
System.out.println("Creating root view: "
+ name);
System.out.println("**************************");
}
//如果根标签是merge,则 root 不能为空, attachToRoot必须是 true。
if (TAG_MERGE.equals(name)) {
if (root == null || !attachToRoot) {
throw new InflateException("<merge /> can be used only with a valid "
+ "ViewGroup root and attachToRoot=true");
}
//递归inflate方法调运
rInflate(parser, root, inflaterContext, attrs, false);
} else {
//已经排除了merge标签,所以这里的根节点一定是View
// Temp is the root view that was found in the xml
//关键点
final View temp = createViewFromTag(root, name, inflaterContext, attrs);
ViewGroup.LayoutParams params = null;
if (root != null) {
if (DEBUG) {
System.out.println("Creating params from root: " +
root);
}
//根据root生成合适的LayoutParams实例
// Create layout params that match root, if supplied
params = root.generateLayoutParams(attrs);
if (!attachToRoot) {
// Set the layout params for temp if we are not
// attaching. (If we are, we use addView, below)
//如果attachToRoot是false就调用view的setLayoutParams方法
temp.setLayoutParams(params);
}
}
if (DEBUG) {
System.out.println("-----> start inflating children");
}
//递归inflate剩下的children
// Inflate all children under temp against its context.
rInflateChildren(parser, temp, attrs, true);
if (DEBUG) {
System.out.println("-----> done inflating children");
}
// We are supposed to attach all the views we found (int temp)
// to root. Do that now.
if (root != null && attachToRoot) {
//root非空且attachToRoot=true则将xml文件的root view加到形参提供的root里
root.addView(temp, params);
}
// Decide whether to return the root that was passed in or the
// top view found in xml.
if (root == null || !attachToRoot) {
//返回xml里解析的root view
result = temp;
}
}
} catch (XmlPullParserException e) {
final InflateException ie = new InflateException(e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(parser.getPositionDescription()
+ ": " + e.getMessage(), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
// Don't retain static reference on context.
mConstructorArgs[0] = lastContext;
mConstructorArgs[1] = null;
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
return result;
}
}

这段代码比较长,首先看如果根节点是merge标签,会调用rInflate函数

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
void rInflate(XmlPullParser parser, View parent, Context context,
AttributeSet attrs, boolean finishInflate) throws XmlPullParserException, IOException {
final int depth = parser.getDepth();
int type;
boolean pendingRequestFocus = false;
//标准的pull解析器解析步骤
while (((type = parser.next()) != XmlPullParser.END_TAG ||
parser.getDepth() > depth) && type != XmlPullParser.END_DOCUMENT) {
//从开始节点开始解析
if (type != XmlPullParser.START_TAG) {
continue;
}
//获取name
final String name = parser.getName();
//处理REQUEST_FOCUS的标记
if (TAG_REQUEST_FOCUS.equals(name)) {
pendingRequestFocus = true;
consumeChildElements(parser);
} else if (TAG_TAG.equals(name)) {
//处理tag标记
parseViewTag(parser, parent, attrs);
} else if (TAG_INCLUDE.equals(name)) {
//处理include标记
if (parser.getDepth() == 0) {
throw new InflateException("<include /> cannot be the root element");
}
parseInclude(parser, context, parent, attrs);
} else if (TAG_MERGE.equals(name)) {
//merge节点必须是xml文件里的根节点(这里不该再出现merge节点)
throw new InflateException("<merge /> must be the root element");
} else {
//其他节点,那就是各种 View 的标签
final View view = createViewFromTag(parent, name, context, attrs);
final ViewGroup viewGroup = (ViewGroup) parent;
final ViewGroup.LayoutParams params = viewGroup.generateLayoutParams(attrs);
rInflateChildren(parser, view, attrs, true);
viewGroup.addView(view, params);
}
}
if (pendingRequestFocus) {
parent.restoreDefaultFocus();
}
if (finishInflate) {
parent.onFinishInflate();
}
}

从上面的代码可以看出,createViewFromTag是来负责创建View对象!!!

再回过头来来看第二部分,也就i是inflate函数中不是merge标签的情况,也就是根节点是个View

1
final View temp = createViewFromTag(root, name, inflaterContext, attrs);

从这里再次认证了上面的结论:createViewFromTag是创建View对象的最核心函数!!!

1
2
3
private View createViewFromTag(View parent, String name, Context context, AttributeSet attrs) {
return createViewFromTag(parent, name, context, attrs, false);
}

继续

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
View createViewFromTag(View parent, String name, Context context, AttributeSet attrs,
boolean ignoreThemeAttr) {
//解析view标签
if (name.equals("view")) {
name = attrs.getAttributeValue(null, "class");
}
// Apply a theme wrapper, if allowed and one is specified.
if (!ignoreThemeAttr) {
final TypedArray ta = context.obtainStyledAttributes(attrs, ATTRS_THEME);
final int themeResId = ta.getResourceId(0, 0);
if (themeResId != 0) {
context = new ContextThemeWrapper(context, themeResId);
}
ta.recycle();
}
//TAG_1995创建BlinkLayout
if (name.equals(TAG_1995创建BlinkLayout)) {
// Let's party like it's 1995!
return new BlinkLayout(context, attrs);
}
try {
View view;
//关键 终于找到了Factory的用处了
//设置Factory,来对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);
}
//如果此时不存在Factory,不管Factory还是Factory2,还是mPrivateFactory都不存在,
//那么会直接对name直接进行解析
if (view == null) {
final Object lastContext = mConstructorArgs[0];
mConstructorArgs[0] = context;
try {
//如果name中包含.即为自定义View,否则为原生的View控件
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
view = createView(name, null, attrs);
}
} finally {
mConstructorArgs[0] = lastContext;
}
}
return view;
} catch (InflateException e) {
throw e;
} catch (ClassNotFoundException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (Exception e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + name, e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
}
}

从这段代码中,我们找到了Factory的用处,可以看出这里存在FactoryFactory2mPrivateFactory,这些Factory可以通过LayoutInflater来设置。

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
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);
}
}

/**
* @hide for use by framework
*/
public void setPrivateFactory(Factory2 factory) {
if (mPrivateFactory == null) {
mPrivateFactory = factory;
} else {
mPrivateFactory = new FactoryMerger(factory, factory, mPrivateFactory, mPrivateFactory);
}
}

其中mPrivateFactory是为了框架层用的。FactoryFactory2只能设置一次,不然就报IllegalStateException

也就是说,如果设置了Factory,就可以提前通过Factory解析name创建View,如果Factory不存在或者Factory创建的View为空,就会使用默认的解析方式创建。

其实,对于对于android.app.Activity来说,mFactory2mFactorymPrivateFactory这三个对象为 null或者空实现,所以在解析创建View时会使用默认的方式创建。

下面看看默认的解析方式。

1
2
3
4
5
//原生控件的解析方式 根据name中是否有· 如果没有则是原生
protected View onCreateView(View parent, String name, AttributeSet attrs)
throws ClassNotFoundException {
return onCreateView(name, attrs);
}

可以看到是调用了onCreateView,而在LayoutInflater的实现类 PhoneLayoutInflater中覆写了该函数。

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
* @hide
*/
public class PhoneLayoutInflater extends LayoutInflater {
private static final String[] sClassPrefixList = {
"android.widget.",
"android.webkit.",
"android.app."
};

/**
* Instead of instantiating directly, you should retrieve an instance
* through {@link Context#getSystemService}
*
* @param context The Context in which in which to find resources and other
* application-specific things.
*
* @see Context#getSystemService
*/
public PhoneLayoutInflater(Context context) {
super(context);
}

protected PhoneLayoutInflater(LayoutInflater original, Context newContext) {
super(original, newContext);
}

/** Override onCreateView to instantiate names that correspond to the
widgets known to the Widget factory. If we don't find a match,
call through to our super class.
*/
@Override protected View onCreateView(String name, AttributeSet attrs) throws ClassNotFoundException {
//循环遍历sClassPrefixList,调用createView
for (String prefix : sClassPrefixList) {
try {
View view = createView(name, prefix, attrs);
if (view != null) {
return view;
}
} catch (ClassNotFoundException e) {
// In this case we want to let the base class take a crack
// at it.
}
}

return super.onCreateView(name, attrs);
}

public LayoutInflater cloneInContext(Context newContext) {
return new PhoneLayoutInflater(this, newContext);
}
}

可以看出,这里对于系统内置的 View,会依次在 View 的标签前面加上android.widget.android.webkit.,android.app.android.view. 然后通过createView()方法创建 View。

自定义控件的解析方式,也是通过调用createView来创建的。

1
2
3
4
5
6
if (-1 == name.indexOf('.')) {
view = onCreateView(parent, name, attrs);
} else {
//自定义View,因为是全类名,所以这里传null
view = createView(name, null, attrs);
}

下面来看看createView方法

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
82
83
84
85
86
87
88
89
90
91
92
public final View createView(String name, String prefix, AttributeSet attrs)
throws ClassNotFoundException, InflateException {
//从构造器缓存sConstructorMap中去取
Constructor<? extends View> constructor = sConstructorMap.get(name);
//判断取出来的构造器是否安全
if (constructor != null && !verifyClassLoader(constructor)) {
constructor = null;
sConstructorMap.remove(name);
}
Class<? extends View> clazz = null;
try {
Trace.traceBegin(Trace.TRACE_TAG_VIEW, name);
if (constructor == null) {
// Class not found in the cache, see if it's real, and try to add it
//如果构造器不存在 通过前缀+name的方式获取Class对象
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//通过过滤去设置一些不需要加载的对象
if (mFilter != null && clazz != null) {
boolean allowed = mFilter.onLoadClass(clazz);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
}
//获取构造器 并将之缓存到sConstructorMap
constructor = clazz.getConstructor(mConstructorSignature);
constructor.setAccessible(true);
sConstructorMap.put(name, constructor);
} else {
// If we have a filter, apply it to cached constructor
//如果构造器存在并且合法,先判断该class是否需要被过滤掉
if (mFilter != null) {
// Have we seen this name before?
//过滤器也有缓存之前的Class是否被允许加载,判断这个Class的过滤状态
Boolean allowedState = mFilterMap.get(name);
if (allowedState == null) {
//加载Class对象操作
// New class -- remember whether it is allowed
clazz = mContext.getClassLoader().loadClass(
prefix != null ? (prefix + name) : name).asSubclass(View.class);
//判断Class是否可被加载
boolean allowed = clazz != null && mFilter.onLoadClass(clazz);
mFilterMap.put(name, allowed);
if (!allowed) {
failNotAllowed(name, prefix, attrs);
}
} else if (allowedState.equals(Boolean.FALSE)) {
failNotAllowed(name, prefix, attrs);
}
}
}
//反射需要的参数
Object lastContext = mConstructorArgs[0];
if (mConstructorArgs[0] == null) {
// Fill in the context if not already within inflation.
mConstructorArgs[0] = mContext;
}
Object[] args = mConstructorArgs;
args[1] = attrs;
//反射实例化对象 关键!!!
final View view = constructor.newInstance(args);
if (view instanceof ViewStub) {
// Use the same context when inflating ViewStub later.
final ViewStub viewStub = (ViewStub) view;
viewStub.setLayoutInflater(cloneInContext((Context) args[0]));
}
mConstructorArgs[0] = lastContext;
return view;
} catch (NoSuchMethodException e) {
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Error inflating class " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassCastException e) {
// If loaded class is not a View subclass
final InflateException ie = new InflateException(attrs.getPositionDescription()
+ ": Class is not a View " + (prefix != null ? (prefix + name) : name), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} catch (ClassNotFoundException e) {
// If loadClass fails, we should propagate the exception.
throw e;
} catch (Exception e) {
final InflateException ie = new InflateException(
attrs.getPositionDescription() + ": Error inflating class "
+ (clazz == null ? "<unknown>" : clazz.getName()), e);
ie.setStackTrace(EMPTY_STACK_TRACE);
throw ie;
} finally {
Trace.traceEnd(Trace.TRACE_TAG_VIEW);
}
}

从这里可以看出,View默认创建方式是通过反射实现的!

总结:通过梳理了从xmlView的创建流程后,我们发现原来Factory接口是系统给开发者留下了后门,通过它可以自己提前对标签解析,进而做一些额外的操作。那么具体怎么用呢?首先看下AppCompat是如何做到向下兼容的。明白这个后,再看该库就会全部明白了。

AppCompat的向下兼容

平时的开发中,我们的BaseActivity一般都会继承AppCompatActivity,然后你会发现当我们在布局文件中明明声明的是TextView,在运行时却变成了AppCompatTextView

这其实就是用到了Factory实现的。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
//获取AppCompatDelegate代理对象
final AppCompatDelegate delegate = getDelegate();
//调用installViewFactory
delegate.installViewFactory();
delegate.onCreate(savedInstanceState);
if (delegate.applyDayNight() && mThemeId != 0) {
// If DayNight has been applied, we need to re-apply the theme for
// the changes to take effect. On API 23+, we should bypass
// setTheme(), which will no-op if the theme ID is identical to the
// current theme ID.
if (Build.VERSION.SDK_INT >= 23) {
onApplyThemeResource(getTheme(), mThemeId, false);
} else {
setTheme(mThemeId);
}
}
super.onCreate(savedInstanceState);
}

看下getDelegate

1
2
3
4
5
6
7
@NonNull
public AppCompatDelegate getDelegate() {
if (mDelegate == null) {
mDelegate = AppCompatDelegate.create(this, this);
}
return mDelegate;
}

调用AppCompatDelegate的静态方法创建mDelegate对象

往下追

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
public static AppCompatDelegate create(Dialog dialog, AppCompatCallback callback) {
return create(dialog.getContext(), dialog.getWindow(), callback);
}

private static AppCompatDelegate create(Context context, Window window,
AppCompatCallback callback) {
if (Build.VERSION.SDK_INT >= 24) {
return new AppCompatDelegateImplN(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 23) {
return new AppCompatDelegateImplV23(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 14) {
return new AppCompatDelegateImplV14(context, window, callback);
} else if (Build.VERSION.SDK_INT >= 11) {
return new AppCompatDelegateImplV11(context, window, callback);
} else {
return new AppCompatDelegateImplV9(context, window, callback);
}
}

可以看到这里根据不同的API Level创建不同的AppCompatDelegate对象,最低支持到API 9

通过该图,我们发现类似这种 AppCompatDelegateImplVxx 的类,都是高版本的继承低版本的。

接着看下AppCompatDelegate中的installViewFactory()方法,点进去发现是个抽象方法,继续追,看到AppCompatDelegateImplV9中实现了该方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
@Override
public void installViewFactory() {
LayoutInflater layoutInflater = LayoutInflater.from(mContext);
if (layoutInflater.getFactory() == null) {
//AppCompatDelegateImplV9自己实现了Factory2接口
LayoutInflaterCompat.setFactory2(layoutInflater, this);
} else {
//如果已经设置了Factory 则什么也不做!
if (!(layoutInflater.getFactory2() instanceof AppCompatDelegateImplV9)) {
Log.i(TAG, "The Activity's LayoutInflater already has a Factory installed"
+ " so we can not install AppCompat's");
}
}
}

往下追LayoutInflaterCompat.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
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
public static void setFactory2(
@NonNull LayoutInflater inflater, @NonNull LayoutInflater.Factory2 factory) {
IMPL.setFactory2(inflater, factory);
}

static final LayoutInflaterCompatBaseImpl IMPL;
static {
if (Build.VERSION.SDK_INT >= 21) {
IMPL = new LayoutInflaterCompatApi21Impl();
} else {
IMPL = new LayoutInflaterCompatBaseImpl();
}
}

static class LayoutInflaterCompatBaseImpl {
@SuppressWarnings("deprecation")
public void setFactory(LayoutInflater inflater, LayoutInflaterFactory factory) {
final LayoutInflater.Factory2 factory2 = factory != null
? new Factory2Wrapper(factory) : null;
setFactory2(inflater, factory2);
}
public void setFactory2(LayoutInflater inflater, LayoutInflater.Factory2 factory) {
//调用setFactory2
inflater.setFactory2(factory);
final LayoutInflater.Factory f = inflater.getFactory();
if (f instanceof LayoutInflater.Factory2) {
// The merged factory is now set to getFactory(), but not getFactory2() (pre-v21).
// We will now try and force set the merged factory to mFactory2
forceSetFactory2(inflater, (LayoutInflater.Factory2) f);
} else {
// Else, we will force set the original wrapped Factory2
forceSetFactory2(inflater, factory);
}
}
@SuppressWarnings("deprecation")
public LayoutInflaterFactory getFactory(LayoutInflater inflater) {
LayoutInflater.Factory factory = inflater.getFactory();
if (factory instanceof Factory2Wrapper) {
return ((Factory2Wrapper) factory).mDelegateFactory;
}
return null;
}
}

可以看到这里最终调用到了LayoutInflater中的setFactory2方法。也就是说AppCompatDelegateImplV9自己实现了Factory2接口,并设置到了与当前Context相关联的LayoutInflater中,通过前面setContentView流程中,我们知道如果页面设置了Factory,则布局文件中的标签会交由该Factory解析。所以,在解析过程中,一定会走到AppCompatDelegateImplV9onCreateView方法!!!

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
@Override
public final View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
// First let the Activity's Factory try and inflate the view
final View view = callActivityOnCreateView(parent, name, context, attrs);
if (view != null) {
return view;
}
// If the Factory didn't handle it, let our createView() method try
return createView(parent, name, context, attrs);
}

/**
* From {@link LayoutInflater.Factory2}.
*/
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return onCreateView(null, name, context, attrs);
}

这里有两个,我们知道一个是兼容Factory接口的。

继续往下追。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
@Override
public View createView(View parent, final String name, @NonNull Context context,
@NonNull AttributeSet attrs) {
if (mAppCompatViewInflater == null) {
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 */
);
}

再看AppCompatViewInflatercreateView方法

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
public final View createView(View parent, final String name, @NonNull Context c
@NonNull AttributeSet attrs, boolean inheritContext,
boolean readAndroidTheme, boolean readAppTheme, boolean wrapContext) {
final Context originalContext = context;
// We can emulate Lollipop's android:theme attribute propagating down the v
// by using the parent's context
if (inheritContext && parent != null) {
context = parent.getContext();
}
if (readAndroidTheme || readAppTheme) {
// We then apply the theme on the context, if specified
context = themifyContext(context, attrs, readAndroidTheme, readAppTheme
}
if (wrapContext) {
context = TintContextWrapper.wrap(context);
}
View view = null;
// We need to 'inject' our tint aware Views in place of the standard framew
//这里开始完成替换
switch (name) {
case "TextView":
view = new AppCompatTextView(context, attrs);
break;
case "ImageView":
view = new AppCompatImageView(context, attrs);
break;
case "Button":
view = new AppCompatButton(context, attrs);
break;
case "EditText":
view = new AppCompatEditText(context, attrs);
break;
case "Spinner":
view = new AppCompatSpinner(context, attrs);
break;
case "ImageButton":
view = new AppCompatImageButton(context, attrs);
break;
case "CheckBox":
view = new AppCompatCheckBox(context, attrs);
break;
case "RadioButton":
view = new AppCompatRadioButton(context, attrs);
break;
case "CheckedTextView":
view = new AppCompatCheckedTextView(context, attrs);
break;
case "AutoCompleteTextView":
view = new AppCompatAutoCompleteTextView(context, attrs);
break;
case "MultiAutoCompleteTextView":
view = new AppCompatMultiAutoCompleteTextView(context, attrs);
break;
case "RatingBar":
view = new AppCompatRatingBar(context, attrs);
break;
case "SeekBar":
view = new AppCompatSeekBar(context, attrs);
break;
}
if (view == null && originalContext != context) {
// If the original context does not equal our themed context, then we n
// inflate it using the name so that android:theme takes effect.
view = createViewFromTag(context, name, attrs);
}
if (view != null) {
// If we have created a view, check its android:onClick
checkOnClickListener(view, attrs);
}
return view;
}

看到这里,我们才发现原来是在这里将TextView替换成了AppCompatTextView

结论:AppCompat组件实现兼容的原理就是通过Factory2接口实现的。另外,如果我们要自己再次添加额外的操作时,必须要调用到AppCompatDelegatecreateView方法,否则V7库将不能发挥作用!

BackgroundLibrary库解析

前面啰嗦了那么多,终于到正题啦!

先来看下该库的用法,在BaseActivity中的super.onCreate之前调用一行代码就可以实现了。

1
BackgroundLibrary.inject(this);

往下追,看看里面做了什么?

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
public static LayoutInflater inject(Context context) {
//获取LayoutInflater
LayoutInflater inflater;
if (context instanceof Activity) {
inflater = ((Activity) context).getLayoutInflater();
} else {
inflater = LayoutInflater.from(context);
}
//创建BackgroundFactory
BackgroundFactory factory = new BackgroundFactory();
if (context instanceof AppCompatActivity) {
final AppCompatDelegate delegate = ((AppCompatActivity) context).getDelegate();
//适配V7AppCompat组件
factory.setInterceptFactory(new LayoutInflater.Factory() {
@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
return delegate.createView(null, name, context, attrs);
}
});
}
inflater.setFactory2(factory);
return inflater;
}

首先是根据不同的Context类型获取到LayoutInflater,然后创建了BackgroundFactory实例。

看下BackgroundFactory是什么。

1
2
3
4
5
6
7
8
9
10
11
12
public class BackgroundFactory implements LayoutInflater.Factory2 {
private LayoutInflater.Factory mViewCreateFactory;
private LayoutInflater.Factory2 mViewCreateFactory2;

public void setInterceptFactory(LayoutInflater.Factory factory) {
mViewCreateFactory = factory;
}
public void setInterceptFactory2(LayoutInflater.Factory2 factory) {
mViewCreateFactory2 = factory;
}
//...
}

原来是个实现了Factory2接口的类,内部有两个成员变量mViewCreateFactorymViewCreateFactory2,分别对应Factory接口和Factory2接口,并且提供了外部传值的方法。

再回头看上面,下一步判断如果当前传入的ContextAppCompatActivity,就调用BackgroundFactorysetInterceptFactory2方法,并回调AppCompatDelegatecreateView方法,最后将BackgroundFactory设置到了inflater中。

由此可知,原来该库同V7库向下兼容的方法是一致的,都是通过Factory2接口做文章的。另外由前文知道,V7库想要发挥作用最终是通过AppCompatDelegatecreateView方法,并且Factory2接口只能设置一次!所以此处就必须要回调该方法!

看下BackgroundFactory实现的两个方法。

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
82
83
84
85
86
87
88
89
90
91
@Override
public View onCreateView(View parent, String name, Context context, AttributeSet attrs) {
return onCreateView(name, context, attrs);
}

@Override
public View onCreateView(String name, Context context, AttributeSet attrs) {
View view = null;
//回调外部设置的Factory
if (mViewCreateFactory2 != null) {
view = mViewCreateFactory2.onCreateView(name, context, attrs);
if (view == null) {
view = mViewCreateFactory2.onCreateView(null, name, context, attrs);
}
} else if (mViewCreateFactory != null) {
view = mViewCreateFactory.onCreateView(name, context, attrs);
}
//获取自定义的属性
TypedArray typedArray = context.obtainStyledAttributes(attrs, R.styleable.background);
TypedArray pressTa = context.obtainStyledAttributes(attrs, R.styleable.background_press);
TypedArray selectorTa = context.obtainStyledAttributes(attrs, R.styleable.background_selector);
TypedArray textTa = context.obtainStyledAttributes(attrs, R.styleable.text_selector);
TypedArray buttonTa = context.obtainStyledAttributes(attrs, R.styleable.background_button_drawable);
TypedArray otherTa = context.obtainStyledAttributes(attrs, R.styleable.bl_other);
try {
//没有任何自定义属性 返回原值
if (typedArray.getIndexCount() == 0 && selectorTa.getIndexCount() == 0
&& pressTa.getIndexCount() == 0 && textTa.getIndexCount() == 0 && buttonTa.getIndexCount() == 0) {
return view;
}
//适配没有继承AppCompatActivity的Activity
if (view == null) {
//自己创建view
view = createViewFromTag(context, name, attrs);
}
if (view == null) {
return null;
}
GradientDrawable drawable = null;
StateListDrawable stateListDrawable = null;
if(buttonTa.getIndexCount() > 0 && view instanceof CompoundButton){
//点击状态drawable 必须设置可点击
view.setClickable(true);
((CompoundButton) view).setButtonDrawable(DrawableFactory.getButtonDrawable(typedArray, buttonTa));
} else if (selectorTa.getIndexCount() > 0) {
stateListDrawable = DrawableFactory.getSelectorDrawable(typedArray, selectorTa);
view.setClickable(true);
setDrawable(stateListDrawable, view, otherTa);
} else if (pressTa.getIndexCount() > 0) {
drawable = DrawableFactory.getDrawable(typedArray);
stateListDrawable = DrawableFactory.getPressDrawable(drawable, typedArray, pressTa);
view.setClickable(true);
setDrawable(stateListDrawable, view, otherTa);
} else if(typedArray.getIndexCount() > 0){
drawable = DrawableFactory.getDrawable(typedArray);
setDrawable(drawable, view, otherTa);
}
if (view instanceof TextView && textTa.getIndexCount() > 0) {
((TextView) view).setTextColor(DrawableFactory.getTextSelectorColor(textTa));
}
if (typedArray.getBoolean(R.styleable.background_bl_ripple_enable, false) &&
typedArray.hasValue(R.styleable.background_bl_ripple_color)) {
int color = typedArray.getColor(R.styleable.background_bl_ripple_color, 0);
if (android.os.Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
Drawable contentDrawable = (stateListDrawable == null ? drawable : stateListDrawable);
RippleDrawable rippleDrawable = new RippleDrawable(ColorStateList.valueOf(color), contentDrawable, contentDrawable);
view.setClickable(true);
view.setBackground(rippleDrawable);
} else if(stateListDrawable == null){
StateListDrawable tmpDrawable = new StateListDrawable();
GradientDrawable unPressDrawable = DrawableFactory.getDrawable(typedArray);
unPressDrawable.setColor(color);
tmpDrawable.addState(new int[]{-android.R.attr.state_pressed}, drawable);
tmpDrawable.addState(new int[]{android.R.attr.state_pressed}, unPressDrawable);
view.setClickable(true);
setDrawable(tmpDrawable, view, otherTa);
}
}
return view;
} catch (Exception e) {
e.printStackTrace();
} finally {
typedArray.recycle();
pressTa.recycle();
selectorTa.recycle();
textTa.recycle();
buttonTa.recycle();
otherTa.recycle();
}
return view;
}

可以看到Factory2接口声明的方法调用了Factory接口声明的方法,所以最终核心是在

1
2
3
public View onCreateView(String name, Context context, AttributeSet attrs) {

}

该方法中。

首先是回调了一开始设置的mViewCreateFactory接口里的方法,继而会回调到V7库中的方法,让V7库先做向下兼容。

接着获取了一系列自定义的属性,包括shapeselectorripple

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
<declare-styleable name="background">
<attr name="bl_shape" format="enum">
<enum name="rectangle" value="0" />
<enum name="oval" value="1" />
<enum name="line" value="2" />
<enum name="ring" value="3" />
</attr>
<attr name="bl_solid_color" format="color" />
<attr name="bl_corners_radius" format="dimension" />
<attr name="bl_corners_bottomLeftRadius" format="dimension" />
<attr name="bl_corners_bottomRightRadius" format="dimension" />
<attr name="bl_corners_topLeftRadius" format="dimension" />
<attr name="bl_corners_topRightRadius" format="dimension" />
//省略
...

如果没有获取的我们自己声明的属性,就返回由V7创建的View。如果获取到了,再次判断V7创建的View是否为空,如果为空,将会由库自己重新去创建View

看下createViewFromTag方法

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
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;
//没有包名 反射创建view
if (-1 == name.indexOf('.')) {
View view = null;
if ("View".equals(name)) {
view = createView(context, name, "android.view.");
}
if (view == null) {
view = createView(context, name, "android.widget.");
}
if (view == null) {
view = createView(context, name, "android.webkit.");
}
return view;
} else {
return createView(context, name, null);
}
} catch (Exception e) {
Log.w("BackgroundLibrary", "cannot create 【" + name + "】 : ");
return null;
} finally {
mConstructorArgs[0] = null;
mConstructorArgs[1] = null;
}
}

是不是感到很熟悉呢?

没错,这就是前文讲到从xmlView流程中系统默认创建View的源码。由前文所知,在反射创建View之前,首先会交由FactoryFactory2这两个后门去创建。当它们两个没有创建出来时,再交由系统默认去反射创建。

所以,库已经给Context设置了Factory2接口,那么在xml解析的流程中,会先交由库的BackgroundFactory去解析,接着由于要兼容V7,又交由V7库去创建。但V7也只是对上面我们提到的某些需要兼容的View创建,所以必然有些View是会为空的。但我们这时又必须要对View添加额外的属性,所以必须要提提前创建View

再往下看,创建完View后,就开始解析属性,根据属性创建对应的Drawable,这里都是对SDK API的应用,没什么可说的。

到这里,库的最核心原理已经全部剖析完毕!

最后,我们再看下库还提供了使用方法。

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
public static LayoutInflater inject2(Context context) {
LayoutInflater inflater;
if (context instanceof Activity) {
inflater = ((Activity) context).getLayoutInflater();
} else {
inflater = LayoutInflater.from(context);
}
try {
Field field = LayoutInflater.class.getDeclaredField("mFactorySet");
field.setAccessible(true);
field.setBoolean(inflater, false);
BackgroundFactory factory = new BackgroundFactory();
if (inflater.getFactory2() != null) {
factory.setInterceptFactory2(inflater.getFactory2());
} else if (inflater.getFactory() != null) {
factory.setInterceptFactory(inflater.getFactory());
}
inflater.setFactory2(factory);
} catch (NoSuchFieldException e) {
e.printStackTrace();
} catch (IllegalArgumentException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return inflater;
}

可以看到,这里是为了兼容其它根据Factory原理实现某些特定功能的库(如换肤)。由前文所知,Factory只能设置一次,会将mFactorySet设置为true,所以这里通过反射的方式解决。

参考

Android 常用换肤方式以及原理分析

Android应用setContentView与LayoutInflater加载解析机制源码分析

无需自定义View,彻底解放shape,selector吧