一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
一、功能介绍
支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
支持多模块工程使用
支持添加多个拦截器,自定义拦截顺序
支持依赖注入,可单独作为依赖注入框架使用
支持InstantRun
支持MultiDex(Google方案)
映射关系按组分类、多级管理,按需初始化
支持用户指定全局降级与局部降级策略
页面、拦截器、服务等组件均自动注册到框架
支持多种方式配置转场动画
支持获取Fragment
完全支持Kotlin以及混编(配置见文末 其他#5)
支持第三方 App 加固(使用 arouter-register 实现自动注册)
支持生成路由文档
提供 IDE 插件便捷的关联路径和目标类
支持增量编译(开启文档生成后无法增量编译)
二、典型应用
从外部URL映射到内部页面,以及参数传递与解析
跨模块页面跳转,模块间解耦
拦截跳转过程,处理登陆、埋点等逻辑
跨模块API调用,通过控制反转来做组件解耦
1.添加依赖和配置
纯java的:
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
compile 'com.alibaba:arouter-api:1.5.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.1'
...
}
Kotlin或者Kotlin&java混编的:
(手动引用一)
plugins {
id 'com.android.library'
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
//...
api 'com.alibaba:arouter-api:1.5.1'
kapt 'com.alibaba:arouter-compiler:1.5.1'
...
}
小插曲:
implementation 和 api 关键字
implementation 和 api 关键字,在Android studio3.0版本中,曾经的 compile 关键字被弃用,而 api 则是 compile 的替代品, api 与 compile 没有区别。但最新官方推荐使用 implementation 来代替 compile 关键字,据说 implementation 会使Android studio的编译速度更快呦。而 implementation 和 api 关键字的区别则在于用 implementation来声明的依赖包只限于当前module内部使用,对于依赖其module的模块是无法使用到该依赖包的。而用 api来声明依赖包时,依赖于该module的模块可以正常使用其模块内的依赖包。除此之外 testCompile 要用testImplementation 或 testApi 替换,androidTestCompile 要用androidTestImplementation 或 androidTestApi 替换。
在本次集成中,将直接继承到一个公共的基础module,来让app module进行依赖,因此使用 api关键字。
若没有对项目进行组件化,则可以使用 implementation 关键字进行依赖。
2.添加混淆规则(如果使用了Proguard)
-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
1.初始化SDK
if (isApkInDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this); // 尽可能早,推荐在Application中初始化
//判断当前应用是否是debug状态
public static boolean isApkInDebug() {
try {
ApplicationInfo info = sAppLike.getApplicationInfo();
return (info.flags & ApplicationInfo.FLAG_DEBUGGABLE) != 0;
} catch (Exception e) {
return false;
}
}
2.添加注解
// 在支持路由的页面上添加注解(必选)
// 这里的路径需要注意的是至少需要有两级,/xx/xx
@Route(path = "/test/activity")
public class YourActivity extend Activity {
...
}
3.发起路由操作
// 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="/test/test"), 如没有特殊需求,请勿指定group字段,废弃功能);
检查目标页面所在的模块的gradle脚本中是否依赖了 arouter-compiler sdk (需要注意的是,要使用apt依赖,而不是compile关键字依赖);
检查编译打包日志,是否出现了形如 ARouter::�Compiler >>> xxxxx 的日志,日志中会打印出发现的路由目标;
启动App的时候,开启debug、log(openDebug/openLog), 查看映射表是否已经被扫描出来,形如 D/ARouter::: LogisticsCenter has already been loaded, GroupIndex[4],GroupIndex > 0。
直接阅读源码,看具体问题所在:
点击进入这个navigation()的源码,一层一层进入:
ARouter.getInstance().build(A_ROUTER_TEST_ACTIVITY).navigation()
/**
* Navigation to the route with path in postcard.
*
* @param context Activity and so on.
*/
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// Pretreatment failed, navigation canceled.
return null;
}
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
//...
继续进入这个方法
LogisticsCenter.completion(postcard);
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
//查找RouteMeta对象,如果存在说明路由成功,如果失败说明还没有被加载或者这个path就是错误的
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// 通过groupsIndex去找IRouteGroup的实现类
Class<? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup());
if (null == groupMeta) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// 通过反射获取IRouteGroup的实现类,然后加载到内存
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
// 加载到内存后Warehouse.groupsIndex去掉这个group
Warehouse.groupsIndex.remove(postcard.getGroup());
}
} else {
//查找到路由地址
//...
}
}
查一下IRouterGroup的实现类,这里特别注意,如果没找到对应的包的实现类(多个包做了路径映射的话,就会有多个实现类),那就证明没有映射成功,就证明出现细节上的bug啦
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$app implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/app/ble", RouteMeta.build(RouteType.ACTIVITY, BleBluetoothTestActivity.class, "/app/ble", "app", null, -1, -2147483648));
}
}
这里的path都是”/app/xxxx/”,Aouter 要求path必须有至少两级的路径,是因为Arouter在寻找route的时候,是通过第一级路径,也就是这里的”app”来寻找的。Aouter通过”app”找到了route,并且在groupIndex中删除了这个路径,代表已经加载到了内存。
因此,不同的module使用了相同的一级路径,在Arouter第一次寻找到route的时候便删除了这个一级路径的group,因为一级路径的重复,再调用另一个module的一级路径是”app”的路由时,由于之前Warehouse.groupsIndex已经删除,便导致了there’s no route matched的错误。