Android 自定义注解处理器并生成 json 文件

code小生 一个专注大前端领域的技术平台公众号回复Android加入安卓技术群

作者:12313凯皇
链接:https://www.jianshu.com/p/5e4dcf1cc317
声明:本文已获12313凯皇授权发表,转发等请联系原作者授权

最近在慕课网上学习Navigation相关的时候,一般Navigation的底部导航菜单设置是直接写xml文件,也通过自定义注解动态生成json文件然后去解析定制底部导航栏。由于这些东西都是比较新的东西之前没怎么接触过,所以特地写个demo来记录一下。

这个Demo中以新建项目中的 Bottom Navigation Activity 模板为例,实现三个Fragment实现对应json文件的解析。

初始化环境

  1. app下的build.gradle中统一JDK版本

android {
     ...
 
     compileOptions {
         sourceCompatibility JavaVersion.VERSION_1_8
         targetCompatibility JavaVersion.VERSION_1_8
     }
 }
  1. 创建一个新的moudle名为libmyannotationprocessor,记得选择类型的时候选择Java Library。

  2. 统一JDK环境,将moudle的build.gradle中的sourceCompatibility和targetCompatibility改为8。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])
    ...
}

sourceCompatibility = "8"
targetCompatibility = "8"
  1. build.gradle中引入所需资源 这里用到了fastjson用作json转换处理,auto-service辅助实现注解处理器,最新版本可至git仓库中获取。

dependencies {
    implementation fileTree(dir: 'libs', include: ['*.jar'])

    //fastjson
    implementation 'com.alibaba:fastjson:1.2.59'
    
    //auto service
    implementation 'com.google.auto.service:auto-service:1.0-rc6'
    annotationProcessor 'com.google.auto.service:auto-service:1.0-rc6'
}

自定义注解

先自定义两个注解分别用于给Activity和Fragment标记。

//public @interface FragmentDestination {
@Target(ElementType.TYPE) //标记在类、接口上
public @interface FragmentDestination {

    String pageUrl();  //url

    boolean needLogin() default false;  //是否需要登录 默认false

    boolean asStarter() default false;  //是否是首页  默认false
}
//ActivityDestination
@Target(ElementType.TYPE) //标记在类、接口上
public @interface ActivityDestination {

    String pageUrl();  //url

    boolean needLogin() default false;  //是否需要登录 默认false

    boolean asStarter() default false;  //是否是首页  默认false
}

自定义注解处理器

