利用 APT 在编译期生成微信回调类

要使用微信支付、分享等功能,必须制定在包名下的wxapi创建一个名为 wxapi 的文件,然后在 wxapi 文件下创建一个名为 WXEntryActivity 或者 WXPayEntryActivity 的 Activity 类,并实现 IWXAPIEventHandler 接口。有做个微信相关功能的同志都知道不就是在知道位置创建一个Activity嘛,so easy 啦。

为什么需要使用到 APT 呢?

由于我开发并维护着一个比较特别的项目,这个项目可以用同一份代码根据配置更改,使用不同的资源文件打包出三个类似的 APK,这三个 APK 使用不同的皮肤、不一样的后台 API。说的可能有点抽象,其实就是三个风格一样的应用,皮肤不同,后台数据也不同,自然三个 APK 的包名也不一致。不同的包名导致这个微信回调类需要 APT 动态生成。

何为 APT

是 Annotation Processing Toold 的缩写,就是注解处理器的意思,该处理器可以在编译期在制定位置生成想要的Java类、文档、HTML等。如果不了解注解的话可以到前文了解一番《Java注解知识》,不然可能看不太懂这篇内容。

实现思路

1.最任意一个文件下面创建一个 WXEntryActivity ,实现逻辑跟往常一样。
2.利用 APT 在制定路径下生成一个空白 WXEntryActivity ,该类继承步骤1的 WXEntryActivity ,不需要任何属性与方法。
3.编写注解 @CustomPackage,该注解包含一个包名属性 packageName,将该注解标记到步骤1中创建的 WXEntryActivity ,注解属性赋值为应用包名。
4.编写注解器 CustomPackageProcessor ,利用该注解器在编译期生成步骤2的 WXEntryActivity 。

@Target(ElementType.TYPE)
@Retention(RetentionPolicy.CLASS)
public @interface CustomPackage {
    String packageName() default "com";  //包名
}
@CustomPackage(packageName = BuildConfig.applicationId + ".wxapi")
public class WXEntryActivity extends AppCompatActivity implements IWXAPIEventHandler {
    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        // 通过WXAPIFactory工厂,获取IWXAPI的实例
        IWXAPI api = WXAPIFactory.createWXAPI(this, "WXAppId", false);
        api.handleIntent(getIntent(), this);
    }

    @Override
    public void onReq(BaseReq baseReq) {

    }

    @Override
    public void onResp(BaseResp resp) {
    }
}
@SupportedAnnotationTypes("com.amazing.wechat_annotation.CustomPackage")
public class CustomPackageProcessor extends AbstractProcessor {
    private Elements elementUtils;

    @Override
    public boolean process(Set set, RoundEnvironment roundEnvironment) {
        Set elememts = roundEnvironment.getElementsAnnotatedWith(CustomPackage.class);
        if (elememts != null) {
            for (Element element : elememts) {

                CustomPackage customPackage = element.getAnnotation(CustomPackage.class);
                String packageName = customPackage.packageName();

                //some check
                if (packageName.isEmpty()) {
                    throw new IllegalArgumentException("packageName can't be null or empty");
                }
                TypeElement typeElement;
                if (element instanceof TypeElement) {
                    typeElement = (TypeElement) element;
                } else {
                    typeElement = (TypeElement) element.getEnclosingElement();
                }
                Set modifiers = typeElement.getModifiers();
                if (modifiers.contains(Modifier.FINAL)) {
                    throw new IllegalArgumentException(typeElement.getQualifiedName() + " must be not final class");
                }
                if (!modifiers.contains(Modifier.PUBLIC)) {
                    throw new IllegalArgumentException(typeElement.getQualifiedName() + " must be public class");
                }
                if (typeElement.getKind() == ElementKind.INTERFACE) {
                    throw new IllegalArgumentException(typeElement.getQualifiedName() + " must be class not interface");
                }
                if (typeElement.getSimpleName().equals(typeElement.getQualifiedName())) {
                    throw new IllegalArgumentException(typeElement.getQualifiedName() + " must be have package");
                }
                for (Element field : elementUtils.getAllMembers(typeElement)) {
                    if (field.getModifiers().contains(Modifier.ABSTRACT) && field.getKind() == ElementKind.METHOD) {
                        throw new IllegalArgumentException(typeElement.getQualifiedName() + " must don't have abstract method , but find " + field.toString());
                    }
                }

                //create class which named WXEntryActivity
                TypeSpec WXEntryActivity = TypeSpec.classBuilder(typeElement.getSimpleName().toString())
                        .addModifiers(Modifier.PUBLIC)
                        .superclass(ParameterizedTypeName.get(typeElement.asType())) //set superclass
                        .build();

                //create file
                JavaFile javaFile = JavaFile.builder(packageName, WXEntryActivity)
                        .build();

                try {
                    //write to file
                    javaFile.writeTo(processingEnv.getFiler());
                } catch (IOException e) {
                    e.printStackTrace();
                }


            }
            return true;
        } else {
            return false;
        }

    }

    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        this.elementUtils = processingEnv.getElementUtils();
        this.processingEnv = processingEnv;
    }

}

CustomPackageProcessor 注解器如何在指定位置生成 一个空白 WXEntryActivity 并继承步骤1的 WXEntryActivity ?

1.注册注解器
在 complier 模块下的 main 目录下生成 resource/META-INF/service,并在 service 文件下创建一个 File,内容为注解器的路径加注解器名称,如:

com.amazing.wechat_compiler.CustomPackageProcessor

2.在注解器上添加@SupportedAnnotationTypes注解,指定支持的注解,有点绕的感觉,意思就是这个注解器可以为哪些注解工作的意思

@SupportedAnnotationTypes("com.amazing.wechat_annotation.CustomPackage")
public class CustomPackageProcessor extends AbstractProcessor {

@SupportedAnnotationTypes 接收拜支持注解的全路径
需要注意的是:设置了上面两个步骤之后注解器才能在编译期进行工作

整个项目的目录结构是这样的:
两个 java 模块,分别是 annotation 和 complier,complier 依赖 annotation; 一个Android主模块,该模块的 gradle 配置

dependencies {
     ...
    implementation 'com.tencent.mm.opensdk:wechat-sdk-android-with-mta:5.4.0'
    implementation project(':annotation')
    annotationProcessor project(':compiler')
}

项目的setting.gradle配置

include ':app'
include ':annotation'
include ':compiler'

注解器的核心方法是:

public boolean process(Set set, RoundEnvironment roundEnvironment) {}

编译期在该方法拿到应用中所有 @CustomPackage 注解过的类:

Set elememts = roundEnvironment.getElementsAnnotatedWith(CustomPackage.class);

通过获取到的类提取 @CustomPackage 注解的属性 packageName 作为包名,并通过 javapoet 生成 WXEntryActivity :

//create class which named WXEntryActivity
TypeSpec WXEntryActivity = TypeSpec.classBuilder(typeElement.getSimpleName().toString())
        .addModifiers(Modifier.PUBLIC)
        .superclass(ParameterizedTypeName.get(typeElement.asType())) //set superclass
        .build();
//create file
JavaFile javaFile = JavaFile.builder(packageName, WXEntryActivity)
        .build();
try {
    //write to file
    javaFile.writeTo(processingEnv.getFiler());
} catch (IOException e) {
    e.printStackTrace();
}

javapoet 的使用可以查阅其 github 地址: https://github.com/square/javapoet

你可能感兴趣的:(利用 APT 在编译期生成微信回调类)