0到1实战ARouter

314 阅读5分钟

感谢佬的点阅~~~

ARouter是一个用于Android App 进行组件化改造的框架 ,主要是为了支持模块间的路由、通信和解耦。其运用广、设计巧妙、涉及技术较全面,好吧,重点是新手友好。基本上每个androider都会研究它。当然,我也不例外,将其作为我步入android领域研究的第一个项目,回到正题👇👇👇

本篇文章主要由浅入深,由0到1的搭建一个仿ARouter的路由跳转框架,以实战角度认识ARouter以及学习掌握其所涉及的技术:apt(现在有ksp了,后面去学学)、自定义gradle插件技术、Transform(在gradle8.0+版本已被移除,本文实现了另一种方法)、ASM,以及运行这个项目需要去掌握的组件化以及android工程中模块配置等技术。其中有些技术网上资料太多了,就不在此论述了,会列一些学习过程中看的博客,至于本文主线还是ARouter的实现(非完全实现,为便于理解,好吧,想偷懒😷,有些地方没有按照原版来,不过大体框架上该有的基本上都实现了)。

0 前言

ARouter是个老生常谈的问题,其原理已经被各代androider扒的清楚的不能太清楚了,因此本文不再叙述其原理,重在实践,其原理可参见如下博客:

[1] “终于懂了” 系列:组件化框架 ARouter 完全解析(一) 原理详解 - 掘金

[2] “终于懂了” 系列:组件化框架 ARouter 完全解析(二)APT技术 - 掘金

[3] “终于懂了” 系列:组件化框架 ARouter 完全解析(三)AGP/Transform/ASM - 掘金

所谓路由框架实质为对startActivity(intent)的包装,一个acitivity跳转另一个activity,如下:

val intent = Intent(this, FirstActivty::class.java)
startActivity(intent)

分析其需要什么,intent即为Intent,其需要当前的上下文以及目标页面类。如果只有几个页面手动输入一下这些还算轻松,但随着项目扩大,如果还手输,难免很繁琐。基于此,能否定义一个框架不用手动创建Intent并输入目标页面类。因此,自定义跳转框架的首要原则是减少手动创建Intent的步骤。

1 第一版(幼童版)

基于上述要求,仅减少Intent的重复书写,那么直接给每个目标页面类一个标志,然后使用Map让标志和类进行映射不就行了。

1.1 代码

class FakeARouter01 private constructor() {

    private val groupMap = mutableMapOf<String, Class<*>>(
        "firstActivity" to FirstActivity::class.java
)

    fun navigation(path: String, context: Context) {
        groupMap[path]?.let  { destination ->
        val intent = Intent(context, destination)
            context.startActivity(intent)
            Log.d("FakeARouter01", "跳转成功:$path")
        } ?: run {
            Log.e("FakeARouter01", "No activity found for path: $path")
        }
    }

    companion object {
        fun newInstance() = FakeARouter01()
    }
}

1.2 小结

分析第一版代码,使用groupMap存映射关系,如果页面多了,也还需手动加入,似乎不是很优雅。有没有什么不需要手动加入groupMap的方法呢?

2 第二版(注解+反射)

基于上述问题,可引入注解,一为标明可跳转的activity,二为携带path。其次,标明了注解还需将标了注解的类识别以及加入groupMap,在此版中使用运行时反射获取。

2.1 代码

(1)Route注解

@Target(AnnotationTarget.CLASS)
@Retention(AnnotationRetention.RUNTIME)
annotation class Route(
    val path: String
)

(2)获取带有Route注解的类,以及相应的path

此代码主要参考此篇文章:Android 获取apk中的所有类_android 获取所有类-CSDN博客,运行时获取到所有的类,从中挑出带有Route注解的类。

public class AntiPlugging {
    private static BaseDexClassLoader mClassLoader = (BaseDexClassLoader) Thread.currentThread().getContextClassLoader();

