自定义注解,打造自己的框架-下下下篇

该系列介绍自定义注解,完成如下功能。

  • @BindView 代替 findViewById
  • @ClickResponder 代替 setOnClickListener
  • @LongClickResponder 代替 setOnLongClickListener
  • @IntentValue 代替 getIntent().getXXX
  • @UriValue 代替 getQueryParameter
  • @BroadcastResponder 代替 registerReceiver
  • @RouterModule、@RouterPath 来进行反依赖传递调用
    该系列源码在https://github.com/huangyuanlove/AndroidAnnotation
    前几篇介绍了@BindView@ClickResponder@LongClickResponder@IntentValue@UriValue的实现,这一篇介绍一下BroadcastResponder 的实现。

其实本来不想写这个的,因为有太多的选择可以在一定程度上代替广播接收器(接收非系统广播,也就是跨页面传值),比如EventBus,RxBus等。
想想项目中可能会用到,就写一写吧。

前提

广播分为本地广播和全局广播,本地广播使用LocalBroadcastManager.getInstance(Context context).sendBroadcast()来发送,全局广播使用Context.sendBroadcast()发送。

同样在注册广播接收器时,本地广播使用LocalBroadcastManager.getInstance(Context context).registerReceiver来注册,全局广播使用Context.registerReceiver()来注册。

需要注意的是,需要再适当的时机,解注册广播接收器,所以我们生成的辅助代码需要保存一下接收器。以便使用者可以去解注册。

声明注解

1
2
3
4
5
6
7
8
9
10
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface BroadcastResponder {

int LOCAL_BROADCAST = 1;
int GLOBAL_BROADCAST = 2;

String[] action();
int type() default LOCAL_BROADCAST;
}

这里需要区分一下注册的是哪种广播,默认是本地广播。

处理注解逻辑

我们还是使用上一篇中的注解处理器,需要添加属性

1
private Map<TypeElement, List<Element>> broadCastResponderMap = new HashMap<>();

添加一下注解支持

1
set.add(BroadcastResponder.class.getCanonicalName());

同样在process中处理注解逻辑

1
2
3
broadCastResponderMap.clear();
Set<? extends Element> broadCastResponderSet = roundEnvironment.getElementsAnnotatedWith(BroadcastResponder.class);
collectBroadCastResponderMapInfo(broadCastResponderSet);
1
2
3
4
5
6
7
8
9
10
11
private void collectBroadCastResponderMapInfo(Set<? extends Element> elements) {
for (Element element : elements) {
TypeElement typeElement = (TypeElement) element.getEnclosingElement();
List<Element> elementList = broadCastResponderMap.get(typeElement);
if (elementList == null) {
elementList = new ArrayList<>();
broadCastResponderMap.put(typeElement, elementList);
}
elementList.add(element);
}
}

