Android 解耦(二)基于路由的解耦

一、 引言

在安卓开发中,随着项目的复杂度增加,模块化(或组件化)开发方式越来越受到开发者的青睐。模块化开发可以将一个大型项目拆分成多个相对独立的模块,每个模块负责一个功能或业务场景,从而提高代码的可读性、可维护性和可复用性。但是模块化开发也带来了一些挑战,其中之一就是如何实现模块间的解耦和通信。

传统的方式是通过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老皮!!!欢迎大家来找我探讨交流👀

相关推荐
CheungChunChiu38 分钟前
Android10 rk3399 以太网接入流程分析
android·framework·以太网·eth·net·netd
木头没有瓜1 小时前
ruoyi 请求参数类型不匹配,参数[giftId]要求类型为:‘java.lang.Long‘,但输入值为:‘orderGiftUnionList
android·java·okhttp
键盘侠0071 小时前
springboot 上传图片 转存成webp
android·spring boot·okhttp
江上清风山间明月2 小时前
flutter bottomSheet 控件详解
android·flutter·底部导航·bottomsheet
Crossoads4 小时前
【汇编语言】外中断(一)—— 外中断的魔法:PC机键盘如何触发计算机响应
android·开发语言·数据库·深度学习·机器学习·计算机外设·汇编语言
sunphp开发者4 小时前
黑客攻击网站,篡改首页问题排查修复
android·js
我又来搬代码了5 小时前
【Android Studio】创建新项目遇到的一些问题
android·ide·android studio
ggs_and_ddu9 小时前
Android--java实现手机亮度控制
android·java·智能手机
zhangphil14 小时前
Android绘图Path基于LinearGradient线性动画渐变,Kotlin(2)
android·kotlin
watl015 小时前
【Android】unzip aar删除冲突classes再zip
android·linux·运维