最近在慕課網(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)境
-
app
下的build.gradle
中統(tǒng)一JDK版本android { ... compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8 } }
- 創(chuàng)建一個(gè)新的
moudle
名為libmyannotationprocessor
,記得選擇類型的時(shí)候選擇Java Library姥敛。 - 統(tǒng)一JDK環(huán)境奸焙,將moudle的
build.gradle
中的sourceCompatibility
和targetCompatibility
改為8
。dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) ... } sourceCompatibility = "8" targetCompatibility = "8"
-
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è)注解分別用于給Activity
和Fragment
標(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();
}
}
使用
- 在
app
的build.gradle
中引入自定義注解處理器dependencies { ... //引入項(xiàng)目已使用注解 如果覺(jué)得跟下面的合起來(lái)有點(diǎn)怪可以單獨(dú)再創(chuàng)建一個(gè)moudle,把注解的定義放到那里面去 implementation project(path: ':libmyannotationprocessor') //配置注解處理器 annotationProcessor project(':libmyannotationprocessor') }
- 使用自定義注解竹椒,給默認(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 { ... }
- 標(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 } }