/**
 * Created by Hy on 2020/01/15 21:27
 * 

* SupportedSourceVersion 源码类型 也可通过{@link #getSupportedSourceVersion()}设置 * SupportedAnnotationTypes 设定需要处理的注解 也可通过{@link #getSupportedAnnotationTypes()}设置 **/ @SuppressWarnings("unused") @AutoService(Processor.class) //auto-service @SupportedSourceVersion(SourceVersion.RELEASE_8) //源码类型 1.8 @SupportedAnnotationTypes({"com.yu.hu.libmyannotationprocessor.annotation.ActivityDestination", "com.yu.hu.libmyannotationprocessor.annotation.FragmentDestination"}) public class NavProcessor extends AbstractProcessor { private static final String OUTPUT_FILE_NAME = "destination.json"; private Messager messager; //使用日志打印 private Filer filer; //用于文件处理 //该方法再编译期间会被注入一个ProcessingEnvironment对象,该对象包含了很多有用的工具类。 @Override public synchronized void init(ProcessingEnvironment processingEnvironment) { super.init(processingEnvironment); //日志打印,在java环境下不能使用android.util.log.e() this.messager = processingEnvironment.getMessager(); //文件处理工具 this.filer = processingEnvironment.getFiler(); } /** * 该方法将一轮一轮的遍历源代码 * * @param set 该方法需要处理的注解类型 * @param roundEnvironment 关于一轮遍历中提供给我们调用的信息. * @return 改轮注解是否处理完成 true 下轮或者其他的注解处理器将不会接收到次类型的注解.用处不大. */ @SuppressWarnings("ResultOfMethodCallIgnored") @Override public boolean process(Set set, RoundEnvironment roundEnvironment) { //通过处理器环境上下文roundEnv分别获取 项目中标记的FragmentDestination.class 和ActivityDestination.class注解。 //此目的就是为了收集项目中哪些类 被注解标记了 Set fragmentElements = roundEnvironment.getElementsAnnotatedWith(FragmentDestination.class); Set activityElements = roundEnvironment.getElementsAnnotatedWith(ActivityDestination.class); if (!fragmentElements.isEmpty() || !activityElements.isEmpty()) { HashMap destMap = new HashMap<>(); //分别 处理FragmentDestination 和 ActivityDestination 注解类型 //并收集到destMap 这个map中。以此就能记录下所有的页面信息了 handleDestination(fragmentElements, FragmentDestination.class, destMap); handleDestination(activityElements, ActivityDestination.class, destMap); // app/src/assets FileOutputStream fos = null; OutputStreamWriter writer = null; try { //filer.createResource()意思是创建源文件 //我们可以指定为class文件输出的地方, //StandardLocation.CLASS_OUTPUT:java文件生成class文件的位置,/app/build/intermediates/javac/debug/classes/目录下 //StandardLocation.SOURCE_OUTPUT:java文件的位置,一般在/ppjoke/app/build/generated/source/apt/目录下 //StandardLocation.CLASS_PATH 和 StandardLocation.SOURCE_PATH用的不多,指的了这个参数,就要指定生成文件的pkg包名了 FileObject resource = filer.createResource(StandardLocation.CLASS_OUTPUT, "", OUTPUT_FILE_NAME); String resourcePath = resource.toUri().getPath(); messager.printMessage(Diagnostic.Kind.NOTE, "resourcePath: " + resourcePath); //由于我们想要把json文件生成在app/src/main/assets/目录下,所以这里可以对字符串做一个截取, //以此便能准确获取项目在每个电脑上的 /app/src/main/assets/的路径 String appPath = resourcePath.substring(0, resourcePath.indexOf("app") + 4); String assetsPath = appPath + "src/main/assets"; File file = new File(assetsPath); if (!file.exists()) { file.mkdir(); } //写入文件 File outputFile = new File(file, OUTPUT_FILE_NAME); if (outputFile.exists()) { outputFile.delete(); } outputFile.createNewFile(); //利用fastjson把收集到的所有的页面信息 转换成JSON格式的。并输出到文件中 String content = JSON.toJSONString(destMap); fos = new FileOutputStream(outputFile); writer = new OutputStreamWriter(fos, StandardCharsets.UTF_8); writer.write(content); writer.flush(); } catch (IOException e) { e.printStackTrace(); } finally { if (writer != null) { try { writer.close(); } catch (IOException e) { e.printStackTrace(); } } if (fos != null) { try { fos.close(); } catch (IOException e) { e.printStackTrace(); } } } } return true; } private void handleDestination(Set elements, Class annotationClass, HashMap destMap) { for (Element element : elements) { //TypeElement是Element的一种。 //如果我们的注解标记在了类名上。所以可以直接强转一下。使用它得到全类名 TypeElement typeElement = (TypeElement) element; //全类名 String className = typeElement.getQualifiedName().toString(); //页面的id.此处不能重复,使用页面的类名做hascode即可 int id = Math.abs(className.hashCode()); //是否需要登录 boolean needLogin = false; //页面的pageUrl相当于隐式跳转意图中的host://schem/path格式 String pageUrl = null; //是否作为首页的第一个展示的页面 boolean asStarter = false; //标记该页面是fragment 还是activity类型的 boolean isFragment = false; Annotation annotation = typeElement.getAnnotation(annotationClass); if (annotation instanceof FragmentDestination) { FragmentDestination dest = (FragmentDestination) annotation; pageUrl = dest.pageUrl(); needLogin = dest.needLogin(); asStarter = dest.asStarter(); isFragment = true; } else if (annotation instanceof ActivityDestination) { ActivityDestination dest = (ActivityDestination) annotation; pageUrl = dest.pageUrl(); needLogin = dest.needLogin(); asStarter = dest.asStarter(); } if (destMap.containsKey(pageUrl)) { messager.printMessage(Diagnostic.Kind.ERROR, "不同的页面不允许使用相同的pageUrl" + className); } else { JSONObject object = new JSONObject(); object.put("id", id); object.put("needLogin", needLogin); object.put("asStarter", asStarter); object.put("pageUrl", pageUrl); object.put("className", className); object.put("isFragment", isFragment); destMap.put(pageUrl, object); } } } /** * 返回我们Java的版本. *

