在組件化子模塊間交互中說了類加載、全局Map記錄實現組件化模塊之間的交互。那么我們是否可以通過APT幫生成java文件,文件中記錄需要記錄的類,然后在我們需要的時候,通過查找獲取類,然后通過類加載的方式實現模塊之間的跳轉呢?
思考:在組件化架構中,我們需要通過APT和JavaPoet技術生成什么樣的類文件呢?
我們會使用APT生成ARouter$$Group$$模塊名.java
與ARouter$$Path$$模塊名.java
兩個文件。
思考:
1、為什么需要組名?
在做路由加載管理類的時候,我們不需要加載所有的類文件。這個時候分組就很有必要了。2、生成這些文件干嘛用?
我們可以通過組文件記錄,找到路徑文件記錄,通過路徑獲取RouterBean。
項目部署
可以參照APT的介紹與使用創建自定義注解的Module(arouter_annotation)與注解處理器的Module(arouter_compiler)
// 引入arouter_annotation注解,目的是引入RouterBean
implementation project(':arouter_annotation')
到這里就可以思考兩個問題了,RouterBean類為什么要在arouter_annotation 下面創建?
RouterBean里面有Element的屬性,而Element它是在javax.lang包下的,不屬于Android Library,這樣就導致需要在Java Library Module里面創建,而arouter_compiler、app、order、personal都需要導入arouter_annotation,所以就在arouter_annotation下創建了RouterBean。
Element類節點為什么要存儲起來呢?
因為我們要在注解處理器中,循環拿到每個類節點。方便賦值與調用。
組文件與路徑文件需要繼承的接口
/**
* 路由組Group加載數據接口
*/
public interface ARouterLoadGroup {
/**
* 加載路由組Group數據
* 比如:"app", ARouter$$Path$$app.class(實現了ARouterLoadPath接口)
*
* @return key:"app", value:"app"分組對應的路由詳細對象類
*/
Map<String, Class<? extends ARouterLoadPath>> loadGroup();
}
public interface ARouterLoadPath {
/**
* 加載路由組Group對應的詳細Path加載數據接口
* 比如:app分組對應有哪些類需要加載
*
* @return key:"/app/MainActivity", value:MainActivity信息封裝到RouterBean對象中
*/
Map<String, RouterBean> loadPath();
}
// 每個功能子模塊既然都要生成APT源文件,而且又是所有模塊依賴的公共庫
// 那么公共基礎庫依賴路由arouter_api就能向每個子模塊提供開放api了
api project(':arouter_api') // 路由對外開放api模塊
// 在gradle文件中配置選項參數值(用于APT傳參接收)
// 切記:必須寫在defaultConfig節點下
javaCompileOptions {
annotationProcessorOptions {
arguments = [moduleName: project.getName(), packageNameForAPT: "com.migill.modular.apt"]
}
}
@AutoService(Processor.class)
// 允許/支持的注解類型,讓注解處理器處理
@SupportedAnnotationTypes({Constants.IROUTER_ANNOTATION_TYPES})
// 指定JDK編譯版本
@SupportedSourceVersion(SourceVersion.RELEASE_7)
@SupportedOptions({Constants.MODULE_NAME, Constants.APT_PACKAGE})
public class ARouterProcessor extends AbstractProcessor {
// 操作Element工具類 (類、函數、屬性都是Element)
private Elements elementUtils;
// type(類信息)工具類,包含用于操作TypeMirror的工具方法
private Types typeUtils;
// Messager用來報告錯誤,警告和其他提示信息
private Messager messager;
// 文件生成器 類/資源,Filter用來創建新的源文件,class文件以及輔助文件
private Filer filer;
// 子模塊名,如:app/order/personal。需要拼接類名時用到(必傳)ARouter$$Group$$order
private String moduleName;
//包名,用于存放APT生成的類文件
private String packageNameForAPT;
// 臨時map存儲,用來存放路由組Group對應的詳細Path類對象,生成路由路徑類文件時遍歷
// key:組名"app", value:"app"組的路由路徑"ARouter$$Path$$app.class"
private Map<String, List<RouterBean>> tempPathMap = new HashMap<>();
// 臨時map存儲,用來存放路由Group信息,生成路由組類文件時遍歷
// key:組名"app", value:類名"ARouter$$Path$$app.class"
private Map<String, String> tempGroupMap = new HashMap<>();
// 該方法主要用于一些初始化的操作,通過該方法的參數ProcessingEnvironment可以獲取一些列有用的工具類
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
elementUtils = processingEnvironment.getElementUtils();
typeUtils = processingEnvironment.getTypeUtils();
messager = processingEnvironment.getMessager();
filer = processingEnvironment.getFiler();
// 通過ProcessingEnvironment去獲取對應的參數
Map<String, String> options = processingEnvironment.getOptions();
if (!EmptyUtils.isEmpty(options)) {
moduleName = options.get(Constants.MODULE_NAME);
packageNameForAPT = options.get(Constants.APT_PACKAGE);
messager.printMessage(Diagnostic.Kind.NOTE, "moduleName >>> " + moduleName);
messager.printMessage(Diagnostic.Kind.NOTE, "packageNameForAPT >>> " + packageNameForAPT);
}
if (EmptyUtils.isEmpty(moduleName) || EmptyUtils.isEmpty(packageNameForAPT)) {
throw new RuntimeException("注解處理器需要的參數moduleName或者packageName為空,請在對應build.gradle配置參數");
}
}
/**
* 相當于main函數,開始處理注解
* 注解處理器的核心方法,處理具體的注解,生成Java文件
*
* @param set 使用了支持處理注解的節點集合
* @param roundEnvironment 當前或是之前的運行環境,可以通過該對象查找的注解。
* @return true 表示后續處理器不會再處理(已經處理完成)
*/
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
//一旦有類之上使用@ARouter注解
if (!EmptyUtils.isEmpty(set)) {
//獲取所有被@ARouter注解的 元素集合
Set<? extends Element> elements = roundEnvironment.getElementsAnnotatedWith(ARouter.class);
if (!EmptyUtils.isEmpty(elements)) {
try {
parseElements(elements);
return true;
} catch (IOException e) {
e.printStackTrace();
}
}
// 坑:必須寫返回值,表示處理@ARouter注解完成
return true;
}
return false;
}
// 解析所有被@ARouter注解的類元素集合
private void parseElements(Set<? extends Element> elements) throws IOException {
//通過Element工具類,獲取Activity、Callback類型
TypeElement activityType = elementUtils.getTypeElement(Constants.ACTIVITY);
//顯示類信息(獲取被注解節點,類節點)這里也叫字描述 Mirror
TypeMirror activityMirror = activityType.asType();
//遍歷節點
for (Element element : elements) {
//獲取每個元素類信息,用于比較
TypeMirror elementMirror = element.asType();
messager.printMessage(Diagnostic.Kind.NOTE, "遍歷元素信息:" + elementMirror.toString());
//獲取每個類上的@ARouter注解中的注解值
ARouter aRouter = element.getAnnotation(ARouter.class);
//路由詳細信息、最終實體封裝類
RouterBean bean = new RouterBean.Builder()
.setGroup(aRouter.group())
.setPath(aRouter.path())
.setElement(element)
.build();
//高級判斷:ARouter注解僅能使用在類之上,并且是規定的Activity
//類型工具類方法isSubtype,相當于instance一樣
if (typeUtils.isSubtype(elementMirror, activityMirror)) {
bean.setType(RouterBean.Type.ACTIVITY);
} else {
// 不匹配拋出異常,這里謹慎使用!考慮維護問題
throw new RuntimeException("@ARouter注解目前僅限用于Activity類之上");
}
//賦值臨時map存儲,用來存放路由組Group對應的詳細Path類對象
valueOfPathMap(bean);
}
//routerMap遍歷后,用來生成類文件
//獲取ARouterLoadGroup、ARouterLoadPath類型(生成類文件需要實現的接口)
//組接口
TypeElement groupLoadType = elementUtils.getTypeElement(Constants.AROUTE_GROUP);
messager.printMessage(Diagnostic.Kind.NOTE, "groupLoadType :" + groupLoadType.getQualifiedName().toString());
//路徑接口
TypeElement pathLoadType = elementUtils.getTypeElement(Constants.AROUTE_PATH);
messager.printMessage(Diagnostic.Kind.NOTE, "pathLoadType :" + pathLoadType.getQualifiedName().toString());
// 第一步:生成路由組Group對應詳細Path類文件,如:ARouter$$Path$$app
createPathFile(pathLoadType);
// 第二步:生成路由組Group類文件(沒有第一步,取不到類文件),如:ARouter$$Group$$app
createGroupFile(groupLoadType, pathLoadType);
}
/**
* 生成路由組Group對應詳細Path,如:ARouter$$Path$$app
*
* @param pathLoadType ARouterLoadPath接口信息
*/
private void createPathFile(TypeElement pathLoadType) throws IOException {
// 判斷是否有需要生成的類文件
if (EmptyUtils.isEmpty(tempPathMap)) return;
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class),// Map
ClassName.get(String.class),// Map<String,
ClassName.get(RouterBean.class)// Map<String, RouterBean>
);
// 遍歷分組,每一個分組創建一個路徑類文件,如:ARouter$$Path$$app
for (Map.Entry<String, List<RouterBean>> entry : tempPathMap.entrySet()) {
//方法配置:public Map<String, RouterBean> loadPath() {
MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.PATH_METHOD_NAME)// 方法名
.addAnnotation(Override.class)// 重寫注解
.addModifiers(Modifier.PUBLIC)//public修飾符
.returns(methodReturns);//方法返回值
// 遍歷之前:Map<String, RouterBean> pathMap = new HashMap<>();
methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ClassName.get(RouterBean.class),
Constants.PATH_PARAMETER_NAME,
HashMap.class);
// 一個分組,如:ARouter$$Path$$app。有很多詳細路徑信息,如:/app/MainActivity、/app/OtherActivity
List<RouterBean> pathList = entry.getValue();
// 方法內容配置(遍歷每個分組中每個路由詳細路徑)
for (RouterBean bean : pathList) {
// pathMap.put("/app/MainActivity", RouterBean.create(RouterBean.Type.ACTIVITY, MainActivity.class, "/app/MainActivity", "app"));
methodBuidler.addStatement(
"$N.put($S, $T.create($T.$L, $T.class, $S, $S))",
Constants.PATH_PARAMETER_NAME, // pathMap.put
bean.getPath(), // "/app/MainActivity"
ClassName.get(RouterBean.class), // RouterBean
ClassName.get(RouterBean.Type.class), // RouterBean.Type
bean.getType(), // 枚舉類型:ACTIVITY
ClassName.get((TypeElement) bean.getElement()), // MainActivity.class
bean.getPath(), // 路徑名
bean.getGroup() // 組名
);
}
// 遍歷之后:return pathMap;
methodBuidler.addStatement("return $N", Constants.PATH_PARAMETER_NAME);
// 最終生成的類文件名
String finalClassName = Constants.PATH_FILE_NAME + entry.getKey();
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由Path類文件:" +
packageNameForAPT + "." + finalClassName);
// 生成類文件:public class ARouter$$Path$$app implements ARouterLoadPath {
JavaFile.builder(packageNameForAPT,//包名
TypeSpec.classBuilder(finalClassName)//類名
.addSuperinterface(ClassName.get(pathLoadType))
.addModifiers(Modifier.PUBLIC)
.addMethod(methodBuidler.build())
.build())//類構建完成
.build()
.writeTo(filer);
//非常重要的一步!!!!!路徑文件生成出來了,才能賦值路由組tempGroupMap
tempGroupMap.put(entry.getKey(), finalClassName);
}
}
/**
* 生成路由組Group文件,如:ARouter$$Group$$app
*
* @param groupLoadType ARouterLoadGroup接口信息
* @param pathLoadType ARouterLoadPath接口信息
*/
private void createGroupFile(TypeElement groupLoadType, TypeElement pathLoadType) throws IOException {
// 判斷是否有需要生成的類文件
if (EmptyUtils.isEmpty(tempGroupMap) || EmptyUtils.isEmpty(tempPathMap)) return;
TypeName methodReturns = ParameterizedTypeName.get(
ClassName.get(Map.class), // Map
ClassName.get(String.class), // Map<String,
// 第二個參數:Class<? extends ARouterLoadPath>
// 某某Class是否屬于ARouterLoadPath接口的實現類
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathLoadType)))
);
// 方法配置:public Map<String, Class<? extends ARouterLoadPath>> loadGroup() {
MethodSpec.Builder methodBuidler = MethodSpec.methodBuilder(Constants.GROUP_METHOD_NAME) // 方法名
.addAnnotation(Override.class) // 重寫注解
.addModifiers(Modifier.PUBLIC) // public修飾符
.returns(methodReturns); // 方法返回值
// 遍歷之前:Map<String, Class<? extends ARouterLoadPath>> groupMap = new HashMap<>();
methodBuidler.addStatement("$T<$T, $T> $N = new $T<>()",
ClassName.get(Map.class),
ClassName.get(String.class),
ParameterizedTypeName.get(ClassName.get(Class.class),
WildcardTypeName.subtypeOf(ClassName.get(pathLoadType))),
Constants.GROUP_PARAMETER_NAME,
HashMap.class);
//方法內容配置
for (Map.Entry<String, String> entry : tempGroupMap.entrySet()) {
// groupMap.put("main", ARouter$$Path$$app.class);
methodBuidler.addStatement("$N.put($S, $T.class)",
Constants.GROUP_PARAMETER_NAME, // groupMap.put
entry.getKey(),
// 類文件在指定包名下
ClassName.get(packageNameForAPT, entry.getValue()));
}
// 遍歷之后:return groupMap;
methodBuidler.addStatement("return $N", Constants.GROUP_PARAMETER_NAME);
// 最終生成的類文件名
String finalClassName = Constants.GROUP_FILE_NAME + moduleName;
messager.printMessage(Diagnostic.Kind.NOTE, "APT生成路由組Group類文件:" +
packageNameForAPT + "." + finalClassName);
// 生成類文件:ARouter$$Group$$app
JavaFile.builder(packageNameForAPT, // 包名
TypeSpec.classBuilder(finalClassName) // 類名
.addSuperinterface(ClassName.get(groupLoadType)) // 實現ARouterLoadGroup接口
.addModifiers(Modifier.PUBLIC) // public修飾符
.addMethod(methodBuidler.build()) // 方法的構建(方法參數 + 方法體)
.build()) // 類構建完成
.build() // JavaFile構建完成
.writeTo(filer); // 文件生成器開始生成類文件
}
/**
* 賦值臨時map存儲,用來存放路由組Group對應的詳細Path類對象,生成路由路徑類文件時遍歷
*
* @param bean 路由詳細信息,最終實體封裝類
*/
private void valueOfPathMap(RouterBean bean) {
messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
if (checkRouterPath(bean)) {
messager.printMessage(Diagnostic.Kind.NOTE, "RouterBean >>> " + bean.toString());
//開始賦值Map
List<RouterBean> routerBeans = tempPathMap.get(bean.getGroup());
// 如果從Map中找不到key為:bean.getGroup()的數據,就新建List集合再添加進Map
if (EmptyUtils.isEmpty(routerBeans)) {
routerBeans = new ArrayList<>();
routerBeans.add(bean);
tempPathMap.put(bean.getGroup(), routerBeans);
} else {// 找到了key,直接加入List集合
routerBeans.add(bean);
}
} else {
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按規范配置,如:/app/MainActivity");
}
}
private boolean checkRouterPath(RouterBean bean) {
String group = bean.getGroup();
String path = bean.getPath();
// @ARouter注解中的path值,必須要以 / 開頭(模仿阿里Arouter規范)
if (EmptyUtils.isEmpty(path) || !path.startsWith("/")) {
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的path值,必須要以 / 開頭");
return false;
}
// 比如開發者代碼為:path = "/MainActivity",最后一個 / 符號必然在字符串第1位
if (path.lastIndexOf("/") == 0) {
// 架構師定義規范,讓開發者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解未按規范配置,如:/app/MainActivity");
return false;
}
// 從第一個 / 到第二個 / 中間截取,如:/app/MainActivity 截取出 app 作為group
String finalGroup = path.substring(1, path.indexOf("/", 1));
// @ARouter注解中的group有賦值情況
if (!EmptyUtils.isEmpty(group) && !group.equals(moduleName)) {
// 架構師定義規范,讓開發者遵循
messager.printMessage(Diagnostic.Kind.ERROR, "@ARouter注解中的group值必須和子模塊名一致!");
return false;
} else {
bean.setGroup(finalGroup);
}
return true;
}
}
/**
* 路由加載管理器
*/
public final class RouterManager {
// 路由組名
private String group;
// 路由詳細路徑
private String path;
private static RouterManager instance;
// Lru緩存,key:類名, value:路由組Group加載接口
private LruCache<String, ARouterLoadGroup> groupCache;
// Lru緩存,key:類名, value:路由組Group對應的詳細Path加載接口
private LruCache<String, ARouterLoadPath> pathCache;
// APT生成的路由組Group源文件前綴名
private static final String GROUP_FILE_PREFIX_NAME = ".ARouter$$Group$$";
// 單例方式,全局唯一
public static RouterManager getInstance() {
if (instance == null) {
synchronized (RouterManager.class) {
if (instance == null) {
instance = new RouterManager();
}
}
}
return instance;
}
private RouterManager() {
// 初始化,并賦值緩存中條目的最大值
groupCache = new LruCache<>(200);
// 每組最多163條路徑值
pathCache = new LruCache<>(200);
}
public BundleManager build(String path) {
// @ARouter注解中的path值,必須要以 / 開頭(模仿阿里Arouter規范)
if (TextUtils.isEmpty(path) || !path.startsWith("/")) {
throw new IllegalArgumentException("未按規范配置,如:/app/MainActivity");
}
group = subFromPath2Group(path);
// 檢查后再賦值
this.path = path;
return new BundleManager();
}
private String subFromPath2Group(String path) {
// 比如開發者代碼為:path = "/MainActivity",最后一個 / 符號必然在字符串第1位
if (path.lastIndexOf("/") == 0) {
// 架構師定義規范,讓開發者遵循
throw new IllegalArgumentException("@ARouter注解未按規范配置,如:/app/MainActivity");
}
// 從第一個 / 到第二個 / 中間截取,如:/app/MainActivity 截取出 app 作為group
String finalGroup = path.substring(1, path.indexOf("/", 1));
if (TextUtils.isEmpty(finalGroup)) {
// 架構師定義規范,讓開發者遵循
throw new IllegalArgumentException("@ARouter注解未按規范配置,如:/app/MainActivity");
}
// 最終組名:app
return finalGroup;
}
/**
* 開始跳轉
*
* @param context 上下文
* @param bundleManager Bundle拼接參數管理類
* @param code 這里的code,可能是requestCode,也可能是resultCode。取決于isResult
* @return 普通跳轉可以忽略,用于跨模塊CALL接口
*/
Object navigation(@NonNull Context context, BundleManager bundleManager, int code) {
String groupClassName = context.getPackageName() + ".apt" + GROUP_FILE_PREFIX_NAME + group;
try {
ARouterLoadGroup groupLoad = groupCache.get(group);
if (groupLoad == null) {
Class<?> clazz = Class.forName(groupClassName);
groupLoad = (ARouterLoadGroup) clazz.newInstance();
groupCache.put(group, groupLoad);
}
// 獲取路由路徑類ARouter$$Path$$app
if (groupLoad.loadGroup().isEmpty()) {
throw new RuntimeException("路由加載失敗");
}
ARouterLoadPath pathLoad = pathCache.get(path);
if (pathLoad == null) {
Class<? extends ARouterLoadPath> clazz = groupLoad.loadGroup().get(group);
if (clazz != null) pathLoad = clazz.newInstance();
if (pathLoad != null) pathCache.put(path, pathLoad);
}
if (pathLoad != null) {
if (pathLoad.loadPath().isEmpty()) {
throw new RuntimeException("路由路徑加載失敗");
}
RouterBean routerBean = pathLoad.loadPath().get(path);
if (routerBean != null) {
switch (routerBean.getType()) {
case ACTIVITY:
Intent intent = new Intent(context, routerBean.getClazz());
intent.putExtras(bundleManager.getBundle());
// startActivityForResult -> setResult
if (bundleManager.isResult()) {
((Activity) context).setResult(code, intent);
((Activity) context).finish();
}
if (code > 0) { // 跳轉時是否回調
((Activity) context).startActivityForResult(intent, code, bundleManager.getBundle());
} else {
context.startActivity(intent, bundleManager.getBundle());
}
break;
}
}
}
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
}
/**
* Bundle拼接參數管理類
*/
public final class BundleManager {
private Bundle bundle = new Bundle();
// 是否回調setResult()
private boolean isResult;
Bundle getBundle() {
return bundle;
}
boolean isResult() {
return isResult;
}
// @NonNull不允許傳null,@Nullable可以傳null
public BundleManager withString(@NonNull String key, @Nullable String value) {
bundle.putString(key, value);
return this;
}
// 示例代碼,需要拓展
public BundleManager withResultString(@NonNull String key, @Nullable String value) {
bundle.putString(key, value);
isResult = true;
return this;
}
public BundleManager withBoolean(@NonNull String key, boolean value) {
bundle.putBoolean(key, value);
return this;
}
public BundleManager withInt(@NonNull String key, int value) {
bundle.putInt(key, value);
return this;
}
public BundleManager withBundle(@NonNull Bundle bundle) {
this.bundle = bundle;
return this;
}
public Object navigation(Context context) {
return RouterManager.getInstance().navigation(context, this, -1);
}
// 這里的code,可能是requestCode,也可能是resultCode。取決于isResult
public Object navigation(Context context, int code) {
return RouterManager.getInstance().navigation(context, this, code);
}
}
public void jumpApp(View view) {
RouterManager.getInstance()
.build("/app/MainActivity")
.withString("username", "migill")
.navigation(this );
}
public void jumpOrder(View view) {
RouterManager.getInstance()
.build("/order/Order_MainActivity")
.withString("username", "migill")
.navigation(this);
}