Android手写自己的路由SDK

实现自己的路由框架

​ 在较大型的Android app中常会用到组件化技术,针对不同的业务/基础功能对模块进行划分,从上到下为壳工程、业务模块、基础模块。其中业务模块依赖基础模块,壳工程依赖业务模块。同级的横向模块(比如多个业务模块)因为不能相互依赖,怎样实现它们之间的路由跳转呢?

​ 我尝试使用kotlin实现一下自己的路由框架,由简到繁、由浅入深。刚开始只求有功能,不求完美,一步步最终优化到比较完善的样子。

1.菜鸟版

在这里插入图片描述

​ 工程中只包含上图中的5个模块,其中main、businessa、businessb、routersdk均为Android Library,只有app是可运行的application。

​ app作为壳工程依赖着剩下的4个模块,main、businessa、businessb作为业务模块依赖着routersdk这个基础模块。

​ 此时依据模块之间的依赖关系,想要实现路由其实只需要在基础模块routersdk中创建一个Router类维护一个映射表并实现两个关键方法。

​ 一个方法register()用来注册路由跳转的键值对,键为path字符串,value为跳转的XXXActivity的class即可。

​ 另一个方法jumpTo()用来具体跳转,实现的时候传入对应的路由path参数,当path在映射表中时直接调用Intent的startActivity()方法完成跳转即可。

‘’

package com.lllddd.routersdkimport android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast/*** author: lllddd* created on: 2024/5/2 14:34* description:路由类*/
object Router {private val routerMap: MutableMap<String, Class<out Activity>> = mutableMapOf()fun register(key: String, value: Class<out Activity>) {routerMap[key] = value}fun jumpTo(activity: Activity, path: String, params: Bundle? = null) {if (!routerMap.containsKey(path)) {Toast.makeText(activity, "找不到路由目的页面!!!", Toast.LENGTH_LONG).show()return}val destinationActivity = routerMap[path]val intent = Intent(activity, destinationActivity)if (params != null) {intent.putExtras(params)}activity.startActivity(intent)}
}

​ 接着在Application的子类MyRouterApp中调用Router的注册方法,将3个业务模块的页面路由分别注册进Router中的路由表,那么路由的注册就已完成。

​ ‘’

package com.lllddd.myrouter.appimport android.app.Application
import com.lllddd.businessa.BusinessAMainActivity
import com.lllddd.businessb.BusinessBMainActivity
import com.lllddd.main.MainActivity
import com.lllddd.routersdk.Router/*** author: lllddd* created on: 2024/5/2 15:06* description:*/
class MyRouterApp : Application() {override fun onCreate() {super.onCreate()Router.register("businessa/main", BusinessAMainActivity::class.java)Router.register("businessb/main", BusinessBMainActivity::class.java)Router.register("app/main", MainActivity::class.java)}
}

​ 上方我们注册了3条路由关系。businessa/main对应BusinessAMainActivity,businessb/main对应BusinessBMainActivity,app/main对应MainActivity。

​ 此时假如要想在app模块中的MainActivity页面路由到businessa模块的BusinessAMainActivity页面或businessb模块的BusinessBMainActivity页面,只需要如下这样写。

​ ‘’

package com.lllddd.mainimport android.os.Bundle
import android.widget.Button
import androidx.appcompat.app.AppCompatActivity
import com.lllddd.routersdk.Routerclass MainActivity : AppCompatActivity() {private lateinit var mBtnJumpA: Buttonprivate lateinit var mBtnJumpB: Buttonoverride fun onCreate(savedInstanceState: Bundle?) {super.onCreate(savedInstanceState)setContentView(R.layout.activity_main)initWidgets()}private fun initWidgets() {mBtnJumpA = findViewById(R.id.btn_jump_a)mBtnJumpA.setOnClickListener {val bundle = Bundle()bundle.putString("param", "好好学习")Router.jumpTo(this, "businessa/main", bundle)}mBtnJumpB = findViewById(R.id.btn_jump_b)mBtnJumpB.setOnClickListener {val bundle = Bundle()bundle.putString("param", "天天向上")Router.jumpTo(this, "businessb/main", bundle)}}
}

​ 此时我们只需要将path传给Router.jumpTo()作为参数就能正确跳转到同级别的业务模块中。

在这里插入图片描述
在这里插入图片描述
在这里插入图片描述

2.进阶版

