使用ARouter进行Android模块化开发
Android模块化
关于为什么要进行Android模块化开发,网上也已经讲烂了,不过归结起来,大体是可以总结为:在App开发的初期,代码量不大,业务量比较少,一个App作为一个单独的模块进行开发,往往问题不大。但随着业务的增多,代码变的越来越复杂,每个模块之间的代码耦合变得越来越严重,结构越来越臃肿,修改一处代码要编译整个工程,导致非常耗时,这时候最好的办法就是进行模块化拆分。
模块化的好处
总结现在的模块化,大体有以下一些好处:
- 公用功能,不用重复开发、修改,代码复用性更强;
- 独立编译运行,提高编译速度,也就提高了开发效率;
- 更利于团队开发,不同的人可以独立负责不同的模块;
- 独立模块可以采用不同的技术架构,尝试新的技术方案,比如采用新的网络框架,甚至换成Kotlin来开发App。
需要解决的问题
要将项目模块化拆分,需要解决以下几个问题:
- 模块间页面跳转;
- 模块间事件通信;
- 模块间服务调用;
- 模块的独立运行;
- 模块间页面跳转路由拦截(如登录)
对于上面的问题,都可以使用ARouter进行解决。
ARouter框架
官方地址:ARouter开源地址
官方对ARouter框架的定义是:一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射关系按组分类、多级管理,按需初始化
- 支持用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 支持多种方式配置转场动画
- 支持获取Fragment
- 完全支持Kotlin以及混编(配置见文末 其他#5)
- 支持第三方 App 加固(使用 arouter-register 实现自动注册)
- 支持生成路由文档
- 提供 IDE 插件便捷的关联路径和目标类
基本用法
添加依赖和配置
使用ARouter之前,需要先添加相应的依赖,依赖的脚步如下:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:x.x.x'
annotationProcessor 'com.alibaba:arouter-compiler:x.x.x'
...
}
// 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4'
// Kotlin配置参考文末'其他#5'
添加注解
要想ARouter识别路由,需要使用@Route注解标示具体的路径。例如:
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
初始化SDK
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
发起路由操作
所谓发起路由操作,就是指触发路由的操作,例如:
// 1\. 应用内简单的跳转(通过URL跳转在'进阶用法'中)
ARouter.getInstance().build("/test/activity").navigation();
// 2\. 跳转并携带参数
ARouter.getInstance().build("/test/1")
.withLong("key1", 666L)
.withString("key3", "888")
.withObject("key4", new Test("Jack", "Rose"))
.navigation();
如果要接受传递处理的数据,可以使用下面的方式:
@Route(path = MainRoutePath.MAIN_ACTIVITY)
public class MainActivity extends BaseActivity {
/**
* 接收参数
*/
@Autowired(name = "name")
public String name;
@Autowired(name = "age")
public int age;
...
}
拦截器
// A more classic application is to handle login events during a jump so that there is no need to repeat the login check on the target page.
// Interceptors will be executed between jumps, multiple interceptors will be executed in order of priority
@Interceptor(priority = 8, name = "test interceptor")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
...
// No problem! hand over control to the framework
callback.onContinue(postcard);
// Interrupt routing process
// callback.onInterrupt(new RuntimeException("Something exception"));
// The above two types need to call at least one of them, otherwise it will not continue routing
}
@Override
public void init(Context context) {
// Interceptor initialization, this method will be called when sdk is initialized, it will only be called once
}
}
降级策略
ARouter提供的降级策略主要有两种方式,一种是通过回调的方式;一种是提供服务接口的方式。
一、回调方式
这种方式在跳转失败的时候会回调NavCallback接口的onLost方法。ARouter提供的回调方式的函数如下:
ARouter.getInstance().build(MainRoutePath.MAIN_ACTIVITY).navigation(this, new NavCallback() {
@Override
public void onFound(Postcard postcard) {
Log.d("ARouter", "找到了");
}
@Override
public void onLost(Postcard postcard) {
Log.d("ARouter", "找不到了");
}
@Override
public void onArrival(Postcard postcard) {
Log.d("ARouter", "跳转完了");
}
@Override
public void onInterrupt(Postcard postcard) {
Log.d("ARouter", "被拦截了");
}
});
二、服务接口
全局降级-服务接口的方式主要处理逻辑在内部,暴露的接口很友好。
ARouter.getInstance().build("/test/test").navigation();
此种降级策略需要实现服务接口DegradeService,返回的就一个方法就是onLost。例如:
@Route(path = RoutePath.DEGRADE)
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
ARouter.getInstance().build(RoutePath.DEGRADE_TIP).greenChannel().navigation();
}
@Override
public void init(Context context) {
}
}
混淆
为了避免打包时出现错误,需要将下面的内容使用keep标示。
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
ARouter Helper,
在 Android Studio 插件市场中搜索 ARouter Helper, 或者直接下载文档上方 最新版本 中列出的 arouter-idea-plugin zip 安装包手动安装,安装后 插件无任何设置,可以在跳转代码的行首找到一个图标 (navigation) 点击该图标,即可跳转到标识了代码中路径的目标类。
ARouter模块化示例
接下来,将会用一个demo介绍如何用ARouter进行模块化开发,demo模块化的整体架构如下图所示。
每个模块的作用如下:
- app:项目的宿主模块,仅仅是一个空壳,依赖于其他模块,成为项目架构的入口;
- baselibrary:项目的基类库,每个子模块都依赖共享公用的类和资源,防止公用的功能在不同的模块中有多个实现方式;
- module_route:集中管理所有模块的route的库;
- module_main:闪屏页,登录页,主页等;
- module_home:首页模块;
- module_video:视频模块;
- module_mine:我的模块;
依赖模式与独立模式的切换
使用模块化开发的一个好处是,各个独立的模块可以同时开发,独立运行而不必依赖于宿主app,也就是每个module是一个独立的App,项目发布的时候依赖到宿主App中即可。各业务模块之间不允许存在相互依赖关系,但是需要依赖基类库。
并且,单一模块生成的apk体积也小,编译时间也快,开发效率会高很多,同时也可以独立测试,要实现这样的效果需要对项目做一些配置。
gradle.properties配置
在主项目的gradle.properties中需要设置一个开关,用来控制module的编译模式,例如:
isModule=false
当isModule为false时作为依赖库,只能以宿主app启动项目,选择运行模块时其他module前都是红色的X,表示无法运行。
当isModule为true时,作为单独的模块进行运行,选择其中一个具体的module就可以直接运行。
module清单文件
为了完成依赖模式与独立模式的切换,module清单文件需要配置两个,一个作为独立项目的清单文件,一个作为库的清单文件,以module_main模块为例。
buildApp作为依赖库的清单文件,和独立项目的清单文件buildModule区别是依赖库的清单文件Application中没有配置入口的Activity,其他都一样。
gradle配置
为了完成切换,还需要对module的build.gradle文件进行配置,如下图:
宿主app配置
接下来,在宿主app的build.gradle中添加模块依赖,如下所示:
dependencies {
if (!isModule.toBoolean()) {
implementation project(':module_home')
implementation project(':module_video')
implementation project(':module_main')
implementation project(':module_mine')
implementation project(':module_router')
}
}
ARouter Helper
常见问题
问题1
错误:ARouter::Compiler >>> No module name, for more information, look at gradle log.
检查项目依赖的全部module,包括module依赖的module,为了能够进行单独的编译,所以需要为每一个module添加名称,即在每个module的 build.gradle中加上下面的代码:
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [ AROUTER_MODULE_NAME : project.getName() ]
}
}
}
问题2
ARouter.getInstance().inject(this);
问题描述:有一个singletask启动模式的activity,在onNewIntent方法中调用ARouter.getInstance().inject(this);得不到参数,查看ARouter在build过程中生成的代码可以知道它是调用了activity的getIntent来获取参数的,但是onNewIntent中的intent和在onCreate方法中的intent并不相同,所以需要在onNewIntent方法中调用setIntent方法,然后就能得到参数了。
问题3
ARouter there's no route matched
不同module的一级路径必须不同,否则会导致一个moudle中的一级路径失效。
附:示例下载地址
文末
好啦,如此文章到这里就结束了,希望这篇文章能够帮到正在看的你们解决小伙伴们的问题~
如果你觉得文章写得不错就给个赞呗?如果你觉得文章非常不错的话那就转发一个呗,让更多小伙伴看到;如果你觉得那里值得改进的,请给我留言。一定会认真查询,修正不足,谢谢~
最后文末放上一个技术交流群:Android架构设计(185873940)
PS:群内有许多技术大牛,有任何问题,欢迎广大网友一起来交流,群内还不定期免费分享高阶Android学习视频资料和面试资料包~
再推荐一篇文章,具体的架构视频,面试专题,学习笔记都在这篇文章中:“寒冬未过”,阿里P9架构分享Android必备技术点,让你offer拿到手软!
偷偷说一句:群里高手如云,欢迎大家加群和大佬们一起交流讨论啊~