    public static void checkApp(Context context, Consumer<Map<String, Class<?>>> callback) {
        if (mClassLoader == null) {
            mClassLoader = (BaseDexClassLoader) context.getClassLoader();
        }
        new Thread(() -> {
            Map<String, Class<?>> pathToClz = new HashMap<>();
            Set<String> list = getAllClassesName(context.getPackageName()); // 获取除去库中所有的类的类名
            List<Class<?>> routeAnnotationClasses = getRouteAnnotationClasses(list, Route.class); // 获取带有Route注解的类
            if (!routeAnnotationClasses.isEmpty()) {
                for (Class<?> clz : routeAnnotationClasses) {
                    Log.d("AntiPlugging anntotation", clz.getName());
                    String path = getRouteAnnotationPath(clz); // 获取path,与相应类形成映射对
                    if (path != null) {
                        pathToClz.put(path, clz);
                    }
                }
            }
            if (callback != null) {
                callback.accept(pathToClz);
            }
        }).start();
    }

    private static String getRouteAnnotationPath(Class<?> clazz) {
        Route annotation = clazz.getAnnotation(Route.class);
        String path = null;
        if (annotation != null) {
            path = annotation.path();
        }
        return path;
    }

    private static List<Class<?>> getRouteAnnotationClasses(Set<String> allClasses, Class<? extends Annotation> annotationClass) {
        List<Class<?>> annotatedClasses = new ArrayList<>();
        for (String className: allClasses) {
            try {
                Class<?> clazz = Class.forName(className);
                if (clazz.isAnnotationPresent(annotationClass)) {
                    annotatedClasses.add(clazz);
                }
            } catch (ClassNotFoundException e) {
                throw new RuntimeException(e);
            }
        }
        return annotatedClasses;
    }

