暴力突破 Android 编译插桩(六)- APT 实战分析

专栏暴力突破 Android 编译插桩系列

一、前言


经过前四篇的介绍,我们已经对注解比较熟悉了,下面介绍一个实战案例。

二、实战


2.1、背景介绍

在公司项目里,所有埋点里的pageName都是由Activity的context来获取Activity的名称来记录,

当我们需要修改pageName来与ios进行统一时,pageName挨个修改的工作量巨大,因此我们思考如何进行批量处理,或利用apt代码生成辅助。

2.2、代码

1、在project下建一个java module,名称为 count_annotation

@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Count {
    /**
     * BI页面名称
     */
    String biPageName();
}

public class VVICCountReflect {

    public static Map<String, String> mPageNameMap = new HashMap<>();

    private static volatile VVICCountReflect mVVICCountReflect;

    private VVICCountReflect() {
        //app module
        try {
            Class<?> cls = Class.forName("com.lerendan.test.Count$$$app");
            cls.newInstance();
        } catch (Exception e) {
        }
        //module_user module
        try {
            Class<?> cls = Class.forName("com.lerendan.test.Count$$$module_user");
            cls.newInstance();
        } catch (Exception e) {
        }
        //module_shop module
        try {
            Class<?> cls = Class.forName("com.lerendan.test.Count$$$module_shop");
            cls.newInstance();
        } catch (Exception e) {
        }
    }

    public static VVICCountReflect getInstance() {
        if (mVVICCountReflect == null) {
            synchronized (VVICCountReflect.class) {
                if (mVVICCountReflect == null) {
                    mVVICCountReflect = new VVICCountReflect();
                }
            }
        }
        return mVVICCountReflect;
    }

    /**
     * reset page name
     */
    public void setPageName(String key, String pageName) {
        if (key != null && !key.equals("") && pageName != null && !pageName.equals("")) {
            mPageNameMap.put(key, pageName);
        }
    }

    /**
     * get page name
     */
    public String getPageName(String key) {
        if (key != null && !key.equals("")) {
            return mPageNameMap.get(key);
        }
        return null;
    }

}

2、新建一个 java module,名字为 count_compiler

@AutoService(Processor.class)
public class CountProcessor extends AbstractProcessor {

    private Map<String, String> mMap = new HashMap<>();
    private Filer filer;
    private String mModuleName = "";

    @Override
    public synchronized void init(ProcessingEnvironment processingEnv) {
        super.init(processingEnv);
        filer = processingEnv.getFiler();
        Map<String, String> map = processingEnv.getOptions();
        if (map != null && map.size() > 0) {
            mModuleName = map.get("moduleName");
        }
    }

    @Override
    public Set<String> getSupportedAnnotationTypes() {
        HashSet<String> supportTypes = new LinkedHashSet<>();
        supportTypes.add(Count.class.getCanonicalName());
        return supportTypes;
    }

    @Override
    public SourceVersion getSupportedSourceVersion() {
        return SourceVersion.latestSupported();
    }

    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
        collectInfo(roundEnvironment);
        writeToFile();
        return true;
    }

    /**
     * 收集注解信息
     */
    private void collectInfo(RoundEnvironment roundEnvironment) {
        mMap.clear();
        Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(Count.class);
        for (Element element : elements) {
            if (element.getKind() == ElementKind.CLASS) {
                String biPageName = element.getAnnotation(Count.class).biPageName();
                TypeElement typeElement = (TypeElement) element;
                String simpleName = typeElement.getSimpleName().toString();
                mMap.put(simpleName, biPageName);
            }
        }
    }

    /**
     * 通过javapoet生成文件
     */
    private void writeToFile() {
        try {
            MethodSpec.Builder constructor = MethodSpec.constructorBuilder().addModifiers(Modifier.PUBLIC)
                    .addStatement("addPageNameToMap()");

            MethodSpec.Builder methodBuilder = MethodSpec.methodBuilder("addPageNameToMap")
                    .addModifiers(Modifier.PRIVATE)
                    .returns(void.class);
            for (String simpleName : mMap.keySet()) {
                String biPageName = mMap.get(simpleName);
                methodBuilder.addStatement("$T.mPageNameMap.put($S, $S)", VVICCountReflect.class, simpleName, biPageName);
            }

            TypeSpec typeSpec = TypeSpec.classBuilder("Count$$$" + mModuleName).addModifiers(Modifier.PUBLIC)
                    .addMethod(constructor.build())
                    .addMethod(methodBuilder.build())
                    .build();
            JavaFile javaFile = JavaFile.builder("com.weisheng.vvic", typeSpec).build();
            javaFile.writeTo(filer);
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

}

@Count(biPageName = "test")
public class TestActivity extends Activity {
    ...
}

最后,如果项目中只有一个主 app module,则在 app.build 中引入下面两个就可以了

api project(':count_annotation')  
annotationProcessor project(':count_compiler')

如果有多个module,则需要在每个module下添加上述配置,其中 api project(':count_annotation')  可以放在 base module 中,但annotationProcessor project(':count_compiler')  必须在每个 module 中都要添加

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值