組件化解耦 | 淺析ARouter路由查找原理與簡單實踐
- 前言
- 專案地址/資源
- 專案概覽
- 路由動態注冊與生成
- 核心思路與技術要點
- APT處理注解
- 路由初始化
- Gradle插件實作
- 目標類查找
- 插入代碼
- 實作一個簡單的路由發現框架
- 模塊組成
- ZRouter-annotation
- ZRouter-compiler
- ZRouter-api
- 結尾
前言
2022新年好,回顧過去兩年雖然零零散散多少也學了點東西,但是缺少了總結輸出,一方面是因為作業比之前忙了很多,一方面也是因為自己懈怠了,正好前段時間公司有重構需求需要了解下路由框架,借這個機會重新撿起一些丟掉的東西吧!
ARouter在眾多路由框架中也算是經典了,但是對于SDK開發來說,ARouter不免有點過重了,里面有大量對Activity和Fragment的業務,對于SDK解耦基本用不上,但是我們還是可以參考ARouter的路由發現思路,完成一個自己的小路由框架,直接進入主題吧
專案地址/資源
- ARouter
專案概覽
對于最新版本(1.5.2)比起兩年前發現多了一個gradle插件(可能是之前沒看到),是可選項,可以加快首次啟動加載速度,專案模塊和對應功能:
| 模塊 | 功能 |
|---|---|
| arouter-api | 核心API,包括路由發現、初始化跳轉等 |
| arouter-compiler | 注解處理器,根據注解和模塊生成對應的類 |
| arouter-annotation | 注解相關資訊 |
| arouter-gradle-plugin | gradle 插件工程,利用ASM插樁加快加載速度 |
路由動態注冊與生成
核心思路與技術要點
- 利用注解生成 路徑 - 類 映射表,以及拓展功能(降級、攔截器、綠色通道等);再通過核心API封裝進行跳轉查找
- APT 注解處理生成各個模塊的路由類
- 【可選】ASM 插樁代碼,加快找到路由注冊表(原有方式是掃描APK下所有dex檔案固定包名下的類)
APT處理注解
arouter-compiler編譯時掃描符合的注解,ARouter共有三個Processor,以主要的RouteProcessor為例,會生成三類檔案
ARouter$$Root$$模塊名: 添加模塊內 《組名 - Gronp類》的映射表ARouter$$Group**$$**組名: 各個組內《路徑 - 路由資訊》的映射表(不同模塊同個組名會導致覆寫!!)ARouter$$Providers$$模塊名: 一個模塊一個,存放所有Provider型別的映射
圖為demo工程生成的類:

還是以基礎的路由發現為例,在demo的moudlejava模塊中,會生成
ARouter$$Root$$modulejava.javaARouter$$Group$$yourservicegroupname.javaARouter$$Group$$test.javaARouter$$Group$$m2.javaARouter$$Group$$module.java
其中ARouter$$Root$$modulejava 內容如下
public interface IRouteRoot {
void loadInto(Map<String, Class<? extends IRouteGroup>> routes);
}
public class ARouter$$Root$$modulejava implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("m2", ARouter$$Group$$m2.class); // 各個模塊的組名和對應的類
routes.put("module", ARouter$$Group$$module.class);
routes.put("test", ARouter$$Group$$test.class);
routes.put("yourservicegroupname", ARouter$$Group$$yourservicegroupname.class);
}
}
組內以ARouter$$Group$$test為例,其內容如下
public interface IRouteGroup {
/**
* Fill the atlas with routes in group.
*/
void loadInto(Map<String, RouteMeta> atlas);
}
public class ARouter$$Group$$test implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/test/activity1", RouteMeta.build(RouteType.ACTIVITY, Test1Activity.class, "/test/activity1", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("ch", 5); put("fl", 6); put("dou", 7); put("boy", 0); put("url", 8); put("pac", 10); put("obj", 11); put("name", 8); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
atlas.put("/test/activity2", RouteMeta.build(RouteType.ACTIVITY, Test2Activity.class, "/test/activity2", "test", new java.util.HashMap<String, Integer>(){{put("key1", 8); }}, -1, -2147483648));
atlas.put("/test/activity3", RouteMeta.build(RouteType.ACTIVITY, Test3Activity.class, "/test/activity3", "test", new java.util.HashMap<String, Integer>(){{put("name", 8); put("boy", 0); put("age", 3); }}, -1, -2147483648));
atlas.put("/test/activity4", RouteMeta.build(RouteType.ACTIVITY, Test4Activity.class, "/test/activity4", "test", null, -1, -2147483648));
atlas.put("/test/fragment", RouteMeta.build(RouteType.FRAGMENT, BlankFragment.class, "/test/fragment", "test", new java.util.HashMap<String, Integer>(){{put("ser", 9); put("pac", 10); put("ch", 5); put("obj", 11); put("fl", 6); put("name", 8); put("dou", 7); put("boy", 0); put("objList", 11); put("map", 11); put("age", 3); put("height", 3); }}, -1, -2147483648));
atlas.put("/test/webview", RouteMeta.build(RouteType.ACTIVITY, TestWebview.class, "/test/webview", "test", null, -1, -2147483648));
}
}
可以看到存放的是完整路徑對應的類、優先級、型別等關系,看到這里應該就大概明白,通過這些生成的類,維護著一個映射關系表,從而實作路由發現和查找
那么接下來就是如何將這一個個檔案中的路由關系收集起來,進而實作各種業務邏輯
路由初始化
上面說到基礎原理是通過維護一個映射關系表,從而實作路由查找功能的,那么ARouter是如何找到這些類并且加載的呢?這里看到 arouter-api 模塊中初始化是如何呼叫的,直接上圖