generateCode()方法中调用generateBroadcastResponderCode();

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
98
private void generateBroadcastResponderCode() {
for (TypeElement typeElement : broadCastResponderMap.keySet()) {

MethodSpec.Builder methodBuilder = generateRegisterReceiverMethodBuilder(typeElement);


ClassName broadcastReceiverClassName = ClassName.bestGuess("android.content.BroadcastReceiver");
ClassName contextClassName = ClassName.bestGuess("android.content.Context");
ClassName intentClassName = ClassName.bestGuess("android.content.Intent");
ClassName localBroadcastManagerClassName = ClassName.bestGuess("androidx.localbroadcastmanager.content.LocalBroadcastManager");


List<Element> elements = broadCastResponderMap.get(typeElement);

HashMap<String, String> localBroadCast = new HashMap<>();
HashMap<String, String> globalBroadCast = new HashMap<>();

for (Element element : elements) {
ExecutableElement executableElement = (ExecutableElement) element;
BroadcastResponder broadcastResponder = executableElement.getAnnotation(BroadcastResponder.class);
int type = broadcastResponder.type();
String[] actions = broadcastResponder.action();
for (String action : actions) {

if (BroadcastResponder.LOCAL_BROADCAST == type) {
methodBuilder.addStatement("localBroadcastFilter.addAction(($S))", action);
localBroadCast.put(action, element.getSimpleName().toString());


} else if (BroadcastResponder.GLOBAL_BROADCAST == type) {
globalBroadCast.put(action, element.getSimpleName().toString());
methodBuilder.addStatement("globalBroadcastFilter.addAction(($S))", action);
}

}


}


if (globalBroadCast.size() > 0) {
CodeBlock.Builder caseBlockBuilder = CodeBlock.builder().beginControlFlow("switch (intent.getAction())");
for (Map.Entry<String, String> entry : globalBroadCast.entrySet()) {
caseBlockBuilder.add("case $S:\n", entry.getKey())
.addStatement("target.$L(context,intent)", entry.getValue())
.addStatement("break");
}


caseBlockBuilder.endControlFlow();
MethodSpec broadcastReceiverMethod = MethodSpec.methodBuilder("onReceive")
.addModifiers(Modifier.PUBLIC)
.addParameter(contextClassName, "context")
.addParameter(intentClassName, "intent")
.addCode(caseBlockBuilder.build())
.returns(void.class)
.build();
TypeSpec innerTypeSpec = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(broadcastReceiverClassName)
.addMethod(broadcastReceiverMethod)
.build();
methodBuilder.addStatement("$T globalBroadcastReceiver = $L", broadcastReceiverClassName, innerTypeSpec);
methodBuilder.addStatement("target.registerReceiver(globalBroadcastReceiver,globalBroadcastFilter)");
methodBuilder.addStatement("hashMap.put($L,globalBroadcastReceiver)", BroadcastResponder.GLOBAL_BROADCAST);
}


if (localBroadCast.size() > 0) {
CodeBlock.Builder caseBlockBuilder = CodeBlock.builder().beginControlFlow("switch (intent.getAction())");
for (Map.Entry<String, String> entry : localBroadCast.entrySet()) {
caseBlockBuilder.add("case $S:\n", entry.getKey())
.addStatement("target.$L(context,intent)", entry.getValue())
.addStatement("break");
}


caseBlockBuilder.endControlFlow();


MethodSpec broadcastReceiverMethod = MethodSpec.methodBuilder("onReceive")
.addModifiers(Modifier.PUBLIC)
.addParameter(contextClassName, "context")
.addParameter(intentClassName, "intent")
.addCode(caseBlockBuilder.build())
.returns(void.class)
.build();
TypeSpec innerTypeSpec = TypeSpec.anonymousClassBuilder("")
.addSuperinterface(broadcastReceiverClassName)
.addMethod(broadcastReceiverMethod)
.build();
methodBuilder.addStatement("$T localBroadcastReceiver = $L", broadcastReceiverClassName, innerTypeSpec);
methodBuilder.addStatement("$T.getInstance(target).registerReceiver(localBroadcastReceiver,localBroadcastFilter)", localBroadcastManagerClassName);
methodBuilder.addStatement("hashMap.put($L,localBroadcastReceiver)", BroadcastResponder.LOCAL_BROADCAST);
}

methodBuilder.addStatement("return hashMap");
}
}
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
private MethodSpec.Builder generateRegisterReceiverMethodBuilder(TypeElement typeElement) {

TypeSpecWrapper typeSpecWrapper = generateTypeSpecWrapper(typeElement);
MethodSpec.Builder methodBuilder = typeSpecWrapper.getMethodBuilder("registerReceiver");
ClassName intentFilterClassName = ClassName.bestGuess("android.content.IntentFilter");
ClassName broadcastReceiverClassName = ClassName.bestGuess("android.content.BroadcastReceiver");


if (methodBuilder == null) {


TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(HashMap.class),
ClassName.get(Integer.class),
broadcastReceiverClassName
);


methodBuilder = MethodSpec.methodBuilder("registerReceiver")
.addParameter(ClassName.get(typeElement.asType()), "target")
.addModifiers(Modifier.PUBLIC, Modifier.STATIC)
.addStatement("HashMap<Integer,BroadcastReceiver> hashMap = new HashMap<>()")

.addStatement("$T localBroadcastFilter = new $T()", intentFilterClassName, intentFilterClassName)
.addStatement("$T globalBroadcastFilter = new $T()", intentFilterClassName, intentFilterClassName)
.returns(methodReturns);
typeSpecWrapper.putMethodBuilder(methodBuilder);
}
return methodBuilder;
}

给使用者提供调用方法

还是在api模块中的ViewInjector类中添加如下方法

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
static final Map<Class<?>, Method> BROADCAST_MAP = new LinkedHashMap<>();
public static HashMap<Integer, BroadcastReceiver> registerReceiver(Activity activity) {
try {
Method method = findRegisterReceiverMethodForClass(activity.getClass());
return (HashMap<Integer,BroadcastReceiver>)method.invoke(null,activity);
}catch (Exception e){
e.printStackTrace();
}
return null;
}

private static Method findRegisterReceiverMethodForClass(Class<?> cls) {
Method registerReceiver = BROADCAST_MAP.get(cls);
if (registerReceiver == null) {
try {
Class<?> bindingClass = Class.forName(cls.getName() + "$ViewInjector");
registerReceiver= bindingClass.getDeclaredMethod("registerReceiver",cls);
BROADCAST_MAP.put(cls, registerReceiver);
} catch (Exception e) {
e.printStackTrace();
}
}
return registerReceiver;
}

使用

首先,在需要注册广播接收器的类中注册一下

1
HashMap<Integer, BroadcastReceiver> broadcastReceiverHashMap = ViewInjector.registerReceiver(this);

使用@BroadcastResponder注解处理广播的方法

1
2
3
4
@BroadcastResponder(action = {"com.huangyuanblog","com.huangyuanblog.www"})
public void onReceiveBroadcast(Context context, Intent intent){
Toast.makeText(context,intent.getAction(),Toast.LENGTH_SHORT).show();
}

最后在合适的时机(onDestroy或者onStop)中解注册

1
2
3
4
5
6
7
8
9
10
11
12
@Override
protected void onDestroy() {
super.onDestroy();
if(broadcastReceiverHashMap!=null){
if(broadcastReceiverHashMap.get(BroadcastResponder.GLOBAL_BROADCAST) !=null){
unregisterReceiver(broadcastReceiverHashMap.get(BroadcastResponder.GLOBAL_BROADCAST));
}
if(broadcastReceiverHashMap.get(BroadcastResponder.LOCAL_BROADCAST) !=null){
LocalBroadcastManager.getInstance(this).unregisterReceiver(broadcastReceiverHashMap.get(BroadcastResponder.LOCAL_BROADCAST));
}
}
}

写这种自定义注解难度并不大,麻烦的地方在于需要考虑好给调用者提供一个什么样的接口,然后怎么实现注解的功能(需要生成什么样的辅助代码)


以上


自定义注解,打造自己的框架-下下下篇
https://blog.huangyuanlove.com/2019/12/10/自定义注解,打造自己的框架-下下下篇/
作者
HuangYuan_xuan
发布于
2019年12月10日
许可协议
BY HUANG兄