Android 自定義注解處理器并生成json文件

最近在慕課網(wǎng)上學(xué)習(xí)Navigation相關(guān)的時(shí)候,一般Navigation的底部導(dǎo)航菜單設(shè)置是直接寫xml文件木张,也通過(guò)自定義注解動(dòng)態(tài)生成json文件然后去解析定制底部導(dǎo)航欄刊驴。由于這些東西都是比較新的東西之前沒(méi)怎么接觸過(guò),所以特地寫個(gè)demo來(lái)記錄一下寸痢。

這個(gè)Demo中以新建項(xiàng)目中的 Bottom Navigation Activity 模板為例赴邻,實(shí)現(xiàn)三個(gè)Fragment實(shí)現(xiàn)對(duì)應(yīng)json文件的解析印衔。

初始化環(huán)境

  1. app下的build.gradle中統(tǒng)一JDK版本
    android {
         ...
     
         compileOptions {
             sourceCompatibility JavaVersion.VERSION_1_8
             targetCompatibility JavaVersion.VERSION_1_8
         }
     }
    
    
  2. 創(chuàng)建一個(gè)新的moudle名為libmyannotationprocessor,記得選擇類型的時(shí)候選擇Java Library姥敛。
  3. 統(tǒng)一JDK環(huán)境奸焙,將moudle的build.gradle中的sourceCompatibilitytargetCompatibility改為8
    dependencies {
        implementation fileTree(dir: 'libs', include: ['*.jar'])
        ...
    }
    
    sourceCompatibility = "8"
    targetCompatibility = "8"
    
  4. build.gradle中引入所需資源
    這里用到了fastjson用作json轉(zhuǎn)換處理徒溪,auto-service輔助實(shí)現(xiàn)注解處理器忿偷,最新版本可至git倉(cāng)庫(kù)中獲取。
    
    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'
    }
    

自定義注解

先自定義兩個(gè)注解分別用于給ActivityFragment標(biāo)記臊泌。

//public @interface FragmentDestination {
@Target(ElementType.TYPE) //標(biāo)記在類鲤桥、接口上
public @interface FragmentDestination {

    String pageUrl();  //url

    boolean needLogin() default false;  //是否需要登錄 默認(rèn)false

    boolean asStarter() default false;  //是否是首頁(yè)  默認(rèn)false
}

//ActivityDestination
@Target(ElementType.TYPE) //標(biāo)記在類、接口上
public @interface ActivityDestination {

    String pageUrl();  //url

    boolean needLogin() default false;  //是否需要登錄 默認(rèn)false

    boolean asStarter() default false;  //是否是首頁(yè)  默認(rèn)false
}

自定義注解處理器

/**
 * Created by Hy on 2020/01/15 21:27
 * <p>
 * SupportedSourceVersion  源碼類型  也可通過(guò){@link #getSupportedSourceVersion()}設(shè)置
 * SupportedAnnotationTypes 設(shè)定需要處理的注解 也可通過(guò){@link #getSupportedAnnotationTypes()}設(shè)置
 **/
