2019-12-10 23:27:06
该系列介绍自定义注解,完成如下功能。
- @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()
来注册。
需要注意的是,需要再适当的时机,解注册广播接收器,所以我们生成的辅助代码需要保存一下接收器。以便使用者可以去解注册。
声明注解
@Target({ElementType.METHOD})
@Retention(RetentionPolicy.CLASS)
public @interface BroadcastResponder {
int LOCAL_BROADCAST = 1;
int GLOBAL_BROADCAST = 2;
String[] action();
int type() default LOCAL_BROADCAST;
}
这里需要区分一下注册的是哪种广播,默认是本地广播。
处理注解逻辑
我们还是使用上一篇中的注解处理器,需要添加属性
private Map<TypeElement, List<Element>> broadCastResponderMap = new HashMap<>();
添加一下注解支持
set.add(BroadcastResponder.class.getCanonicalName());
同样在process
中处理注解逻辑
broadCastResponderMap.clear();
Set<? extends Element> broadCastResponderSet = roundEnvironment.getElementsAnnotatedWith(BroadcastResponder.class);
collectBroadCastResponderMapInfo(broadCastResponderSet);
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();
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");
}
}
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
类中添加如下方法
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;
}
使用
首先,在需要注册广播接收器的类中注册一下
HashMap<Integer, BroadcastReceiver> broadcastReceiverHashMap = ViewInjector.registerReceiver(this);
使用@BroadcastResponder
注解处理广播的方法
@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)中解注册
@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));
}
}
}
写这种自定义注解难度并不大,麻烦的地方在于需要考虑好给调用者提供一个什么样的接口,然后怎么实现注解的功能(需要生成什么样的辅助代码)
以上