ARouter 简介
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦。
组件化或者模块化开发模式,已逐渐成为热浪的形式,使用这些模式可以让我们程序更容易的扩展、更方便的维护。
更快捷的同步开发与更简单的单独调试,而ARouter的出现就是让组件间、模块间是实现完全的独立。
ARouter是:阿里巴巴自研路由框架,主要解决组件间、模块间的界面跳转 问题。
是ARouter是阿里巴巴开源的Android平台中对页面、服务提供路由功能的中间件,提倡的是简单且够用。 帮助改进Android组件化的框架。
ARouter基本使用
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'
...
}
// 旧版本gradle插件(< 2.2),可以使用apt插件,配置方法见文末'其他#4'
// Kotlin配置参考文末'其他#5'
- Kotlin项目中的配置方式
plugins {
// 1.增加kotlin-kapt引用
id 'kotlin-kapt'
}
android {
// 2.增加Arouter编译配置,注意顺序。此处应该在android{}中
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
dependencies {
// 3. 添加gradle引用
implementation 'com.alibaba:arouter-api:1.5.2'
kapt "com.alibaba:arouter-compiler:1.5.2"
}
2. 在Application初始化ARouter
class MyApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(this); // 尽可能早,推荐在Application中初始化
}
}
3. 添加注解,发起路由操作
- MainActivity 跳转到MainActivity2
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
Log.e("xyh", "onCreate: " + BuildConfig.DEBUG)
}
fun doClick(view: View) {
when (view.id) {
R.id.btn1 -> {
// 1. 应用内简单的跳转
ARouter.getInstance().build("/test/activity2").navigation();
}
}
}
}
- MainActivity2
/**
* 在支持路由的页面上添加注解(必选)
* 这里的路径需要注意的是至少需要有两级,/xx/xx
*/
@Route(path = "/test/activity2")
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
}
}
4. 跳转并携带参数
上面是跳转界面不带参,现在来实现携带参数。
ARouter 提供了丰富大量的参数类型,供我们选择。
fun doClick(view: View) {
when (view.id) {
R.id.btn1 -> {
// 1. 应用内简单的跳转
// ARouter.getInstance().build("/test/activity2").navigation()
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/activity2")
.withString("key1", "888")
.withLong("key2", 666L)
.withSerializable("key3", Test("Jack", "Rose"))
.navigation()
}
}
}
- 获取参数:
/**
* 在支持路由的页面上添加注解(必选)
* 这里的路径需要注意的是至少需要有两级,/xx/xx
*/
@Route(path = "/test/activity2")
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val key1 = intent.getStringExtra("key1")
val key2 = intent.getLongExtra("key2", 0)
val key3: Test = intent.getSerializableExtra("key3") as Test
Log.e("xyh", "onCreate: key1=$key1,key2=$key2,key3=${key3.name1}")
}
}
5. 如果你想实现像 startActivityForResult() 功能,可以这样使用:
navigation(Activity mContext, int requestCode)
- 具体使用如下:
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
fun doClick(view: View) {
when (view.id) {
R.id.btn1 -> {
// 1. 应用内简单的跳转
//ARouter.getInstance().build("/test/activity2").navigation()
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/activity2")
.withString("key1", "888")
.withLong("key2", 666L)
.withSerializable("key3", Test("Jack", "Rose"))
.navigation(this, 100)
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == 100 && resultCode == 200 && data != null) {
Log.e("xyh", "onActivityResult: ${data.getStringExtra("result")}")
}
}
}
- 在MainActivity2 中就可以传递结果:
/**
* 在支持路由的页面上添加注解(必选)
* 这里的路径需要注意的是至少需要有两级,/xx/xx
*/
@Route(path = "/test/activity2")
class MainActivity2 : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val key1 = intent.getStringExtra("key1")
val key2 = intent.getLongExtra("key2", 0)
val key3: Test = intent.getSerializableExtra("key3") as Test
Log.e("xyh", "onCreate: key1=$key1,key2=$key2,key3=$key3")
}
fun back(view: View) {
val intent = Intent()
intent.putExtra("result", "zly")
setResult(200, intent)
finish()
}
}
6. 添加混淆规则(如果使用了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
7. 使用 Gradle 插件实现路由表的自动加载 (可选)
apply plugin: 'com.alibaba.arouter'
buildscript {
repositories {
mavenCentral()
}
dependencies {
classpath "com.alibaba:arouter-register:1.0.2"
}
}
可选使用,通过 ARouter 提供的注册插件进行路由表的自动加载(power by AutoRegister), 默认通过扫描 dex 的方式 进行加载通过 gradle 插件进行自动注册可以缩短初始化时间解决应用加固导致无法直接访问 dex 文件,初始化失败的问题,需要注意的是,该插件必须搭配 api 1.3.0 以上版本使用!
8. 使用 IDE 插件导航到目标类 (可选)
进阶用法
通过URL跳转
- 目标界面:
@Route(path = "/test/schameFilter")
class SchameFilterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_schame_filter)
}
}
- 界面配置:
<activity android:name=".activity.SchameFilterActivity">
<!-- Schame -->
<intent-filter>
<data
android:host="m.aliyun.com"
android:scheme="arouter"/>
<action android:name="android.intent.action.VIEW"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
</intent-filter>
</activity>
- 发送跳转操作:
val testUriMix = Uri.parse("arouter://m.aliyun.com/test/schameFilter")
ARouter.getInstance().build(testUriMix)
.withString("key1", "value1")
.navigation()
监听路由过程(跳转结果监听)
在路由跳转的过程中,我们可以监听路由的过程,只需要使用:
navigation(Context context, NavigationCallback callback)
fun doClick(view: View) {
when (view.id) {
R.id.btn1 -> {
// 1. 应用内简单的跳转
//ARouter.getInstance().build("/test/activity2").navigation()
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/activity2")
.withString("key1", "888")
.withLong("key2", 666L)
.withSerializable("key3", Test("Jack", "Rose"))
.navigation(this, object : NavigationCallback {
override fun onFound(postcard: Postcard?) {
Log.e("xyh", "onFound: " + "找到目标时回调");
}
override fun onLost(postcard: Postcard?) {
Log.e("xyh", "onLost: " + "找不到目标时回调");
}
override fun onArrival(postcard: Postcard?) {
Log.e("xyh", "onArrival: " + "跳转玩完");
}
override fun onInterrupt(postcard: Postcard?) {
Log.e("xyh", "onInterrupt: " + "被拦截");
}
})
}
}
}
@Autowired注解解析传递的参数
// 为每一个参数声明一个字段,并使用 @Autowired 标注
// URL中不能传递Parcelable类型数据,通过ARouter api可以传递Parcelable对象
@Route(path = "/test/activity")
public class Test1Activity extends Activity {
@Autowired
public String name;
@Autowired
int age;
// 通过name来映射URL中的不同参数
@Autowired(name = "girl")
boolean boy;
// 支持解析自定义对象,URL中使用json传递
@Autowired
TestObj obj;
// 使用 withObject 传递 List 和 Map 的实现了
// Serializable 接口的实现类(ArrayList/HashMap)
// 的时候,接收该对象的地方不能标注具体的实现类类型
// 应仅标注为 List 或 Map,否则会影响序列化中类型
// 的判断, 其他类似情况需要同样处理
@Autowired
List<TestObj> list;
@Autowired
Map<String, List<TestObj>> map;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ARouter.getInstance().inject(this);
// ARouter会自动对字段进行赋值,无需主动获取
Log.d("param", name + age + boy);
}
}
// 如果需要传递自定义对象,新建一个类(并非自定义对象类),然后实现 SerializationService,并使用@Route注解标注(方便用户自行选择序列化方式),例如:
@Route(path = "/yourservicegroupname/json")
public class JsonServiceImpl implements SerializationService {
@Override
public void init(Context context) {
}
@Override
public <T> T json2Object(String text, Class<T> clazz) {
return JSON.parseObject(text, clazz);
}
@Override
public String object2Json(Object instance) {
return JSON.toJSONString(instance);
}
}
// 2. 跳转并携带参数
ARouter.getInstance().build("/test/activity3")
.withString("name", "zly")
.withInt("age", 28)
.withBoolean("isGirl", true)
.withSerializable("key3", Test("Jack", "Rose"))
.navigation()
@Route(path = "/test/activity3")
public class Main3Activity extends AppCompatActivity {
//通过Autowired注解 & 将name作为属性的名称
@Autowired
public String name;
@Autowired
public int age;
//通过Autowired注解表明key
@Autowired(name = "isGirl")
public boolean girl;
@Autowired(name = "key3")
public Test test;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main3);
ARouter.getInstance().inject(this);
Log.e("xyh", "onCreate: " + name + "==" + age + "==" + girl + "==" + test.toString());
}
}
跳转界面带参(传递Object)
- 定义解析Obeject的SerializationService
/**
* 处理传递参数中自定义的Object---》withObject
*/
@Route(path = "/custom/json")
public class JsonSerializationService implements SerializationService {
Gson gson;
@Override
public <T> T json2Object(String input, Class<T> clazz) {
return gson.fromJson(input,clazz);
}
@Override
public String object2Json(Object instance) {
return gson.toJson(instance);
}
@Override
public <T> T parseObject(String input, Type clazz) {
return gson.fromJson(input,clazz);
}
@Override
public void init(Context context) {
gson = new Gson();
}
}
- 发送跳转操作
ARouter.getInstance().build("/test/1")
.withObejct("key4", new Test("Jack", "Rose"))
.navigation();
@Route(path = "/test/1")
public class YourActivity extend Activity {
...
SerializationService serializationService = ARouter.getInstance().navigation(SerializationService.class);
serializationService.init(this);
User obj = serializationService.parseObject(getIntent().getStringExtra("key4"), User.class);
}
声明拦截器(拦截跳转过程,面向切面编程)
比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查。
拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行。
// 比较经典的应用就是在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查
// 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
@Interceptor(priority = 8, name = "测试用拦截器")
public class TestInterceptor implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
...
callback.onContinue(postcard); // 处理完成,交还控制权
// callback.onInterrupt(new RuntimeException("我觉得有点异常")); // 觉得有问题,中断路由流程
// 以上两种至少需要调用其中一种,否则不会继续路由
}
@Override
public void init(Context context) {
// 拦截器的初始化,会在sdk初始化的时候调用该方法,仅会调用一次
}
}
自定义全局降级策略
// 实现DegradeService接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx")
public class DegradeServiceImpl implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
// do something.
}
@Override
public void init(Context context) {
}
}
为目标页面声明更多信息
// 我们经常需要在目标页面中配置一些属性,比方说"是否需要登陆"之类的
// 可以通过 Route 注解中的 extras 属性进行扩展,这个属性是一个 int值,换句话说,单个int有4字节,也就是32位,可以配置32个开关
// 剩下的可以自行发挥,通过字节操作可以标识32个开关,通过开关标记目标页面的一些属性,在拦截器中可以拿到这个标记进行业务逻辑判断
@Route(path = "/test/activity", extras = Consts.XXXX)
动态注册路由信息
适用于部分插件化架构的App以及需要动态注册路由信息的场景,可以通过 ARouter 提供的接口实现动态注册 路由信息,目标页面和服务可以不标注 @Route 注解,注意:同一批次仅允许相同 group 的路由信息注册
ARouter.getInstance().addRouteGroup(new IRouteGroup() {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/dynamic/activity", // path
RouteMeta.build(
RouteType.ACTIVITY, // 路由信息
TestDynamicActivity.class, // 目标的 Class
"/dynamic/activity", // Path
"dynamic", // Group, 尽量保持和 path 的第一段相同
0, // 优先级,暂未使用
0 // Extra,用于给页面打标
)
);
}
});
更多功能
1. 初始化中的其他设置
ARouter.openLog(); // 开启日志
ARouter.openDebug(); // 使用InstantRun的时候,需要打开该开关,上线之后关闭,否则有安全风险
ARouter.printStackTrace(); // 打印日志的时候打印线程堆栈
2. 详细的API说明
// 构建标准的路由请求
ARouter.getInstance().build("/home/main").navigation();
// 构建标准的路由请求,并指定分组
ARouter.getInstance().build("/home/main", "ap").navigation();
// 构建标准的路由请求,通过Uri直接解析
Uri uri;
ARouter.getInstance().build(uri).navigation();
// 构建标准的路由请求,startActivityForResult
// navigation的第一个参数必须是Activity,第二个参数则是RequestCode
ARouter.getInstance().build("/home/main", "ap").navigation(this, 5);
// 直接传递Bundle
Bundle params = new Bundle();
ARouter.getInstance()
.build("/home/main")
.with(params)
.navigation();
// 指定Flag
ARouter.getInstance()
.build("/home/main")
.withFlags();
.navigation();
// 获取Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
// 对象传递
ARouter.getInstance()
.withObject("key", new TestObj("Jack", "Rose"))
.navigation();
// 觉得接口不够多,可以直接拿出Bundle赋值
ARouter.getInstance()
.build("/home/main")
.getExtra();
// 转场动画(常规方式)
ARouter.getInstance()
.build("/test/activity2")
.withTransition(R.anim.slide_in_bottom, R.anim.slide_out_bottom)
.navigation(this);
// 转场动画(API16+)
ActivityOptionsCompat compat = ActivityOptionsCompat.
makeScaleUpAnimation(v, v.getWidth() / 2, v.getHeight() / 2, 0, 0);
// ps. makeSceneTransitionAnimation 使用共享元素的时候,需要在navigation方法中传入当前Activity
ARouter.getInstance()
.build("/test/activity2")
.withOptionsCompat(compat)
.navigation();
// 使用绿色通道(跳过所有的拦截器)
ARouter.getInstance().build("/home/main").greenChannel().navigation();
// 使用自己的日志工具打印日志
ARouter.setLogger();
// 使用自己提供的线程池
ARouter.setExecutor();
3. 获取原始的URI
String uriStr = getIntent().getStringExtra(ARouter.RAW_URI);
4. 重写跳转URL
// 实现PathReplaceService接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx") // 必须标明注解
public class PathReplaceServiceImpl implements PathReplaceService {
/**
* For normal path.
*
* @param path raw path
*/
String forString(String path) {
return path; // 按照一定的规则处理之后返回处理后的结果
}
/**
* For uri type.
*
* @param uri raw uri
*/
Uri forUri(Uri uri) {
return url; // 按照一定的规则处理之后返回处理后的结果
}
}
5. 生成路由文档
// 更新 build.gradle, 添加参数 AROUTER_GENERATE_DOC = enable
// 生成的文档路径 : build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: "enable"]
}
}
}
}
组件间数据共享
可以获取其他组件的数据,和调用其他组件的方法。
通过依赖注入解耦:服务管理(一) 暴露服务
- 定义接口继承IProvider
// 声明接口,其他组件通过接口来调用服务
public interface HelloService extends IProvider {
String sayHello(String name);
//可以定义多个需要暴露的方法
}
- 实现接口
// 实现接口
@Route(path = "/yourservicegroupname/hello", name = "测试服务")
public class HelloServiceImpl implements HelloService {
@Override
public String sayHello(String name) {
return "hello, " + name;
}
@Override
public void init(Context context) {
}
}
通过依赖注入解耦:服务管理(二) 发现服务
public class Test {
@Autowired
HelloService helloService;
@Autowired(name = "/yourservicegroupname/hello")
HelloService helloService2;
HelloService helloService3;
HelloService helloService4;
public Test() {
ARouter.getInstance().inject(this);
}
public void testService() {
// 1. (推荐)使用依赖注入的方式发现服务,通过注解标注字段,即可使用,无需主动获取
// Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)
helloService.sayHello("Vergil");
helloService2.sayHello("Vergil");
// 2. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
helloService3 = ARouter.getInstance().navigation(HelloService.class);
helloService4 = (HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation();
helloService3.sayHello("Vergil");
helloService4.sayHello("Vergil");
}
}
预处理服务
// 实现 PretreatmentService 接口,并加上一个Path内容任意的注解即可
@Route(path = "/xxx/xxx")
public class PretreatmentServiceImpl implements PretreatmentService {
@Override
public boolean onPretreatment(Context context, Postcard postcard) {
// 跳转前预处理,如果需要自行处理跳转,该方法返回 false 即可
}
@Override
public void init(Context context) {
}
}
Fragment 路由
创建 Fragment 类,并且添加路由注解
/**
* Created by xiaoyehai on 2018/8/17 0017.
*/
@Route(path = "/com/MyFragment")
public class MyFragment extends Fragment {
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_my, container, false);
return view;
}
}
获取 Fragment 实例
@Route(path = "/com/Main2Activity ")
public class Main2Activity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
//获取Fragment 实例
Fragment fragment = (Fragment) ARouter.getInstance().build("/com/MyFragment").navigation();
//添加Fragment
FragmentManager fragmentManager = getSupportFragmentManager();
FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction();
fragmentTransaction.add(R.id.fl_content, fragment);
fragmentTransaction.commit();
}
}
ARouter原理
从A界面跳转到B界面这个过程,我们看看arouter与界面间关系,如下图:
1.注册
B界面将类的信息,通过key-value的形式,注册到arouter中。
2.查询
A界面将类信息与额外信息(传输参数、跳转动画等),通过key传递至arouter中,并查询对应需要跳转类的信息。
3.结合
将A界面类信息、参数与B界面的类信息进行封装结合。
4.跳转
将结合后的信息,使用startActivity实现跳转。
A界面跳转到B界面,arouter做了以下工作:
从上图流程中,我们可以发现Arouter中原理:
- 通过apt技术利用注解编译时生成类,封装目标界面类的类信息。
- 在初始化时,把编译生成的类通过key-value的方式存储在arouter中。
- 发送操作者通过key获取到目标界面类的信息。
- 把发送操作者的信息与目标界面类信息进行结合或者关联在一起。
- 实现跳转功能。
其实简单概括:将需要相互跳转的界面信息传递至arouter中存储关联 & 实现跳转。