一、 引言
在安卓开发中,随着项目的复杂度增加,模块化(或组件化)开发方式越来越受到开发者的青睐。模块化开发可以将一个大型项目拆分成多个相对独立的模块,每个模块负责一个功能或业务场景,从而提高代码的可读性、可维护性和可复用性。但是模块化开发也带来了一些挑战,其中之一就是如何实现模块间的解耦和通信。
传统的方式是通过Intent进行页面跳转和数据传递,但这种方式存在以下缺点:
- 需要显式地指定目标页面的类名或Action,导致强依赖关系;
- 需要在AndroidManifest.xml中注册每个页面,并配置相应的过滤器;
- 需要手动处理数据序列化和反序列化;
- 需要在跨进程通信时使用Bundle或AIDL等机制;
- 难以适应动态加载和插件化等需求。
为了解决这些问题,许多开源框架提出了基于路由(Router)的方案。路由是一种将URL映射到具体页面或服务的机制,可以用于将Intent页面跳转的强依赖关系解耦,同时减少跨团队开发的互相依赖问题。
二、 基本概念
2.1 路由
路由(Router)是一种将URL映射到具体页面或服务的机制。URL是一种统一资源定位符(Uniform Resource Locator),用于标识一个资源或目标。URL通常包含以下几个部分:
- 协议(Scheme):表示资源访问所采用的协议类型,如http、https等;
- 主机名(Host):表示资源所在服务器域名或IP地址;
- 端口号(Port):表示资源所在服务器端口号,默认为80;
- 路径(Path):表示资源在服务器上具体位置;
- 查询参数(Query):表示请求资源时附加给服务器端程序处理数据;
- 片段标识符(Fragment):表示请求资源时定位到某个子部分。
2.2 路由表
路由表(Router Table)是一种存储URL和页面或服务之间映射关系的数据结构。路由表可以是静态的或动态的,也可以是本地的或远程的。静态路由表是在编译期生成的,动态路由表是在运行期生成的。本地路由表是存储在客户端内存或文件中的,远程路由表是存储在服务器端数据库或配置文件中的。
路由表通常包含以下几个字段:
- URL:表示一个页面或服务的唯一标识符;
- 类名:表示对应页面或服务所属类名;
- 类型:表示对应页面或服务所属类型,如Activity、Fragment、Service等;
- 拦截器:表示对应页面或服务是否需要经过拦截器处理;
- 优先级:表示对应页面或服务在多个匹配结果中的优先级;
- 其他属性:表示对应页面或服务所需的其他属性,如进程名、启动模式等。
2.3 路由器
路由器(Router)是一种负责处理URL请求和跳转到相应页面或服务的组件。路由器通常包含以下几个功能:
- 初始化:负责初始化路由表和拦截器等配置信息;
- 解析:负责解析URL请求,并根据路由表查找匹配结果;
- 拦截:负责根据拦截器规则判断是否需要拦截当前请求,并执行相应操作;
- 跳转:负责根据匹配结果创建相应类型的实例,并执行跳转逻辑;
- 回调:负责提供回调接口给调用者获取跳转结果和数据;
例如:
java
// 初始化
Router.init(context);
// 解析
Postcard postcard = Router.parse("router://moduleA/pageA?name=Tom&age=18");
// 拦截
boolean intercepted = Router.intercept(postcard);
// 跳转
Router.navigate(postcard);
// 回调
Router.setCallback(new Callback() {
@Override
public void onSuccess(Postcard postcard) {
// 跳转成功
}
@Override
public void onFail(Postcard postcard) {
// 跳转失败
}
});
这段代码演示了使用Router组件进行URL请求处理和跳转到pageA页面的过程。
2.4 拦截器
拦截器(Interceptor)是一种负责对URL请求进行拦截和处理的组件。拦截器通常用于实现以下功能:
- 权限检查:判断当前用户是否有权限访问目标页面或服务;
- 登录检查:判断当前用户是否已经登录,如果没有则跳转到登录页面;
- 参数校验:判断当前请求是否携带了合法的参数,如果没有则提示错误信息;
- 数据预处理:对当前请求携带的数据进行预处理,如加密、解密、压缩、解压等;
- 业务逻辑:根据业务需求执行一些特定的逻辑,如埋点、统计、广告等;
例如:
java
public class LoginInterceptor implements Interceptor {
@Override
public boolean intercept(Postcard postcard) {
// 判断当前用户是否已经登录
if (!UserManager.isLogin()) {
// 跳转到登录页面
Router.navigate("router://moduleA/login");
return true;
}
return false;
}
}
这个拦截器实现了登录检查的功能,如果当前用户没有登录,则跳转到登录页面,并拦截原始请求。
三、使用实例
本节将以一个简单的示例来演示如何使用基于路由的module解耦方案。假设我们有一个电商项目,包含以下几个模块:
- app模块:主模块,负责启动应用和管理其他模块;
- home模块:首页模块,负责展示商品列表和轮播图等内容;
- detail模块:详情模块,负责展示商品详情和评论等内容;
- cart模块:购物车模块,负责展示用户已添加的商品和结算等内容;
- user模块:用户模块,负责展示用户信息和订单等内容;
为了实现这些模块之间的解耦和通信,我们可以使用一个开源框架ARouter来实现基于路由的方案。ARouter是一个轻量级且功能强大的路由框架,支持静态路由表生成、自动参数注入、多种类型跳转、多种拦截器配置等功能。
- ARouter框架是一个用于帮助Android App进行组件化改造的框架,支持模块间的路由、通信、解耦
- ARouter框架使用静态注解处理,为适应多模块,使用moduleName后缀生成了一组统一规则的注册类
- ARouter框架在GitHub上有完整的文档和示例代码,可以方便地查看和学习(github.com/alibaba/ARo...)
- ARouter框架的基本使用包括初始化、注册页面和服务、跳转页面和调用服务、配置拦截器等步骤
3.1 配置依赖
首先,在项目根目录下的build.gradle文件中添加ARouter插件依赖:
java
buildscript {
dependencies {
classpath "com.alibaba:arouter-register:1.0.2"
}
}
然后,在每个子模块下的build.gradle文件中添加ARouter库依赖,并应用ARouter插件:
java
apply plugin: 'com.alibaba.arouter'
dependencies {
implementation "com.alibaba:arouter-api:1.5.2"
annotationProcessor "com.alibaba:arouter-compiler:1.5.2"
}
最后,在app模块下的build.gradle文件中添加以下配置:
java
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
这样就完成了ARouter框架在项目中的配置。
3.2 初始化路由器
接下来,在app模块中创建一个Application类,并在onCreate方法中初始化路由器:
java
public class MyApplication extends Application {
@Override
public void onCreate() {
super.onCreate();
// 初始化路由器
ARouter.init(this);
}
}
同时,在AndroidManifest.xml文件中注册该Application类,并添加meta-data标签指定路由表生成路径:
java
<application
android:name=".MyApplication">
<meta-data
android:name="ROUTER_MODULE_APP"
android:value="app/src/main/java/com/example/app/router" /> </application>
这样就完成了路由器在app模块中的初始化。
3.3 注册页面和服务
然后,在每个子模块中,使用@Route注解来注册页面和服务,并指定对应的URL和其他属性:
java
// home模块中的HomePageActivity
@Route(path = "router://home/homePage")
public class HomePageActivity extends AppCompatActivity {
// ...
}
// detail模块中的DetailPageActivity
@Route(path = "router://detail/detailPage", extras = 1)
public class DetailPageActivity extends AppCompatActivity {
// ...
}
// cart模块中的CartService实现类
@Route(path = "router://cart/cartService")
public class CartServiceImpl implements CartService {
// ...
}
// user模块中的UserService实现类
@Route(path = "router://user/userService")
public class UserServiceImpl implements UserService {
// ...
}
这样就完成了页面和服务在各个子模块中的注册。
3.4 跳转页面和调用服务
接下来,在任意一个子模块中,可以使用ARouter.getInstance()方法获取路由器实例,并通过build方法构建Postcard对象,然后通过navigation方法进行跳转或调用:
java
// 从home模块跳转到detail模块的DetailPageActivity,并传递商品id参数
ARouter.getInstance().build("router://detail/detailPage").withString("id", "123456").navigation();
// 从detail模块调用cart模块的CartService接口,添加商品到购物车
CartService cartService = ARouter.getInstance().build("router://cart/cartService").navigation();
cartService.addProductToCart(product);
// 从cart模块调用user模块的UserService接口,获取用户信息
UserService userService = ARouter.getInstance().build("router://user/userService").navigation();
User user = userService.getUserInfo();
这样就完成了页面和服务在各个子模块之间的跳转和调用。
3.5 配置拦截器
最后,在任意一个子模块中,可以使用@Interceptor注解来配置拦截器,并指定优先级和名称:
java
// 配置一个登录拦截器,优先级为8,名称为loginInterceptor
@Interceptor(priority = 8, name = "loginInterceptor")
public class LoginInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard) {
// 判断当前请求是否需要登录检查(extras为1表示需要)
if (postcard.getExtras() == 1) {
// 判断当前用户是否已经登录
if (!UserManager.isLogin()) {
// 跳转到登录页面,并传递原始请求信息(requestCode为100表示是从拦截器跳转过来)
ARouter.getInstance().build("router://user/login").with(postcard).withInt("requestCode", 100).navigation();
// 拦截当前请求,并设置结果码为失败(RESULT_FAILED表示失败)
postcard.setGreenChannel();
postcard.withInt("resultCode", RESULT_FAILED);
}
}
// 继续执行下一个拦截器或目标请求(onContinue方法表示继续)
callback.onContinue(postcard);
}
@Override
public void init(Context context) {
// 初始化拦截器相关资源(可选)
}
}
这样就完成了拦截器在某个子模块中的配置。
四、优缺点分析
1.基于路由的module解耦方案有以下优点:
- 实现了模块之间的完全解耦,不需要依赖任何接口或实现类;
- 支持多种类型的跳转和调用,包括Activity、Fragment、Service等;
- 支持多种方式的参数传递和注入,包括基本类型、对象类型、Bundle等;
- 支持多种拦截器配置和处理,可以实现权限检查、登录检查等功能;
- 支持静态路由表生成和动态路由注册,提高了性能和灵活性;
2.基于路由的module解耦方案也有以下缺点:
- 需要额外引入一个路由框架,增加了项目的复杂度和维护成本;
- 需要为每个页面和服务定义一个唯一的URL,并保证其正确性和一致性;
- 需要注意URL的命名规范和安全性,避免与其他应用或模块冲突或被恶意调用;
- 需要注意拦截器的优先级和执行顺序,避免出现逻辑错误或死循环;
👀关注公众号:Android老皮!!!欢迎大家来找我探讨交流👀