* 也可以通过{@link SupportedSourceVersion}注解来设置 */ @Override public SourceVersion getSupportedSourceVersion() { //return SourceVersion.latest(); return super.getSupportedSourceVersion(); } /** * 返回我们将要处理的注解 *

* 也可通过{@link SupportedAnnotationTypes}注解来设置 */ @Override public Set getSupportedAnnotationTypes() { // Set annotataions = new LinkedHashSet<>(); // annotataions.add(ActivityDestination.class.getCanonicalName()); // annotataions.add(FragmentDestination.class.getCanonicalName()); // return annotataions; return super.getSupportedAnnotationTypes(); } }

使用

  1. 在app的build.gradle中引入自定义注解处理器

dependencies {
     ...
     
     //引入项目已使用注解 如果觉得跟下面的合起来有点怪可以单独再创建一个moudle,把注解的定义放到那里面去
     implementation project(path: ':libmyannotationprocessor')
     //配置注解处理器
     annotationProcessor project(':libmyannotationprocessor')
 
 
 }
  1. 使用自定义注解,给默认生成的三个fragment加上自定义的注解:

@FragmentDestination(pageUrl = "tabs/dashboard")
public class DashboardFragment extends Fragment {
    ...
}

@FragmentDestination(pageUrl = "tabs/home")
public class HomeFragment extends Fragment {
    ...
}

@FragmentDestination(pageUrl = "tabs/notification")
public class NotificationsFragment extends Fragment {
    ...
}
  1. 标记好注解后,重新Make Project即可在app/src/main/assets/生成对应的destination.json文件:

{
    "tabs/dashboard":{
        "isFragment":true,
        "asStarter":false,
        "needLogin":false,
        "pageUrl":"tabs/dashboard",
        "className":"com.yu.hu.annotationprocessortest.ui.dashboard.DashboardFragment",
        "id":1222462875
    },
    "tabs/notification":{
        "isFragment":true,
        "asStarter":false,
        "needLogin":false,
        "pageUrl":"tabs/notification",
        "className":"com.yu.hu.annotationprocessortest.ui.notifications.NotificationsFragment",
        "id":2030977523
    },
    "tabs/home":{
        "isFragment":true,
        "asStarter":false,
        "needLogin":false,
        "pageUrl":"tabs/home",
        "className":"com.yu.hu.annotationprocessortest.ui.home.HomeFragment",
        "id":1050166585
    }
}

参考文章

Java中的注解-自定义注解

Java注解解析-搭建自己的注解处理器(CLASS注解使用篇)

相关阅读

1 Gson 解析服务端返回的多种类型的 JSON
2 为 Retrofit2 提供的 FastJson 转换库
3 探索 Android 内存优化方法
4 LiveData+Retrofit 网络请求实战
5 安卓开发小总结

如果你有写博客的好习惯

欢迎投稿

点个在看,小生感恩❤️

你可能感兴趣的:(Android 自定义注解处理器并生成 json 文件)