Skip to content
Permalink
Branch: master
Find file Copy path
Find file Copy path
Fetching contributors…
Cannot retrieve contributors at this time
289 lines (221 sloc) 10.7 KB
title tags date keywords
自定义注解,打造自己的框架-下下下篇
Android
2019-12-10 15:27:06 -0800
自定义注解
Annotation
AutoService
AbstractProcessor
反射
javapoet

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

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

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


以上

You can’t perform that action at this time.