    private static Set<String> getAllClassesName(String targetPackageName) {
        Set<String> classNames = new HashSet<>();
        try {
            Field f_pathList = getField("pathList", BaseDexClassLoader.class);
            Field f_dexElements = getField("dexElements", getClass("dalvik.system.DexPathList"));
            Field f_dexFile = getField("dexFile", getClass("dalvik.system.DexPathList$Element"));

            Object pathList = getObjectFromField(f_pathList, mClassLoader);
            Object[] list = (Object[]) getObjectFromField(f_dexElements, pathList);

            String prefix = targetPackageName + '.';

            if (list == null) {
                return classNames;
            }
            if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) {
                for (Object o: list) {
                    DexFile file = (DexFile) getObjectFromField(f_dexFile, o);
                    if (file != null) {
                        Enumeration<String> entries = file.entries();
                        while (entries != null && entries.hasMoreElements()) {
                            String className = entries.nextElement();
                            if (className.startsWith(prefix)) {
                                classNames.add(className);
                            }
                        }
                    }
                }
            } else {
                Class<?> dexFile = getClass("dalvik.system.DexFile");
                if (dexFile != null) {
                    Field f_mCookie = getField("mCookie", dexFile);
                    if (f_mCookie != null) {
                        Method classNameList = getMethod("getClassNameList", dexFile, Object.class);
                        if (classNameList != null) {
                            for (Object o : list) {
                                Object file = getObjectFromField(f_dexFile, o);
                                Object o_mCookie = getObjectFromField(f_mCookie, file);
                                if (o_mCookie == null) {
                                    continue;
                                }
                                String[] classList = (String[]) classNameList.invoke(file, o_mCookie);
                                if (classList != null) {
                                    for (String className: classList) {
                                        if (className.startsWith(prefix)) {
                                            classNames.add(className);
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
        return classNames;
    }

    private static Field getField(String field, Class<?> Class) throws NoSuchFieldException {
        if (field != null && Class != null) {
            return Class.getDeclaredField(field);
        }
        return null;
    }

    private static Method getMethod(String method, Class<?> Class, Class<?>... parameterTypes) throws NoSuchMethodException {
        if (Class != null && parameterTypes != null) {
            Method res = Class.getDeclaredMethod(method, parameterTypes);
            res.setAccessible(true);
            return res;
        }
        return null;
    }

    private static Class<?> getClass(String className) throws ClassNotFoundException {
        return mClassLoader.loadClass(className);
    }

    private static Object getObjectFromField(Field field, Object arg) throws IllegalAccessException {
        if (field != null && arg != null) {
            field.setAccessible(true);
            return field.get(arg);
        }
        return null;
    }
}

(3)注入groupMap

class FakeARouter01 private constructor() {
    
    fun init(context: Context) {
       AntiPlugging.checkApp(context, Consumer  { res ->
        res.forEach { (key, value) ->
                groupMap.putIfAbsent(key, value)
                Log.d("FakeARouter01", "获取及注入:$key  ${value.simpleName}")
            }
        } )
    }

    fun navigation(path: String, context: Context) {
        groupMap[path]?.let  { destination ->
        val intent = Intent(context, destination)
            context.startActivity(intent)
            Log.d("FakeARouter01", "跳转成功:$path")
        } ?: run {
            Log.e("FakeARouter01", "No activity found for path: $path")
        }
    }

    companion object {

        private val groupMap = mutableMapOf<String, Class<*>>()

        fun newInstance() = FakeARouter01()
    }
}

运行程序,获取path与相应的类、注入group、跳转均可行:

2.2 小结

第二版在第一版的基础上更加省事了,但也还有不足:第一,在app运行启动时获取包中所有的类以及映射路径和相应的类,会严重增加app负担及其启动时间;第二,groupMap声明为静态变量(此种情况下也只能声明为静态变量),那么其在整个app进程存在过程中都会存在内存中,如果path较少没啥问题,但随着path增多,会占据很多内存。第三,为了不阻塞主线程,这里使用的是子线程解析所有类,如果解析的类太多,会很耗时,就会存在要跳转时groupMap中还不存在相关映射。在这里进行了实验,给子线程加了10秒sleep,确实出现了这种情况。

3 第三版(apt+javapoet)

针对第二版的问题,可以使用java自带的apt技术,即注解处理器,在编译期去处理相应的注解以及获取到相应的类。其次,获取到相应的类应该如何将其加载到内存,由于apt是编译时技术,因此此时加载没啥用。

此时就可以引入javapoet技术,生成一个帮助类函数,如下:

fun loadInto( atlas: Map<String, RouteMeta>){
    atlas.put(key, RouteMeta.build(...))
} 

在app启动时或者要跳转时去调用这个函数,就可将映射加载到内存中去,这样就可解决上述第二版中的三个问题。

3.1 代码

(1)路由元信息RouteMeta,存储路由的相关信息

在这里我就写的比较简单了,ARouter这里可写了很多东西,而且还将其包装,实现了一个postcard,不过在这里实现页面的跳转,如下几个也够了。

class RouteMeta public constructor(
    var path: String,
    var group: String,
    var destination: Class<*>? = null,
    var className: ClassName? = null

){

    constructor(path: String, group: String, destination: Class<*>?) : this(path, group, destination, null)
    constructor(path: String, group: String, className: ClassName?) : this(path, group, null, className)

    companion object {
        fun build(path: String, group: String, destination: Class<*>?) : RouteMeta {
            return RouteMeta(path, group, destination)
        }
    }
}

(2)使用apt处理Route注解,同时使用javapoet生成加载映射关系的类:

ARouter在该processor中,还进行了参数的处理,因为跳转activity可以携带一些参数给另一个activity,本质上也是在生成的group帮助类函数中将参数以map的形式存储然后再注入RouteMeta中的。

@AutoService(Processor::class)
@SupportedAnnotationTypes("com.example.libarouter.annotation.Route")
class RouteProcessor : BaseProcessor() {

    private val groupMap = mutableMapOf<String, MutableSet<RouteMeta>>() //存各模块带有Route注解的类信息
    private val rootMap = mutableMapOf<String, String>() //存各模块的group类

    override fun init(p0: ProcessingEnvironment?) {
        super.init(p0)
    }

    override fun process(
        annotations: MutableSet<out TypeElement>?,
        roundEnv: RoundEnvironment?
    ): Boolean {
        println(">>> Route start process... <<<")
        annotations?.takeIf { it.isNotEmpty() } ?.let {
        val routeElements = roundEnv?.getElementsAnnotatedWith(Route::class.java)
            try {
                println("routeElements size: " + routeElements?.size)
                parseRoute(routeElements)
            } catch (e: Exception) {
                println("Error: " + e.message)
            }
            return true
        }
        return false
    }

    private fun parseRoute(elements: Set<Element>?) {
        elements?.takeIf { it.isNotEmpty() } ?.forEach { element ->
        if (element.kind == ElementKind.CLASS) { //类-类型
                val typeElement = element as TypeElement
                val className = typeElement.simpleName.toString()
                val packageName = processingEnv.elementUtils.getPackageOf(typeElement).toString()

                val routeAnnotation = typeElement.getAnnotation(Route::class.java)
                val routeMeta = RouteMeta(routeAnnotation.path, moduleName!!, ClassName.get(packageName, className))

                groupMap.getOrPut(routeMeta.group) { mutableSetOf() } .add(routeMeta)
            }
        }

        for ((groupName, groupData) in groupMap) {
            generateGroupClass(groupName, groupData) //通过javapoet生成group类
        }
        generateRootClass() // 通过javapoet生成root类
    }

    /**生成如下类:
    * public class ARouter$$Group$$moduleName implements IRouteGroup {
    *   @Override
    *   public void loadInto(Map<String, RouteMeta> atlas) {
    *     atlas.put(moduleName, RouteMeta.build(...));
    *   }
    * }
    */
    private fun generateGroupClass(groupName: String, groupData: Set<RouteMeta>) {

        // 生成参数
        val inputMapTypeOfGroup = ParameterizedTypeName.get(
            ClassName.get(Map::class.java),
            ClassName.get(String::class.java),
            ClassName.get(RouteMeta::class.java)
        )

        val groupParamSpec = ParameterSpec.builder(inputMapTypeOfGroup, "atlas").build()

        // 生成loadInto方法
        val loadIntoMethodBuilder = MethodSpec.methodBuilder("loadInto")
            .addAnnotation(Override::class.java)
            .addModifiers(Modifier.PUBLIC)
            .returns(Void.TYPE)
            .addParameter(groupParamSpec)

        groupData.forEach { routeMeta ->

            generateProviderClass(routeMeta) //通过javapoet生成provider类

            loadIntoMethodBuilder.addStatement(
                "atlas.put($S, $T.Companion.build($S, $S, $T.class))",
                routeMeta.path,
                ClassName.get(RouteMeta::class.java),
                routeMeta.path,
                routeMeta.group,
                routeMeta.className
            )
        }

    val groupClassName = "FakeARouter${SEPARATORS}Group${SEPARATORS}$moduleName"
        val groupClass = TypeSpec.classBuilder(groupClassName)
            .addModifiers(Modifier.PUBLIC)
            .addSuperinterface(ClassName.get("com.example.libarouter.model", "IRouteGroup"))
            .addMethod(loadIntoMethodBuilder.build())
            .build()

        rootMap[groupName] = groupClassName

        val javaFile = JavaFile.builder(PACKAGE_OF_GENERATE_FILE, groupClass).build()

        try {
            javaFile.writeTo(processingEnv.filer)
        } catch (e: Exception) {
            println("error: ${e.message}")
        }
    }

    /**生成如下类:
    * public class FakeARouter$$Root$$moduleName implements IRouteRoot {
    *   @Override
    *   public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
    *     routes.put("moduleName", ARouter$$Group$$moduleName.class);
    *   }
    * }
    */
    private fun generateRootClass() {
        val inputMapTypeOfRoot = ParameterizedTypeName.get(
            ClassName.get(Map::class.java),
            ClassName.get(String::class.java),
            ParameterizedTypeName.get(
                ClassName.get(Class::class.java),
                WildcardTypeName.subtypeOf(ClassName.get("com.example.libarouter.model", "IRouteGroup"))
            )
        )


        val rootParamSpec = ParameterSpec.builder(inputMapTypeOfRoot, "routes").build()

        val loadIntoMethodOfRootBuilder = MethodSpec.methodBuilder("loadInto")
            .addAnnotation(Override::class.java)
            .addModifiers(Modifier.PUBLIC)
            .returns(Void.TYPE)
            .addParameter(rootParamSpec)

        rootMap.forEach { (moduleName, className) ->
            loadIntoMethodOfRootBuilder.addStatement(
                "routes.put($S, $T.class)",
                moduleName,
                ClassName.get(PACKAGE_OF_GENERATE_FILE, className)
            )
        }

        val rootFileName = "FakeARouter${SEPARATORS}root${SEPARATORS}$moduleName"
        val rootCLass = TypeSpec.classBuilder(rootFileName)
            .addSuperinterface(ClassName.get("com.example.libarouter.model", "IRouteRootJava"))
            .addModifiers(Modifier.PUBLIC)
            .addMethod(loadIntoMethodOfRootBuilder.build())
            .build()
        val javaFile = JavaFile.builder(PACKAGE_OF_GENERATE_FILE, rootCLass).build()

        try {
            javaFile.writeTo(processingEnv.filer)
        } catch (e: Exception) {
            println("error: ${e.message}")
        }
    }

    private fun generateProviderClass(routeMeta: RouteMeta) {
        // 生成FakeARouter$$Providers$$moduleName类,模块对外的统一api接口
        // 与上述两个生成方式差不多
        // 由于我的demo目前用不到就不写啦
    }

}

(2)FakeARouter对外api

代码:

class FakeARouter private constructor() {

    // 就在这里就简单的用个参数存了
    // 正版的使用了单独的一个存储类Warehouse
    private var groupMap: MutableMap<String, RouteMeta> = mutableMapOf()

    fun init(routeGroup: IRouteGroup) {
        routeGroup.loadInto(groupMap) // 调用生成的函数将其注入groupMap中
    }

    companion object {
        private class Holder {
            companion object {
                val INSTANCE = FakeARouter()
            }
        }

        fun getInstance(): FakeARouter {
            return Holder.INSTANCE
        }
    }

    fun navigation(path: String, context: Context) {
        groupMap[path]?.let { routeMeta ->
        val intent = Intent(context, routeMeta.destination)
            context.startActivity(intent)
            Log.d("FakeARouter01", "跳转成功:$path")
        } ?: run {
            Log.e("FakeARouter01", "No activity found for path: $path")
        }
    }
}

(3)app启动加载以及跳转

// 加载
class FakeARouterApp : Application() {
    override fun onCreate() {
        super.onCreate()
        FakeARouter.getInstance().init(`FakeARouter$$Group$$app`()) // 直接调用生成的类
    }
}
// 跳转
@Route(path = "firstActivity")
class FirstActivity : Activity() {

    private lateinit var activityFirstBinding: ActivityFirstBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        activityFirstBinding = ActivityFirstBinding.inflate(layoutInflater)
        setContentView(activityFirstBinding.root)

        activityFirstBinding.btn.setOnClickListener {
            FakeARouter.getInstance().navigation("secondActivity", this)
        }
}
}

(4)跳转成功

3.2 小结

至此基本上可用了,也基本上摆脱了手动注册等工作,但还有一个地方需要手动,即app启动时调用loadInto函数,将跳转映射加载入内存。

4 第四版(gradle+Transform)

为了做到极致的自动化,ARouter引入了Transform以及ASM技术。Transform扫描到要修改的类,再使用ASM修改class文件,进行代码注入。由于Transform技术是打包过程中的技术,因此实现这个需要自定义gradle插件,所以此版还需采用自定义gradle插件技术。

(1)自定义gradle-plugin

自定义gradle插件在此就不进行论述了,可参考以下资料,虽然在定义插件的时候也踩了一些坑:

  [1] Android Gradle学习(四)- gradle插件开发和调试AOP思想 - 掘金

  [2] Android 自定义Gradle插件(一): 创建自定义插件与发布自定义Gradle插件 - 掘金

  [3] 如何使用Android Studio自定义Gradle插件(gradle7.0)Android Stuido 自定义 G - 掘金

⚠️:新建这个目录时不要一下子这样新建,虽然最后看起来是这样,但它不生效!【裂开】,需要先建META-INF目录,再建gradle-plugins目录。

⚠️:Android Stdio的gradle一般只显示test task,需要将如下配置打开,打开后gradle就有上面的所示的其他任务了:

2)transform被移除

进入代码编写,按理说一切应正常进行,可是出意外了,gradle 8.0及以上,API 'android.registerTransform' 被移除了【裂开】,此外官网说没有单一的替代API【裂开】:

[1] Transform API 废弃了,路由插件怎么办? - 掘金

[2] github.com/JailedBird/…

[3] github.com/FlyJingFish…

[4] juejin.cn/post/737467…

(3)此demo实现的gradle-plugin目录如下:

  • PluginLaunch主要为了启动插件
  • ScanAndTrandformAllClassesTask是一个Task,继承自DefaultTask,实现了类似transform的功能,即扫描所有的class文件以及jar包,获取到第三版生成的帮助类以及LogisticsCenter类
  • ScanSetting是一个数据存储类,主要为了存储实现了IRouteRoot、IInterceptorGroup、IProviderGroup这三个接口的类
  • ScanUtils是扫描帮助类,识别帮助类以及LogisticsCenter类
  • InjectUtils是ASM代码注入类,在LogisticsCenter类的loadRouterMap方法中注入如下代码:
register("com.example.fakearouter01.routes.FakeARouter$$root$$app");

4.1 代码

ScanSetting、ScanUtils、InjectUtils的代码分别对应原版arouter的ScanSetting、ScanUtil、RegisterCodeGenerator,代码差不多,为减少篇幅就不列出了。

(1)PluginLaunch

class PluginLaunch : Plugin<Project> {

    override fun apply(project: Project) {
        // 仅application模块用这个插件
        if (project.plugins.hasPlugin(AppPlugin::class.java)) {
            println("====start apply plugin====")
            val androidComponents =
                project.extensions.getByType(AndroidComponentsExtension::class.java)

            androidComponents.onVariants { variant ->

                val providerScanAndTransformAllClassesTask = project.tasks.register(
                    "${variant.name}ScanAndTransformAllClassesTask",
                    ScanAndTransformAllClassesTask::class.java
)
                // 可以换成ScopedArtifacts.Scope.Project,提升编译速度
                variant.artifacts.forScope(ScopedArtifacts.Scope.ALL)
                    .use(providerScanAndTransformAllClassesTask)
                    .toTransform(
                        ScopedArtifact.CLASSES,
                        ScanAndTransformAllClassesTask::allJars,
                        ScanAndTransformAllClassesTask::allDirectories,
                        ScanAndTransformAllClassesTask::output
                    )
            }
}
    }
}

(2)ScanAndTrandformAllClassesTask

abstract class ScanAndTransformAllClassesTask : DefaultTask() {
    @get:InputFiles
    abstract val allDirectories: ListProperty<Directory>

    @get:InputFiles
    abstract val allJars: ListProperty<RegularFile>

    @get:OutputFile
    abstract val output: RegularFileProperty

    @TaskAction
    fun taskAction() {
        val leftSlash = File.separator == "/"
        // 三种目标类的接口类:root、provider、interceptor
        val targetList: List<ScanSetting> = listOf(
            ScanSetting("IRouteRoot"),
            ScanSetting("IInterceptorGroup"),
            ScanSetting("IProviderGroup"),
        )
        JarOutputStream(output.asFile.get().outputStream()).use { jarOutput ->
        // Scan directory
            allDirectories.get().forEach { directory ->
                val directoryPath =
                    if (directory.asFile.absolutePath.endsWith(File.separatorChar)) {
                        directory.asFile.absolutePath
                    } else {
                        directory.asFile.absolutePath + File.separatorChar
                    }
                //println("Directory is $directoryPath")
                directory.asFile.walk().forEach { file ->
                        if (file.isFile) {
                        val entryName = if (leftSlash) {
                            file.path.substringAfter(directoryPath)
                        } else {
                            file.path.substringAfter(directoryPath).replace(File.separatorChar, '/')
                        }
                        //println("\tDirectory entry name $entryName")
                        if (entryName.isNotEmpty()) {
                            if (entryName.startsWith("com/example/fakearouter01/routes/")) {
                                println("\tfakearouter routes name $entryName")
                                file.inputStream().use { input ->
                                    ScanUtils.scanClass(input, targetList, false)
                                }
                        }

                            // 流只允许读,需Copy到另外的流
                            file.inputStream().use { input ->
                                jarOutput.saveEntry(entryName, input)
                            }
                        }
                    }
                }
            }


            var originInject: ByteArray? = null
            // 获取
            val jars = allJars.get().map { it.asFile }
            for (sourceJar in jars) {
                //println("\tjar file is $sourceJar")
                val jar = JarFile(sourceJar)
                val entries = jar.entries()
                while (entries.hasMoreElements()) {
                    val entry = entries.nextElement()
                    try {
                        // Exclude directory
                        if (entry.isDirectory || entry.name.isEmpty()) {
                            continue
                        }
                        //println("\tJar entry is ${entry.name}")
                        if (entry.name != "com/example/fakearouter_api/LogisticsCenter.class") {
                            // Scan and choose
                            if (entry.name.startsWith("com/example/fakearouter01/routes/")) {
                                jar.getInputStream(entry).use { inputs ->
                                    ScanUtils.scanClass(inputs, targetList, false)
                                }
                            }
                            // Copy
                            jar.getInputStream(entry).use { input ->
                                jarOutput.saveEntry(entry.name, input)
                            }
                        } else {
                            // Skip,在后面进行asm注入
                            println("Find inject byte code, Skip ${entry.name}")
                            jar.getInputStream(entry).use { inputs ->
                                originInject = inputs.readAllBytes()
                                 println("Find before originInject is ${originInject?.size}")
                            }
                        }
                    } catch (e: Exception) {
                        // Format Optimize: exclude [java.util.zip.ZipException: duplicate entry: META-INF/MANIFEST.MF]
                        if(e is ZipException && e.message?.contains("META-INF/MANIFEST.MF") == true){
                            // Skip META-INF/MANIFEST.MF
                        }else{
                            println("[Warning] Merge [jar:entry] ${jar.name}:${entry.name}, error is $e ")
                        }
                    }
                }
                jar.close()
            }


            // Do inject,asm
            println("Start inject byte code")
            if (originInject == null) { // Check
                error("Can not find ARouter inject point, Do you import ARouter?")
            }
            val resultByteArray = InjectUtils.referHackWhenInit( //注入函数
                ByteArrayInputStream(originInject), targetList
            )
            // 保存到jar包
            jarOutput.saveEntry(
                "com/example/fakearouter_api/LogisticsCenter.class",
                ByteArrayInputStream(resultByteArray)
            )
            println("Inject byte code successful")
        }
}

    private fun JarOutputStream.saveEntry(entryName: String, inputStream: InputStream) {
        this.putNextEntry(JarEntry(entryName))
        IOUtils.copy(inputStream, this)
        this.closeEntry()
    }
}

(3)LogisticsCenter

public class LogisticsCenter {
    private static Context mContext;
    private static boolean registerByPlugin;

    public synchronized static void init(Context context) {
        mContext = context;
        Log.i("FakeARouter", "LogisticsCenter init");
        loadRouterMap();
    }

    private static void loadRouterMap() {
        registerByPlugin = false;
    }

    private static void register(String className) {
        if (!TextUtils.isEmpty(className)) {
            try {
                if (registerByPlugin) {
                    Log.i("FakeARouter", "load arouter map by plugin");
                } else {

                    // 正版这里加了缓存

                    Class<?> clazz = Class.forName(className);
                    Log.d("FakeARouter", "LogisticsCenter register" + className);
                    Object obj = clazz.getConstructor().newInstance();
                    if (obj instanceof IRouteRoot) {
                        registerRouteRoot((IRouteRoot) obj);
                    }
                }
            } catch (Exception e) {
                throw (RuntimeException) e;
            }
        }
    }

    private static void registerRouteRoot(IRouteRoot routeRoot) {
        markRegisteredByPlugin();
        if (routeRoot != null) {
            routeRoot.loadInto(WareHouse.INSTANCE.getGroupIndex());
        }
    }

    private static void markRegisteredByPlugin() {
        if (!registerByPlugin) {
            registerByPlugin = true;
        }
    }

    public static void completion(RouteMeta routeMeta) {
        doCompletion(routeMeta);
    }

    public static void doCompletion(RouteMeta routeMeta) {

        RouteMeta completionRouteMeta = WareHouse.INSTANCE.getRoutes().get(routeMeta.getPath());
        if (completionRouteMeta == null) {
            Class<? extends IRouteGroup> groupMeta = WareHouse.INSTANCE.getGroupIndex().get(routeMeta.getGroup());
            try {
                IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
                Log.i("FakeARouter", "LogisticsCenter doCompletion: " + iGroupInstance.getClass().getName());
                iGroupInstance.loadInto(WareHouse.INSTANCE.getRoutes());
            } catch (Exception e) {
                throw (RuntimeException) e;
            }
            doCompletion(routeMeta);
        } else {
            routeMeta.setDestination(completionRouteMeta.getDestination());
        }

    }
}

(4)FakeARouter-api

class FakeARouter private constructor() {

    private var groupMap: MutableMap<String, RouteMeta> = mutableMapOf()

    fun init(routeGroup: IRouteGroup) {
        //routeGroup.loadInto(groupMap)
    }

    fun init(application: Application) {
        LogisticsCenter.init(application)
    }

    companion object {
        private class Holder {
            companion object {
                val INSTANCE = FakeARouter()
            }
        }

        fun getInstance(): FakeARouter {
            return Holder.INSTANCE
        }
    }

    fun navigation(path: String, context: Context) {
        val routeMeta = RouteMeta.build(path, "app", null)
        LogisticsCenter.completion(routeMeta)
        val intent = Intent(context, routeMeta.destination)
        context.startActivity(intent)
        Log.d("FakeARouter", "跳转成功:$path")
    }
}

(5)注入结果

⚠️:用kotlin写某些类有些问题,比如IRouteRoot(其实也行,但比较麻烦)、LogisticsCenter这两个类,运行正常,但功能实现不了,后面反编译出来一看,代码不对劲,所以后面就换成java编写了

4.2 小结

至于此,用于路由的话,基本上就可行了,不需要手动去写Intent等操作了。但相比ARouter还有些粗糙。ARouter考虑的比较周全,其还实现了在运行时需要了再加载映射进来,还加入了缓存、路由拦截,考虑了线程安全等问题。由于本文主要为了熟悉其中涉及的技术,实现这些基本上把这些技术都用了一遍,至于ARouter的其他部分目前以理解为主,实现的话后续再去完善。

5 总结

本文主要运用apt、gradle-plugin、ASM等技术,以一个初学者的角度,基于ARouter思考一个路由框架由0到1的实现过程,以及使用自定义Task解决gradle 8.0及以上,API 'android.registerTransform' 被移除的问题。

完结,撒花🎉🎉🎉。

写点感想:前前后后断断续续历时好几个月,终于实现了。最大的困难不是写代码,而是调环境,引入工程,一调一个不吱声。自以为操作无误,实际上也是根据资料一步步来的,可是在运行时就是有问题【😣】。但是,放弃吧,又不甘心,不放弃吧,头发不保【拿捏🫴】。

可谓是:代码虐我千百遍,我待代码如初恋呀