跟蹤代碼可以發現最后邏輯在LogisticsCenter#init 方法,邏輯參照注釋
/**
* LogisticsCenter init, load all metas in memory. Demand initialization
*/
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
//load by plugin first
loadRouterMap(); // 注意:這個方法是個空實作,如果使用了gradle插件,會在這個方法進行插樁
// registerByPlugin 默認為false 只有使用了gradle插件才會置為true
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set<String> routerMap;
// It will rebuild router map every times when debuggable.
// 如果首次啟動或者debug模式,就會掃描特定包名下所有的類
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
// These class was generated by arouter-compiler.
// 工具類 掃描特定包名下類,有興趣的可以翻原始碼查看,主要是根據各種不同的dex情況進行查找
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet<String>()));
}
...
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
結合邏輯圖和原始碼,大概可以知道整個框架的路由發現這塊的邏輯就是查找固定包名下所有的類,這里不糾結細節,繼續往下看使用Gradle插件時的邏輯以及他是如何加快啟動速度的
Gradle插件實作
gradle插件是一個可選的模塊,通過初始化的原始碼可以看到如果沒有應用此插件,在應用啟動的時候會對包下所有的dex進行掃描并找到特定包名下所有的類,這樣有個問題就是首次啟動會慢一點,于是便有了這個插件進行ASM進行插樁代碼,直接在打包時就把路由資訊打入,直接看下應用插件后的效果
- 應用插件前
// LogisticsCenter
private static void loadRouterMap() {
registerByPlugin = false;
// auto generate register code by gradle plugin: arouter-auto-register
// looks like below:
// registerRouteRoot(new ARouter..Root..modulejava());
// registerRouteRoot(new ARouter..Root..modulekotlin());
}
- 應用插件后
// LogisticsCenter
private static void loadRouterMap() {
registerByPlugin = false;
register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$modulekotlin");
register("com.alibaba.android.arouter.routes.ARouter$$Root$$arouterapi");
register("com.alibaba.android.arouter.routes.ARouter$$Interceptors$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulejava");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$modulekotlin");
register("com.alibaba.android.arouter.routes.ARouter$$Providers$$arouterapi");
}
// 跟蹤其他方法 呼叫國產
private static void register(String className) {
String str = "ARouter::";
if (!TextUtils.isEmpty(className)) {
try {
Object obj = Class.forName(className).getConstructor(new Class[0]).newInstance(new Object[0]);
if (obj instanceof IRouteRoot) {
registerRouteRoot((IRouteRoot) obj);
} else if (obj instanceof IProviderGroup) {
registerProvider((IProviderGroup) obj);
} else if (obj instanceof IInterceptorGroup) {
registerInterceptor((IInterceptorGroup) obj);
} else {
ARouter.logger.info(str, "register failed, class name: " + className + " should implements one of IRouteRoot/IProviderGroup/IInterceptorGroup.");
}
} catch (Exception e) {
ARouter.logger.error(str, "register class error:" + className, e);
}
}
}
private static void registerRouteRoot(IRouteRoot routeRoot) {
markRegisteredByPlugin();
if (routeRoot != null) {
routeRoot.loadInto(Warehouse.groupsIndex);
}
}
private static void markRegisteredByPlugin() {
if (!registerByPlugin) {
registerByPlugin = true;
}
}
現在大概知道ARouter的gradle插件做了些什么東西了,反推實作在RegisterTransform 這個類中掃描了jar、原始碼里面相關的類,并記錄下來,最后寫入方法中,下面直接看插件和RegisterTransform實作原始碼
gradle插件這里是初始化了目標介面資訊,將相關的介面封裝成一個ScanSetting的串列(ScanSetting主要存放介面名稱和查找到的類名串列),然后注冊了RegisterTransform
public class PluginLaunch implements Plugin<Project> {
@Override
public void apply(Project project) {
def isApp = project.plugins.hasPlugin(AppPlugin)
//only application module needs this plugin to generate register code
if (isApp) {
Logger.make(project)
Logger.i('Project enable arouter-register plugin')
def android = project.extensions.getByType(AppExtension)
def transformImpl = new RegisterTransform(project)
//init arouter-auto-register settings
ArrayList<ScanSetting> list = new ArrayList<>(3)
list.add(new ScanSetting('IRouteRoot'))
list.add(new ScanSetting('IInterceptorGroup'))
list.add(new ScanSetting('IProviderGroup'))
RegisterTransform.registerList = list
//register this plugin
android.registerTransform(transformImpl)
}
}
}
RegisterTransform實作
// RegisterTransform#transform
void transform(Context context, Collection<TransformInput> inputs
, Collection<TransformInput> referencedInputs
, TransformOutputProvider outputProvider
, boolean isIncremental) throws IOException, TransformException, InterruptedException {
...
// 省略遍歷所有code、jar的代碼,最后都會走到ScanUtil的幾個方法中,見下文決議
// registerList 型別是 ArrayList<ScanSetting> 存放目標介面資訊
if (fileContainsInitClass) {
registerList.each { ext ->
Logger.i('Insert register code to file ' + fileContainsInitClass.absolutePath)
if (ext.classList.isEmpty()) {
Logger.e("No class implements found for interface:" + ext.interfaceName)
} else {
ext.classList.each {
Logger.i(it)
}
// 這里插入代碼
RegisterCodeGenerator.insertInitCodeTo(ext)
}
}
}
Logger.i("Generate code finish, current cost time: " + (System.currentTimeMillis() - startTime) + "ms")
}
目標類查找
上面省略的代碼在拿到特定包名所有的類后(jar、原始碼)走到了
ScanUtil.scanJar(src, dest)、ScanUtil.scanClass(file) 這兩個方法,跟蹤呼叫后都走到ScanUtil#scanClass(InputStream inputStream),然后通過ASM遍歷是否實作了相關介面,如果存在就存放在對應的ScanSetting里面的類名串列等待下一步插入代碼
static void scanClass(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ScanClassVisitor cv = new ScanClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
inputStream.close()
}
static class ScanClassVisitor extends ClassVisitor {
ScanClassVisitor(int api, ClassVisitor cv) {
super(api, cv)
}
void visit(int version, int access, String name, String signature,
String superName, String[] interfaces) {
super.visit(version, access, name, signature, superName, interfaces)
// 這里的ext是一個ScanSetting類,里面有掃描的資訊包括IRouteRoot、IProviderGroup、IInterceptorGroup
RegisterTransform.registerList.each { ext ->
// 這里判斷是不是符合的類
if (ext.interfaceName && interfaces != null) {
interfaces.each { itName ->
if (itName == ext.interfaceName) {
//fix repeated inject init code when Multi-channel packaging
if (!ext.classList.contains(name)) {
// 存放在ScanSetting.classList 中
ext.classList.add(name)
}
}
}
}
}
}
}
插入代碼
插入代碼這里也是遍歷ScanSetting串列了,然后通過RegisterCodeGenerator.(ScanSetting registerSetting)方法插入代碼
// RegisterCodeGenerator#insertInitCodeTo
static void insertInitCodeTo(ScanSetting registerSetting) {
if (registerSetting != null && !registerSetting.classList.isEmpty()) {
RegisterCodeGenerator processor = new RegisterCodeGenerator(registerSetting)
// 這里的file是指LogisticsCenter.class 所在的jar,掃描時標注賦值的,畢竟要在一個類插入代碼,首先要知道這個類在哪里
File file = RegisterTransform.fileContainsInitClass
if (file.getName().endsWith('.jar'))
processor.insertInitCodeIntoJarFile(file)
}
// RegisterCodeGenerator#insertInitCodeIntoJarFile
private File insertInitCodeIntoJarFile(File jarFile) {
if (jarFile) {
...
while (enumeration.hasMoreElements()) {
...
if (ScanSetting.GENERATE_TO_CLASS_FILE_NAME == entryName) {
// 找到目標檔案開始插入
def bytes = referHackWhenInit(inputStream)
jarOutputStream.write(bytes)
} else {
jarOutputStream.write(IOUtils.toByteArray(inputStream))
}
inputStream.close()
jarOutputStream.closeEntry()
}
...
}
return jarFile
}
// RegisterCodeGenerator#referHackWhenInit
private byte[] referHackWhenInit(InputStream inputStream) {
ClassReader cr = new ClassReader(inputStream)
ClassWriter cw = new ClassWriter(cr, 0)
ClassVisitor cv = new MyClassVisitor(Opcodes.ASM5, cw)
cr.accept(cv, ClassReader.EXPAND_FRAMES)
return cw.toByteArray()
}
前面掃描標注了LogisticsCenter.class所在的jar,這里也是找到這個jar,然后遍歷這個jar找到LogisticsCenter.class,然后呼叫了MyClassVisitor,MyClassVisitor里面再呼叫一個RouteMethodVisitor找到目標方法然后插入代碼,這一塊應該很好理解,直接看RouteMethodVisitor的實作
class RouteMethodVisitor extends MethodVisitor {
RouteMethodVisitor(int api, MethodVisitor mv) {
super(api, mv)
}
@Override
void visitInsn(int opcode) {
//generate code before return
if ((opcode >= Opcodes.IRETURN && opcode <= Opcodes.RETURN)) {
// 遍歷前一步存放的類名串列
extension.classList.each { name ->
name = name.replaceAll("/", ".")
mv.visitLdcInsn(name)//類名
// generate invoke register method into LogisticsCenter.loadRouterMap()
mv.visitMethodInsn(Opcodes.INVOKESTATIC
, ScanSetting.GENERATE_TO_CLASS_NAME
, ScanSetting.REGISTER_METHOD_NAME
, "(Ljava/lang/String;)V"
, false)
}
}
super.visitInsn(opcode)
}
@Override
void visitMaxs(int maxStack, int maxLocals) {
super.visitMaxs(maxStack + 4, maxLocals)
}
}
至此,Gradle插件就完成了所有的作業
實作一個簡單的路由發現框架
前面說到這次重新看ARouter的原因主要是公司專案重構,而且主要是想用一下這個路由發現業務,在分析完ARouter路由注冊和發現這塊的邏輯,可以看到還是比較好實作的,下面示例一個簡單版本的實作
模塊組成
- zrouter-annotation 定義了注解還有包含注解的資訊
- zrouter-api 核心API,包括初始化等
- zrouter-compiler 注解處理器,用來根據注解動態生成模塊代碼
ZRouter-annotation
這個模塊主要定義了用到的注解和處理注解資訊生成的bean類工具類等,示例也非常簡單直接參考ARouter的
// Route.java
@Target({ElementType.TYPE})
@Retention(RetentionPolicy.CLASS)
public @interface Route {
String path();
int priority() default -1;
int extras() default Integer.MIN_VALUE;
}
// RouteMeta.java
public class RouteMeta {
private Class<?> destination;
private String path;
private String group;
private int priority = -1;
private int extra;
...
// 省略 get set方法和建構式
}
ZRouter-compiler
直接上代碼,核心部分是process,其他一些api工具也貼上方便以后參考,三方庫主要使用了javapoet生成代碼以及auto-service動態注冊
// ZRouterCompiler.java
@AutoService(Processor.class)
public class ZRouterCompiler extends AbstractProcessor {
/**
* 節點工具類(類、函式、屬性都是節點)
*/
private Elements mElementUtils;
/**
* 類資訊工具類
*/
private Types mTypeUtils;
/**
* 檔案生成器
*/
private Filer mFiler;
/**
* 日志資訊列印器
*/
private Messager mMessager;
// Module name, maybe its 'app' or others
String moduleName = null;
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
mElementUtils = processingEnv.getElementUtils();
mTypeUtils = processingEnv.getTypeUtils();
mFiler = processingEnv.getFiler();
mMessager = processingEnv.getMessager();
//通過 key 獲取 build.gradle 中對應的 value
// processingEnv.getOptions().get("KeyValue");
// Attempt to get user configuration [moduleName]
Map<String, String> options = processingEnv.getOptions();
if (MapUtils.isNotEmpty(options)) {
moduleName = options.get(KEY_MODULE_NAME);
}
if (StringUtils.isNotEmpty(moduleName)) {
moduleName = moduleName.replaceAll("[^0-9a-zA-Z_]+", "");
mMessager.printMessage(Diagnostic.Kind.NOTE,"The user has configuration the module name, it was [" + moduleName + "]");
} else {
mMessager.printMessage(Diagnostic.Kind.ERROR,"NO_MODULE_NAME");
throw new RuntimeException("ZRouter::Compiler >>> No module name, for more information, look at gradle log.");
}
}
@Override
public boolean process(Set<? extends TypeElement> annotations, RoundEnvironment roundEnv) {
if (CollectionUtils.isEmpty(annotations)) {
return false;
}
List<Element> elementList = new ArrayList<>(roundEnv.getElementsAnnotatedWith(Route.class));
Map<Element,RouteMeta> metaMap = new HashMap<>();
try {
// 獲取注解資訊,構建bean類
for (Element element : elementList){
System.out.println("ele:" + element.getSimpleName());
RouteMeta routeMeta = new RouteMeta();
Route annotation = element.getAnnotation(Route.class);
routeMeta.setPath(annotation.path());
routeMeta.setExtra(annotation.extras());
routeMeta.setPriority(annotation.priority());
metaMap.put(element,routeMeta);
}
// javapoet API,構建一個方法
MethodSpec.Builder loadPluginMethodBuilder = Utils.getLoadPluginMethodBuilder();
// 遍歷注解資訊,生成代碼
for (Map.Entry<Element,RouteMeta> entry : metaMap.entrySet()){
RouteMeta routeMeta = entry.getValue();
Element element = entry.getKey();
loadPluginMethodBuilder.addStatement("data.add(new $T($T.class,$S,$S," +
routeMeta.getPriority()+ "," + routeMeta.getExtra()+ "))",
CLS_ROUTE_META,
ClassName.get((TypeElement) element),
routeMeta.getPath(),
routeMeta.getGroup()
);
}
// 生成檔案
String fileName = PREFIX_CLASS_NAME + moduleName;
System.out.println("ready to generate file:" + fileName);
JavaFile.builder(PACKAGE_OF_GENERATE_FILE,
TypeSpec.classBuilder(fileName)
.addJavadoc(FILE_TIP)
.addSuperinterface(CLS_ROUTER_ACQUIRER)
.addModifiers(PUBLIC)
.addMethod(loadPluginMethodBuilder.build())
.build()
).addFileComment(FILE_TIP).build().writeTo(mFiler);
}catch (Exception e){
e.printStackTrace();
System.out.println("=====build route error =====");
}
return true;
}
/**
* 接收外來傳入的引數,最常用的形式就是在 build.gradle 腳本檔案里的 javaCompileOptions 的配置
*
* @return 屬性的 Key 集合
*/
@Override
public Set<String> getSupportedOptions() {
// Set<String> hashSet = new LinkedHashSet<>();
// hashSet.add("MODULE_NAME");
return super.getSupportedOptions();
}
/**
* 當前注解處理器支持的注解集合,如果支持,就會呼叫 process 方法
*
* @return 支持的注解集合
*/
@Override
public Set<String> getSupportedAnnotationTypes() {
Set<String> hashSet = new LinkedHashSet<>();
hashSet.add(Route.class.getCanonicalName());
// hashSet.add(BindClick.class.getCanonicalName());
return hashSet;
}
/**
* 編譯當前注解處理器的 JDK 版本
*
* @return JDK 版本
*/
@Override
public SourceVersion getSupportedSourceVersion() {
return SourceVersion.RELEASE_8;
}
工具類和常量
// Utils.java
public class Utils {
static final ClassName ROUTER_ACQUIRER = ClassName.get("com.hjl.zrouter_api","IRouteAcquirer");
static ParameterizedTypeName inputMapType = ParameterizedTypeName.get(
ClassName.get(List.class),
ClassName.get(RouteMeta.class)
);
public static MethodSpec.Builder getLoadPluginMethodBuilder(){
return MethodSpec.methodBuilder(METHOD_LOAD_PLUGIN)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(inputMapType,"data");
}
}
// Constant.java
interface Constant {
String KEY_MODULE_NAME = "ZROUTER_MODULE_NAME";
String PREFIX_CLASS_NAME = "ZRouter$$";
String METHOD_LOAD_PLUGIN = "loadPlugin";
String PACKAGE_OF_GENERATE_FILE = "com.hjl.zrouter.routes";
String FILE_TIP = "This file generate by ZRouter, Do not modify";
ClassName CLS_ROUTER_ACQUIRER = ClassName.get("com.hjl.zrouter_api","IRouteAcquirer");
ClassName CLS_ROUTE_META = ClassName.get(RouteMeta.class);
}
build.gradle
apply plugin: 'java-library'
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation project(':zrouter:zrouter-annotation')
implementation 'com.squareup:javapoet:1.13.0'
implementation 'org.apache.commons:commons-lang3:3.5'
implementation 'org.apache.commons:commons-collections4:4.1'
annotationProcessor 'com.google.auto.service:auto-service:1.0-rc7'
compileOnly 'com.google.auto.service:auto-service-annotations:1.0-rc7'
}
核心代碼邏輯還是很簡單的,主要是思路,生成的代碼也很簡單,接下來的就是遍歷包名找到這些類了
// TestJavaChannelAdapter.java
@Route(path = "/test/java")
public class TestJavaChannelAdapter implements IChannelAdapter {
...
// 省略業務邏輯
}
// 下面是對應生成的類
/**
* This file generate by ZRouter, Do not modify
*/
public class ZRouter$$testmodulejava implements IRouteAcquirer {
@Override
public void loadPlugin(List<RouteMeta> data) {
data.add(new RouteMeta(TestJavaChannelAdapter.class,"/test/java",null,-1,-2147483648));
}
}
ZRouter-api
這里主要是獲取到所有的注解類以及一些跳轉邏輯的處理了,個人認為跳轉邏輯根據業務需要可以直接實作,掃描包內或者Gradle插件可以直接參考ARouter,這里只是非常簡單的示范了下
public interface IRouteAcquirer {
void loadPlugin(List<RouteMeta> data);
}
public class ZRouter {
private List<RouteMeta> routeMetaList = new ArrayList<>();
public void init(Context context){
mContext = context;
Log.i(TAG, "start init");
routeMetaList.clear();
loadRouterMap();
if (isLoadByPlugin){
Log.i(TAG, "load route meta from plugin");
}else {
try {
// 耗時操作 可用Gradle插件解決 ,可以直接拿ARouter的改一改,這里的工具類直接使用ARouter的
Set<String> clsNameSet = ClassUtils.getFileNameByPackageName(context, "com.hjl.zrouter.routes");
parseCls(clsNameSet);
} catch (Exception e) {
e.printStackTrace();
Log.e(TAG,"load route meta error :",e);
}
}
}
private static void loadRouterMap(){
// TODO : ASM 代碼插入
}
// 決議類并添加到串列
private void parseCls(Set<String> data) throws Exception {
for (String cls : data){
((IRouteAcquirer)Class.forName(cls).getConstructor().newInstance()).loadPlugin(routeMetaList);
}
}
// 獲取決議好的注解類資訊
public List<RouteMeta> getRouteMetaList() {
return routeMetaList;
}
}
獲取到決議好的注解類資訊后,就可以根據具體業務去實作相關邏輯了,demo示范一下獲取單例集合渠道串列
val supportChannelList = mutableListOf<IChannelAdapter>()
ZRouter.getInstance().init(context)
ZRouter.getInstance().routeMetaList.forEach {
Log.i("ward", "get route meta:$it ")
val newInstance = it.destination.getConstructor().newInstance() as IChannelAdapter
supportChannelList.add(newInstance)
}
至此,我們完成了可以用于SDK解耦的一個小型的路由注解發現框架
結尾
其實可以發現大部分好用的開源框架都是運用了注解AOP和Gradle插件ASM插入代碼分別在不同的時機插入代碼,減少了大量的作業,因此對于閱讀第三方框架的原始碼和了解的他們思想來說,熟悉這兩項技術的流程和運用還是很有必要的,同時也可以在自己的專案運用來解決一些需求場景
轉載請註明出處,本文鏈接:https://www.uj5u.com/yidong/403996.html
標籤:其他
