美文网首页
module解耦(二)基于路由的解耦

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

作者: 长点点 | 来源:发表于2023-03-10 15:09 被阅读0次

    一、 引言

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

    传统的方式是通过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):表示请求资源时定位到某个子部分。

    例如:

    https://www.jianshu.com/u/1d9c468843cf/code?userId=aschnloih8ajkbkjb
    

    这个URL包含以下部分:

    • 协议:https
    • 主机名:www.jianshu.com
    • 端口号:默认为80
    • 路径:/u/1d9c468843cf
    • 查询参数:code?userId=aschnloih8ajkbkjb
      片段标识符:无
      在基于路由的module解耦方案中,URL通常用于表示一个页面或服务,而不是一个网络资源。因此,协议部分可以自定义,例如:

    router://moduleA/pageA?name=Tom&age=18
    这个URL表示跳转到moduleA模块中的pageA页面,并传递name和age两个参数。

    2.2 路由表

    路由表(Router Table)是一种存储URL和页面或服务之间映射关系的数据结构。路由表可以是静态的或动态的,也可以是本地的或远程的。静态路由表是在编译期生成的,动态路由表是在运行期生成的。本地路由表是存储在客户端内存或文件中的,远程路由表是存储在服务器端数据库或配置文件中的。

    路由表通常包含以下几个字段:

    • URL:表示一个页面或服务的唯一标识符;
    • 类名:表示对应页面或服务所属类名;
    • 类型:表示对应页面或服务所属类型,如Activity、Fragment、Service等;
    • 拦截器:表示对应页面或服务是否需要经过拦截器处理;
    • 优先级:表示对应页面或服务在多个匹配结果中的优先级;
    • 其他属性:表示对应页面或服务所需的其他属性,如进程名、启动模式等。
      例如:
    URL 类名 类型 拦截器 优先级 其他属性
    router://moduleA/pageA com.example.modulea.PageAActivity Activity true 1 process=“:moduleA”
    router://moduleB/pageB/:id com.example.moduleb.PageBFragment Fragment false 0

    这个路由表包含两条记录,分别表示moduleA模块中的pageA页面和moduleB模块中的pageB页面。其中pageB页面支持路径参数id,用于传递动态数据。

    2.3 路由器

    路由器(Router)是一种负责处理URL请求和跳转到相应页面或服务的组件。路由器通常包含以下几个功能:

    • 初始化:负责初始化路由表和拦截器等配置信息;
    • 解析:负责解析URL请求,并根据路由表查找匹配结果;
    • 拦截:负责根据拦截器规则判断是否需要拦截当前请求,并执行相应操作;
    • 跳转:负责根据匹配结果创建相应类型的实例,并执行跳转逻辑;
    • 回调:负责提供回调接口给调用者获取跳转结果和数据;
      例如:
    // 初始化
    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请求进行拦截和处理的组件。拦截器通常用于实现以下功能:

    • 权限检查:判断当前用户是否有权限访问目标页面或服务;
    • 登录检查:判断当前用户是否已经登录,如果没有则跳转到登录页面;
    • 参数校验:判断当前请求是否携带了合法的参数,如果没有则提示错误信息;
    • 数据预处理:对当前请求携带的数据进行预处理,如加密、解密、压缩、解压等;
    • 业务逻辑:根据业务需求执行一些特定的逻辑,如埋点、统计、广告等;
      例如:
    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上有完整的文档和示例代码,可以方便地查看和学习(https://github.com/alibaba/ARouter)
    • ARouter框架的基本使用包括初始化、注册页面和服务、跳转页面和调用服务、配置拦截器等步骤

    3.1 配置依赖

    首先,在项目根目录下的build.gradle文件中添加ARouter插件依赖:

    buildscript {
        dependencies {
            classpath "com.alibaba:arouter-register:1.0.2"
        }
    }
    

    然后,在每个子模块下的build.gradle文件中添加ARouter库依赖,并应用ARouter插件:

    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文件中添加以下配置:

    android {
        defaultConfig {
            javaCompileOptions {
                annotationProcessorOptions {
                    arguments = [AROUTER_MODULE_NAME: project.getName()]
                }
            }
        }
    }
    

    这样就完成了ARouter框架在项目中的配置。

    3.2 初始化路由器

    接下来,在app模块中创建一个Application类,并在onCreate方法中初始化路由器:

    public class MyApplication extends Application {
    
        @Override
        public void onCreate() {
            super.onCreate();
            // 初始化路由器
            ARouter.init(this);
        }
    }
    

    同时,在AndroidManifest.xml文件中注册该Application类,并添加meta-data标签指定路由表生成路径:

    <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和其他属性:

    // 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方法进行跳转或调用:

    // 从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注解来配置拦截器,并指定优先级和名称:

    // 配置一个登录拦截器,优先级为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的命名规范和安全性,避免与其他应用或模块冲突或被恶意调用;
    • 需要注意拦截器的优先级和执行顺序,避免出现逻辑错误或死循环;

    相关文章

      网友评论

          本文标题:module解耦(二)基于路由的解耦

          本文链接:https://www.haomeiwen.com/subject/kfroldtx.html