​ 菜鸟版方式很容易就让我实现了一个菜鸟版的路由框架。但是它存在一些很明显的问题,首先就是注册关系必须要在MyRouterApp(Application)类中去维护,那么在模块众多多人协作开发时完全没有解耦,造成了MyRouterApp难以维护,模块职责不清的问题。其次,app模块中实际上没有任何页面,只是一个壳工程,但是它也要依赖routersdk,这样的情况也不合理。

​ 我就在想能不能让路由关系注册这样的操作分散在各自的业务模块中,这样就能很好地解决上面两个问题。

​ 很自然的想到在routersdk模块中定义一个接口用来约束装载路由的方法。

‘’

package com.lllddd.routersdkimport android.app.Activity/*** author: lllddd* created on: 2024/5/2 20:12* description:各个业务模块装载路由信息的接口*/
interface ILoadRouters {/*** 装载路由信息** @param loadRouters 路由表*/fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>)
}

​ 之后在每个业务模块中新建一个路由类实现该接口。

​ main模块中的实现类如下。

‘’

package com.lllddd.main.routerimport android.app.Activity
import com.lllddd.main.MainActivity
import com.lllddd.routersdk.ILoadRouters/*** author: lllddd* created on: 2024/5/2 20:21* description:业务main模块的路由装载类*/
class MainRouter : ILoadRouters {override fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>) {routerMap["main/main"] = MainActivity::class.java}
}

​ businessa模块中的实现类如下。

‘’

package com.lllddd.businessa.routerimport android.app.Activity
import com.lllddd.businessa.BusinessAMainActivity
import com.lllddd.routersdk.ILoadRouters/*** author: lllddd* created on: 2024/5/2 20:16* description:业务模块A的路由装载类*/
class BusinessARouter : ILoadRouters {override fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>) {routerMap["/businessa/main"] = BusinessAMainActivity::class.java}
}

​ businessb模块中的实现类如下。

‘’

package com.lllddd.businessb.routerimport android.app.Activity
import com.lllddd.businessb.BusinessBMainActivity
import com.lllddd.routersdk.ILoadRouters/*** author: lllddd* created on: 2024/5/2 20:19* description:业务模块B的路由装载类*/
class BusinessBRouter : ILoadRouters {override fun loadRouters(routerMap: MutableMap<String, Class<out Activity>>) {routerMap["businessb/main"] = BusinessBMainActivity::class.java}
}

​ 这样一来,我们只需要在Router类中增加一个init()方法,在该方法中调用各模块的loadRouters()方法即可。

‘’

package com.lllddd.routersdkimport android.app.Activity
import android.content.Intent
import android.os.Bundle
import android.widget.Toast/*** author: lllddd* created on: 2024/5/2 14:34* description:路由类*/
object Router {private val routerMap: MutableMap<String, Class<out Activity>> = mutableMapOf()fun init() {ABusinessRouter().loadRouters(routerMap)BBusinessRouter().loadRouters(routerMap)MainRouter().loadRouters(routerMap)}//    fun register(key: String, value: Class<out Activity>) {
//        routerMap[key] = value
//    }fun jumpTo(activity: Activity, path: String, params: Bundle? = null) {if (!routerMap.containsKey(path)) {Toast.makeText(activity, "找不到路由目的页面!!!", Toast.LENGTH_LONG).show()return}val destinationActivity = routerMap[path]val intent = Intent(activity, destinationActivity)if (params != null) {intent.putExtras(params)}activity.startActivity(intent)}
}

​ 此时MyRouterApp中只需要直接调用Router的init()初始化方法即可。

‘’

