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.xdnf.cn/news/1410480.html

如若内容造成侵权/违法违规/事实不符,请联系一条长河网进行投诉反馈,一经查实,立即删除!

相关文章

【数据库主从架构】

【数据库主从架构】 1. 什么是数据库的主从架构1.1 主从复制1.1.1 MySQL的主从主从复制技术三级目录 1. 什么是数据库的主从架构 随着公司业务线的增多&#xff0c;各种数据都在迅速增加&#xff0c;并且数据的读取流量也大大增加&#xff0c;就面临着数据安全问题&#xff0c;…

用栈实现队列——leetcode刷题

题目要求我们只用栈的基本操作 push to top 入栈&#xff0c;peek from top 返回栈顶元素&#xff0c;pop from top 移除并返回栈顶元素&#xff0c;size 栈的大小&#xff0c;is_empty 判断栈是否为空&#xff0c;这几个函数来实现队列&#xff0c;也就是说&#xff0c;我们在…

25计算机考研院校数据分析 | 哈尔滨工业大学

哈尔滨工业大学&#xff08;Harbin Institute of Technology&#xff09;&#xff0c;简称哈工大&#xff0c; 校本部位于黑龙江省哈尔滨市&#xff0c;是由工业和信息化部直属的全国重点大学&#xff0c;位列国家“双一流”、“985工程”、“211工程”&#xff0c;九校联盟 、…

「笔试刷题」:最长回文子串(中心扩展算法)

一、题目 描述 对于长度为 n 的一个字符串 A&#xff08;仅包含数字&#xff0c;大小写英文字母&#xff09;&#xff0c;请设计一个高效算法&#xff0c;计算其中最长回文子串的长度。 数据范围&#xff1a; 1≤n≤1000 要求&#xff1a;空间复杂度 O(1)&#xff0c;时间复…

和丰多媒体信息发布系统 QH.aspx 文件上传漏洞复现

0x01 免责声明 请勿利用文章内的相关技术从事非法测试&#xff0c;由于传播、利用此文所提供的信息而造成的任何直接或者间接的后果及损失&#xff0c;均由使用者本人负责&#xff0c;作者不为此承担任何责任。工具来自网络&#xff0c;安全性自测&#xff0c;如有侵权请联系删…

IDEA中测试时的包名问题

报错&#xff1a;Unable to find a SpringBootConfiguration, you need to use ContextConfiguration or SpringBootTest(classes...) with your test 原因&#xff1a;&#xff08;图是别人那巴来的&#xff09;启动类所在的包名和测试类的包名不一致导致的&#xff0c;原因是…

Qt 信号槽中信号重名解决办法

1、类似与Qt4中的写法&#xff1a; 2、函数指针 3、泛型 connect(ui->combox, QOverload<int>::of(&QCombox::currentIndexChanged), this ,&mainwindow::onindexchange);

RabbitMQ入门教学(浅入浅出)

进程间通信 互联网的通讯时网络的基础&#xff0c;一般情况下互联网的资源数据对储存在中心服务器上&#xff0c;一般情况下个体对个体的访问仅限于局域网下&#xff0c;在公网即可完成资源的访问&#xff0c;如各种网站资源&#xff0c;下载资源&#xff0c;种子等。网络通讯…

《架构即未来》读后感

目录 一、引言 二、《架构即未来》读后感 1、主题的简要介绍 2、我的看法和理解 3、作者的优点和传递的信息 4、思想如何适用于当今社会 三、《架构即未来》对于企业发展的影响具体体现在哪些方面&#xff1f; 一、引言 任何一个持续成长的公司最终都需要解决系统、组织…

XUbuntu24.04之更换国内高速源(二百二十八)

简介&#xff1a; CSDN博客专家&#xff0c;专注Android/Linux系统&#xff0c;分享多mic语音方案、音视频、编解码等技术&#xff0c;与大家一起成长&#xff01; 优质专栏&#xff1a;Audio工程师进阶系列【原创干货持续更新中……】&#x1f680; 优质专栏&#xff1a;多媒…