@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;  //用于文件處理

    //該方法再編譯期間會(huì)被注入一個(gè)ProcessingEnvironment對(duì)象渠概,該對(duì)象包含了很多有用的工具類茶凳。
    @Override
    public synchronized void init(ProcessingEnvironment processingEnvironment) {
        super.init(processingEnvironment);
        //日志打印,在java環(huán)境下不能使用android.util.log.e()
        this.messager = processingEnvironment.getMessager();
        //文件處理工具
        this.filer = processingEnvironment.getFiler();
    }

    /**
     * 該方法將一輪一輪的遍歷源代碼
     *
     * @param set              該方法需要處理的注解類型
     * @param roundEnvironment 關(guān)于一輪遍歷中提供給我們調(diào)用的信息.
     * @return 改輪注解是否處理完成 true 下輪或者其他的注解處理器將不會(huì)接收到次類型的注解.用處不大.
     */
    @SuppressWarnings("ResultOfMethodCallIgnored")
    @Override
    public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {

        //通過(guò)處理器環(huán)境上下文roundEnv分別獲取 項(xiàng)目中標(biāo)記的FragmentDestination.class 和ActivityDestination.class注解。
        //此目的就是為了收集項(xiàng)目中哪些類 被注解標(biāo)記了
        Set<? extends Element> fragmentElements = roundEnvironment.getElementsAnnotatedWith(FragmentDestination.class);
        Set<? extends Element> activityElements = roundEnvironment.getElementsAnnotatedWith(ActivityDestination.class);

        if (!fragmentElements.isEmpty() || !activityElements.isEmpty()) {
            HashMap<String, JSONObject> destMap = new HashMap<>();
            //分別 處理FragmentDestination  和 ActivityDestination 注解類型
            //并收集到destMap 這個(gè)map中播揪。以此就能記錄下所有的頁(yè)面信息了
            handleDestination(fragmentElements, FragmentDestination.class, destMap);
            handleDestination(activityElements, ActivityDestination.class, destMap);

            // app/src/assets
            FileOutputStream fos = null;
            OutputStreamWriter writer = null;
            try {
                //filer.createResource()意思是創(chuàng)建源文件
                //我們可以指定為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用的不多箱沦,指的了這個(gè)參數(shù),就要指定生成文件的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/目錄下,所以這里可以對(duì)字符串做一個(gè)截取雇庙,
                //以此便能準(zhǔn)確獲取項(xiàng)目在每個(gè)電腦上的 /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把收集到的所有的頁(yè)面信息 轉(zhuǎn)換成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<? extends Element> elements, Class<? extends Annotation> annotationClass, HashMap<String, JSONObject> destMap) {
        for (Element element : elements) {
            //TypeElement是Element的一種。
            //如果我們的注解標(biāo)記在了類名上疆前。所以可以直接強(qiáng)轉(zhuǎn)一下寒跳。使用它得到全類名
            TypeElement typeElement = (TypeElement) element;
            //全類名
            String className = typeElement.getQualifiedName().toString();
            //頁(yè)面的id.此處不能重復(fù),使用頁(yè)面的類名做hascode即可
            int id = Math.abs(className.hashCode());
            //是否需要登錄
            boolean needLogin = false;
            //頁(yè)面的pageUrl相當(dāng)于隱式跳轉(zhuǎn)意圖中的host://schem/path格式
            String pageUrl = null;
            //是否作為首頁(yè)的第一個(gè)展示的頁(yè)面
            boolean asStarter = false;
            //標(biāo)記該頁(yè)面是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, "不同的頁(yè)面不允許使用相同的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的版本.
     * <p>
     * 也可以通過(guò){@link SupportedSourceVersion}注解來(lái)設(shè)置
     */
    @Override
    public SourceVersion getSupportedSourceVersion() {
        //return SourceVersion.latest();
        return super.getSupportedSourceVersion();
    }

    /**
     * 返回我們將要處理的注解
     * <p>
     * 也可通過(guò){@link SupportedAnnotationTypes}注解來(lái)設(shè)置
     */
    @Override
    public Set<String> getSupportedAnnotationTypes() {
//        Set<String> annotataions = new LinkedHashSet<>();
//        annotataions.add(ActivityDestination.class.getCanonicalName());
//        annotataions.add(FragmentDestination.class.getCanonicalName());
//        return annotataions;
        return super.getSupportedAnnotationTypes();
    }

}

使用

  1. appbuild.gradle中引入自定義注解處理器
    dependencies {
         ...
         
         //引入項(xiàng)目已使用注解 如果覺(jué)得跟下面的合起來(lái)有點(diǎn)怪可以單獨(dú)再創(chuàng)建一個(gè)moudle,把注解的定義放到那里面去
         implementation project(path: ':libmyannotationprocessor')
         //配置注解處理器
         annotationProcessor project(':libmyannotationprocessor')
     
     
     }
    
  2. 使用自定義注解竹椒,給默認(rèn)生成的三個(gè)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 {
        ...
    }
    
  3. 標(biāo)記好注解后童太,重新Make Project即可在app/src/main/assets/生成對(duì)應(yīng)的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
        }
    }
    

參考文章

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請(qǐng)聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個(gè)濱河市,隨后出現(xiàn)的幾起案子书释,更是在濱河造成了極大的恐慌翘贮,老刑警劉巖,帶你破解...
    沈念sama閱讀 218,386評(píng)論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件征冷,死亡現(xiàn)場(chǎng)離奇詭異择膝,居然都是意外死亡,警方通過(guò)查閱死者的電腦和手機(jī)检激,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,142評(píng)論 3 394
  • 文/潘曉璐 我一進(jìn)店門肴捉,熙熙樓的掌柜王于貴愁眉苦臉地迎上來(lái),“玉大人叔收,你說(shuō)我怎么就攤上這事齿穗。” “怎么了饺律?”我有些...
    開(kāi)封第一講書(shū)人閱讀 164,704評(píng)論 0 353
  • 文/不壞的土叔 我叫張陵窃页,是天一觀的道長(zhǎng)。 經(jīng)常有香客問(wèn)我复濒,道長(zhǎng)脖卖,這世上最難降的妖魔是什么? 我笑而不...
    開(kāi)封第一講書(shū)人閱讀 58,702評(píng)論 1 294
  • 正文 為了忘掉前任巧颈,我火速辦了婚禮畦木,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘砸泛。我一直安慰自己十籍,他們只是感情好,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,716評(píng)論 6 392
  • 文/花漫 我一把揭開(kāi)白布唇礁。 她就那樣靜靜地躺著勾栗,像睡著了一般。 火紅的嫁衣襯著肌膚如雪盏筐。 梳的紋絲不亂的頭發(fā)上围俘,一...
    開(kāi)封第一講書(shū)人閱讀 51,573評(píng)論 1 305
  • 那天,我揣著相機(jī)與錄音琢融,去河邊找鬼楷拳。 笑死,一個(gè)胖子當(dāng)著我的面吹牛吏奸,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播陶耍,決...
    沈念sama閱讀 40,314評(píng)論 3 418
  • 文/蒼蘭香墨 我猛地睜開(kāi)眼奋蔚,長(zhǎng)吁一口氣:“原來(lái)是場(chǎng)噩夢(mèng)啊……” “哼!你這毒婦竟也來(lái)了?” 一聲冷哼從身側(cè)響起泊碑,我...
    開(kāi)封第一講書(shū)人閱讀 39,230評(píng)論 0 276
  • 序言:老撾萬(wàn)榮一對(duì)情侶失蹤坤按,失蹤者是張志新(化名)和其女友劉穎,沒(méi)想到半個(gè)月后馒过,有當(dāng)?shù)厝嗽跇?shù)林里發(fā)現(xiàn)了一具尸體臭脓,經(jīng)...
    沈念sama閱讀 45,680評(píng)論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡,尸身上長(zhǎng)有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,873評(píng)論 3 336
  • 正文 我和宋清朗相戀三年腹忽,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了来累。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,991評(píng)論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡窘奏,死狀恐怖嘹锁,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情着裹,我是刑警寧澤领猾,帶...
    沈念sama閱讀 35,706評(píng)論 5 346
  • 正文 年R本政府宣布,位于F島的核電站骇扇,受9級(jí)特大地震影響摔竿,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜少孝,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,329評(píng)論 3 330
  • 文/蒙蒙 一继低、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧韭山,春花似錦郁季、人聲如沸。這莊子的主人今日做“春日...
    開(kāi)封第一講書(shū)人閱讀 31,910評(píng)論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽(yáng)。三九已至盖淡,卻和暖如春年柠,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背褪迟。 一陣腳步聲響...
    開(kāi)封第一講書(shū)人閱讀 33,038評(píng)論 1 270
  • 我被黑心中介騙來(lái)泰國(guó)打工冗恨, 沒(méi)想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人味赃。 一個(gè)月前我還...
    沈念sama閱讀 48,158評(píng)論 3 370
  • 正文 我出身青樓掀抹,卻偏偏與公主長(zhǎng)得像,于是被迫代替她去往敵國(guó)和親心俗。 傳聞我的和親對(duì)象是個(gè)殘疾皇子傲武,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,941評(píng)論 2 355