package com.lllddd.myrouter.appimport android.app.Application
import com.lllddd.routersdk.Router/*** author: lllddd* created on: 2024/5/2 15:06* description:*/
class MyRouterApp : Application() {override fun onCreate() {super.onCreate()// 初始化路由SDKRouter.init()}
}

​ 思路虽然没错,但是Router中的init()方法却是飘红的,这里作为基础模块,怎么可能拿到业务模块的类引用从而实例化出ABusiniessRouter、BBusinissRouter、MainRouter呢?

​ 所以当前的init()方法一定是行不通的,既然基础模块不能直接使用上层业务模块中的类,那我们只能重新想办法。

​ 此处突然想到了一个好办法,那就是反射,我在这里反射出来ABusiniessRouter、BBusinissRouter、MainRouter这3个对象不就好了。说干就干,改造后的init()代码是这样的。

‘’

fun init() {
//        ABusinessRouter().loadRouters(routerMap)
//        BBusinessRouter().loadRouters(routerMap)
//        MainRouter().loadRouters(routerMap)val aBizClazz = Class.forName("com.lllddd.businessa.router.BusinessARouter")val aBizRouter = aBizClazz.newInstance()val methodABiz = aBizClazz.methods.find { it.name == "loadRouters" }methodABiz?.invoke(aBizRouter, routerMap)val bBizClazz = Class.forName("com.lllddd.businessb.router.BusinessBRouter")val bBizRouter = bBizClazz.newInstance()val methodBBiz = bBizClazz.methods.find { it.name == "loadRouters" }methodBBiz?.invoke(bBizRouter, routerMap)val mainClazz = Class.forName("com.lllddd.main.router.MainRouter")val mainRouter = mainClazz.newInstance()val methodMain = mainClazz.methods.find { it.name == "loadRouters" }methodMain?.invoke(mainRouter, routerMap)}

​ 看起来确实不再报错了,demo也正常运行。但是造成的问题是每次增减一个业务模块,就需要在基础模块routersdk的Router类的init()方法中增删代码,而且对应业务模块的路由类是通过反射在此逐一实例化的,用起来也不方便。

​ 那么有没有更好的办法呢?

​ 当然是有,这里需要结合类加载、PMS、反射的知识来综合处理。

​ 我只要想办法遍历整个应用apk,想办法找到满足规则com.lllddd.xxx.router.xxx.kt的kotlin文件完整包路径即可。

​ 此时就需要借助PMS拿到我们的apk。当应用安装后运行时,对应的apk文件其实是在下面这个路径中的

​ /data/app/com.lllddd.myrouter-m-SApQoUtVytou1_nl1aUA==/base.apk

​ 之后可以利用类加载技术中的DexFile匹配正则规则来遍历apk找到符合规则的类路径,即

​ com.lllddd.businessa.router.BusinessARouter
​ com.lllddd.businessb.router.BusinessBRouter
​ com.lllddd.main.router.MainRouter

​ 之后还是在Router的init()方法中利用反射调用每个XXXRouter的loadRouters()方法就能实现路由注册。

​ 我将相关的关键操作封装进ClassHelper类。

‘’

package com.lllddd.routersdkimport android.app.Application
import android.content.Context
import dalvik.system.DexFile
import java.util.regex.Pattern/*** author: lllddd* created on: 2024/5/2 21:43* description:类帮助者*/
class ClassHelper {companion object {/*** 获取当前的apk文件** @param context 应用上下文* @return apk文件路径列表*/private fun getSourcePaths(context: Context): List<String> {val applicationInfo = context.applicationInfoval pathList = mutableListOf<String>()// /data/app/com.lllddd.myrouter-m-SApQoUtVytou1_nl1aUA==/base.apkpathList.add(applicationInfo.sourceDir)if (applicationInfo.splitSourceDirs != null) {val array = applicationInfo.splitSourceDirsfor (ele in array) {pathList.add(ele)}}return pathList}/*** 根据Router类所在包名的正则规则,拿到所有Router的完整包名路径,以便后期反射调用** @param context 应用上下文* @param packageRegex Router类所在包名的正则规则* @return 所有Router的完整包名路径*/fun getFileNameByPackageName(context: Application, packageRegex: String): Set<String> {val set = mutableSetOf<String>()val pathList = getSourcePaths(context)val pattern = Pattern.compile(packageRegex)for (path in pathList) {var dexFile: DexFile? = nulltry {dexFile = DexFile(path)val entries = dexFile.entries()if (entries != null) {while (entries.hasMoreElements()) {val className = entries.nextElement()val matcher = pattern.matcher(className)if (matcher.find()) {set.add(className)}}}} finally {dexFile?.close()}}return set}}
}

​ 之后Router中的init()方法直接调用ClassHelper中的方法并遍历反射即可。

‘’

 fun init(application: Application) {// 方案1:飘红
//        ABusinessRouter().loadRouters(routerMap)
//        BBusinessRouter().loadRouters(routerMap)
//        MainRouter().loadRouters(routerMap)// 方案2:并不优雅
//        val aBizClazz = Class.forName("com.lllddd.businessa.router.BusinessARouter")
//        val aBizRouter = aBizClazz.newInstance()
//        val methodABiz = aBizClazz.methods.find { it.name == "loadRouters" }
//        methodABiz?.invoke(aBizRouter, routerMap)
//
//        val bBizClazz = Class.forName("com.lllddd.businessb.router.BusinessBRouter")
//        val bBizRouter = bBizClazz.newInstance()
//        val methodBBiz = bBizClazz.methods.find { it.name == "loadRouters" }
//        methodBBiz?.invoke(bBizRouter, routerMap)
//
//        val mainClazz = Class.forName("com.lllddd.main.router.MainRouter")
//        val mainRouter = mainClazz.newInstance()
//        val methodMain = mainClazz.methods.find { it.name == "loadRouters" }
//        methodMain?.invoke(mainRouter, routerMap)// 方案3:自动扫包val set = ClassHelper.getFileNameByPackageName(application,"com.lllddd.[a-zA-Z0-9]+\\.router\\.[a-zA-Z0-9]+")for (fileName in set) {val clazz = Class.forName(fileName)val router = clazz.newInstance()val method = clazz.methods.find { it.name == "loadRouters" }method?.invoke(router, routerMap)}}

3.完善版

未完待续…

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/diannao/6134.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

软件杯 深度学习的动物识别

文章目录 0 前言1 背景2 算法原理2.1 动物识别方法概况2.2 常用的网络模型2.2.1 B-CNN2.2.2 SSD 3 SSD动物目标检测流程4 实现效果5 部分相关代码5.1 数据预处理5.2 构建卷积神经网络5.3 tensorflow计算图可视化5.4 网络模型训练5.5 对猫狗图像进行2分类 6 最后 0 前言 &#…

MySQL-逻辑架构

1、MySQL服务器处理客户端请求 MySQL是典型的C/S架构&#xff0c;服务端程序使用 mysqld。实现效果&#xff1a;客户端进程像服务端发送&#xff08;SQL语句&#xff09;&#xff0c;服务器进程处理后再像客户端进程发送 处理结果。 2、connectors 指不同语言中与SQL的交互…

【C++】双指针算法:四数之和

1.题目 2.算法思路 这道题目十分困难&#xff0c;在leetcode上的通过率只有36%&#xff0c;大家要做好心理准备。 在做个题目前强烈建议大家先看看我的上一篇博客&#xff1a;有效三角形个数&#xff0c;看完之后再去leetcode上写一写三数之和&#xff0c;搞懂那两个题目之后…

JavaEE 初阶篇-深入了解 Junit 单元测试框架和 Java 中的反射机制(使用反射做一个简易版框架)

&#x1f525;博客主页&#xff1a; 【小扳_-CSDN博客】 ❤感谢大家点赞&#x1f44d;收藏⭐评论✍ 文章目录 1.0 Junit 单元测试框架概述 1.1 使用 Junit 框架进行测试业务代码 1.2 Junit 单元测试框架的常用注解&#xff08;Junit 4.xxx 版本&#xff09; 2.0 反射概述 2.1 获…

计算机毕业设计php自行车在线租赁管理系统-vue+mysql

本系统的开发使获取自行车在线租赁管理系统信息能够更加方便快捷&#xff0c;同时也使自行车在线租赁管理系统管理信息变的更加系统化、有序化。系统界面较友好&#xff0c;易于操作。 自行车在线租赁管理系统&#xff0c;主要的模块包括首页、个人中心、用户管理、会员管理、自…

软件系统安全设计(安全保证措施)

软件安全保证措施word 软件所有全套资料获取进主页或者本文末个人名片直接。

C++之set/map相关实现

看着上面的图片&#xff0c;你可能对set和map的多样变化产生疑惑&#xff0c;下面我们就来详细讲解他们的区别以及实现 一.set/map 首先&#xff0c;在这里我们要声明&#xff0c;如果你对二叉搜索树一点都不了解的话&#xff0c;建议你先去将搜索二叉树学会再来学习这里的内…

ArkTS开发原生鸿蒙HarmonyOS短视频应用

HarmonyOS实战课程“2024鸿蒙零基础快速实战-仿抖音App开发&#xff08;ArkTS版&#xff09;”已经于今日上线至慕课网&#xff08;https://coding.imooc.com/class/843.html&#xff09;&#xff0c;有致力于鸿蒙生态开发的同学们可以关注一下。 课程简介 本课程以原生鸿蒙Ha…

【Canvas与艺术】新制无底图安布雷拉暗黑系桌面(1920*1080)

【主要变化】 1.去掉底图&#xff0c;改为金丝正六边形组合而成的网格&#xff1b; 2.将安布雷拉标志调暗&#xff1b; 【成图】 【代码】 <!DOCTYPE html> <html lang"utf-8"> <meta http-equiv"Content-Type" content"text/html;…

力扣HOT100 - 78. 子集

解题思路&#xff1a; class Solution {public List<List<Integer>> subsets(int[] nums) {List<List<Integer>> lists new ArrayList<>(); // 解集lists.add(new ArrayList<Integer>()); // 首先将空集加入解集中for(int i 0; i < n…

Mac 安装 JDK21 流程

一、下载JDK21 访问Oracle官方网站或选择OpenJDK作为替代品。Oracle JDK从11版本开始是商业的&#xff0c;可能需要支付费用。OpenJDK是一个免费开源选项。 Oracle JDK官方网站&#xff1a;Oracle JDK Downloads OpenJDK官方网站&#xff1a;OpenJDK Downloads 这里以JDK21为…

FP16、BF16、INT8、INT4精度模型加载所需显存以及硬件适配的分析

大家好,我是herosunly。985院校硕士毕业,现担任算法研究员一职,热衷于机器学习算法研究与应用。曾获得阿里云天池比赛第一名,CCF比赛第二名,科大讯飞比赛第三名。拥有多项发明专利。对机器学习和深度学习拥有自己独到的见解。曾经辅导过若干个非计算机专业的学生进入到算法…

EDA(一)Verilog

EDA&#xff08;一&#xff09;Verilog Verilog是一种用于电子系统设计自动化&#xff08;EDA&#xff09;的硬件描述语言&#xff08;HDL&#xff09;&#xff0c;主要用于设计和模拟电子系统&#xff0c;特别是在集成电路&#xff08;IC&#xff09;和印刷电路板&#xff08;…

CogVLM/CogAgent环境搭建推理测试

引子 对于多模态大语言模型&#xff0c;一直没有怎么接触。刚巧一朋友有问到这方面的问题&#xff0c;也就顺手调研下。智谱AI的东西一直以来&#xff0c;还是很不错的。ChatGLM的忠实fans&#xff0c;看到白嫖网站github上有他们开源的多模态CogVLM/CogAgent&#xff0c;那就…

C语言:文件操作(中)

片头 嗨&#xff01;小伙伴们&#xff0c;大家好&#xff01;在上一篇中&#xff0c;我们学习了C语言&#xff1a;文件操作&#xff08;上&#xff09;&#xff0c;在这一篇中&#xff0c;我们将继续学习文件操作&#xff0c;准备好了吗&#xff1f;Ready Go ! ! ! 文件的顺序…

Linux下top命令指标说明

目录 Linux下top命令指标说明1. 概览2. CPU利用率3. 内存利用率4. 进程信息 Linux下top命令指标说明 在Linux系统中&#xff0c;top 命令是一个用于实时监视系统运行状态的工具。通过 top 命令&#xff0c;我们可以了解系统的负载情况、CPU利用率、内存使用情况以及各个进程的…

ubuntu修改/etc/resolve.conf总是被重置

ubuntu修改/etc/resolve.conf总是被重置 其实处理来很简单&#xff0c;根据英文提示删除/etc/resolve.conf,那是一个软链接&#xff0c;重新创建/etc/resolve.conf rm /etc/resolve.conf vi /etc/resolve.conf 添加nameserver 223.5.5.5

短视频素材去哪里搬运?短视频素材有哪些类型?

在这个数字化和视觉传达至关重要的时代&#xff0c;选择合适的视频素材对于提升视频内容的吸引力和观众参与度至关重要。无论您是一名广告制片人、社交媒体经理还是独立视频制作者&#xff0c;以下这些精选的视频素材网站将为您提供从高清视频到特效资源的全面支持&#xff0c;…

深入解析算法效率核心:时间与空间复杂度概览及优化策略

算法复杂度&#xff0c;即时间复杂度与空间复杂度&#xff0c;衡量算法运行时资源消耗。时间复杂度反映执行时间随数据规模增长的关系&#xff0c;空间复杂度表明额外内存需求。优化策略&#xff0c;如选择合适数据结构、算法改进、循环展开等&#xff0c;对于提升程序效率、减…

如何用 Redis 实现延迟队列?

延迟队列是一种常见的消息队列模式&#xff0c;用于处理需要延迟执行的任务或消息。Redis 是一种快速、开源的键值对存储数据库&#xff0c;具有高性能、持久性和丰富的数据结构&#xff0c;因此很适合用于实现延迟队列。在这篇文章中&#xff0c;我们将详细讨论如何使用 Redis…