C++_set和map的学习

1. 关联式容器 STL中的容器有序列式容器和关联式容器。 其中 vector 、 list 、 deque 、 forward_list(C11)就是序列式容器&#xff0c; 因为其底层为线性序列的数据结构&#xff0c;里面 存储的是元素本身 关联式容器 也是用来存储数据的&#xff0c;与序列式容器不同的是&am…

[MRCTF2020]你传你呢 1

上传一个文件 图片木马 新建一个图片木马&#xff0c;这里我命名为a.php&#xff0c;名字需和待会上传的.htaccess一致 GIF89a <script languagephp>eval($_REQUEST["cmd"])</script>抓包上传的a.php文件&#xff0c;修改两个地方 新建一个.htacces…

02 变量和基本类型

数据类型是程序的基础&#xff1a;它告诉我们数据的意义以及我们能在数据上执行的操作。 C语言支持广泛的数据类型。它定义了几种基本内置类型(如字符、整型、浮点数等), 同时也为程序员提供了自定义数据类型的机制。基于此&#xff0c;C标准库定义了一些更加复杂的数据类型&a…

ubuntu与redhat的不同之处

华子目录 什么是ubuntu概述 ubuntu版本简介桌面版服务器版 安装部署部署后的设置设置root密码关闭防火墙启用允许root进行ssh登录更改apt源安装所需软件 网络配置Netplan概述配置详解配置文件DHCP静态IP设置设置 软件安装方法apt安装软件作用常用命令配置apt源 deb软件包安装概…

华为Pura70发布,供应链公司进入静默保密期

保密措施&#xff1a;与华为Pura70发布相关的供应链公司在产品发布前后处于静默保密期。这可能是由于华为对于手机供应链的一些信息处于保密状态&#xff0c;尤其是关于麒麟芯片的代工厂商等敏感信息。这种保密措施有助于保持产品的神秘感&#xff0c;调动用户的好奇心&#xf…

【软件工程】习题一

目录 一二三四五 一 软件工程学&#xff0c;是用工程化的方法指导计算机软件**开发和维护&#xff08;开发和管理&#xff09;**的一门工程学科。 软件工程包括软件开发技术&#xff08;过程、方法和工具&#xff09;与软件工程管理两方面的内容。 软件工程管理是通过计划、组…

Deep learning Part Five RNN--24.4.29

接着上期&#xff0c;CBOW模型无法解决文章内容过长的单词预测的&#xff0c;那该如何解决呢&#xff1f; 除此之外&#xff0c;根据图中5-5的左图所示&#xff0c;在CBOW模型的中间层求单词向量的和&#xff0c;这时就会出现另一个问题的&#xff0c;那就是上下文的单词的顺序…

【算法题解】部分洛谷题解(上)

前言 本篇为我做过的洛谷题的部分题解&#xff0c;大多是我认为比较具有代表性的或者比较有意思的题目&#xff0c;包含我自己的思考过程和想法。 [NOIP2001 提高组] 数的划分 题目描述 将整数 n n n 分成 k k k 份&#xff0c;且每份不能为空&#xff0c;任意两个方案不相…

003 redis分布式锁 jedis分布式锁 Redisson分布式锁 分段锁

文章目录 Redis分布式锁原理1.使用set的命令时&#xff0c;同时设置过期时间2.使用lua脚本&#xff0c;将加锁的命令放在lua脚本中原子性的执行 Jedis分布式锁实现pom.xmlRedisCommandLock.javaRedisCommandLockTest.java 锁过期问题1乐观锁方式&#xff0c;增加版本号(增加版本…

实体映射解决方案-MapStruct

序言 本文给大家介绍 MapStruct。 一、问题引入 在我们的日常开发过程中&#xff0c;我们经常会将 VO、DTO、PO …… 等对象相互进行映射。实现的方式可能有以下几种&#xff1a; 自己手动进行转换利用诸如 Spring 或 Apache 提供的 BeanUtils 等工具类 如果自己手动进行转…