探索Android路由框架-ARouter
博客中代码已上传github,点击此处即可到达
ARouter:一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
ARouter官方项目地址
一、功能介绍
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射关系按组分类、多级管理,按需初始化
- 支持用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 支持多种方式配置转场动画
- 支持获取Fragment
- 完全支持Kotlin以及混编(配置见文末 其他#5)
- 支持第三方 App 加固(使用 arouter-register 实现自动注册)
- 支持生成路由文档
- 提供 IDE 插件便捷的关联路径和目标类
二、典型应用
- 从外部URL映射到内部页面,以及参数传递与解析
- 跨模块页面跳转,模块间解耦
- 拦截跳转过程,处理登陆、埋点等逻辑
- 跨模块API调用,通过控制反转来做组件解耦
三、对比与接入
原生跳转方式的不足
显式跳转,
Intent intent = new Intent(activity, XXActivity.class);
由于需要直接持有对应class,从而导致了强依赖关系,提高了耦合度
隐式跳转,譬如
Intent intent = new Intent();
intent.setAction(“com.android.activity.MY_ACTION”);
action等属性的定义在Manifest,导致了扩展性较差,规则集中式管理,导致协作变得非常困难。Manifest文件会有很多过滤配置,而且非常不利于后期维护。
添加依赖:
注意,需要将library也添加进入依赖
android {
defaultConfig {
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
implementation ('com.alibaba:arouter-api:1.4.1'){
exclude group: 'com.android.support'
}//(support 是我自己添加,排除依赖冲突)
annotationProcessor 'com.alibaba:arouter-compiler:1.2.2'
implementation project(':modulerone')//示例中个人lib
implementation project(':modulertwo')//示例中个人lib
初始化SDK
if (isDebug()) { // 这两行必须写在init之前,否则这些配置在init过程中将无效
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // 开启调试模式(如果在InstantRun模式下运行,必须开启调试模式!线上版本需要关闭,否则有安全风险)
}
ARouter.init(mApplication); // 尽可能早,推荐在Application中初始化
别忘记了在清单文件里面配置自定义的Application和Activity。
项目依赖导入和初始化就已经完成了,下面就开始正式的功能使用以及简单的封装。
开始使用:
-
1)在Activity/Fragment类上面写上 Route path 注解。
这里的路径需要注意的是至少需要有两级,/xx/xx
ARouter::Extract the default group failed, the path must be start with '/' and contain more than 2 '/'!
2)在Activity/Fragment类oncreate()里面进入Arouter 注入,也就是:
ARouter.getInstance().inject(this);
3)目标的Activity类上面需要声明Route path 注解,以此对应(跳转如果不对应路径,框架会Toast说路径不匹配)
简单页面跳转
@Route(path = "/app/MainActivity")
public class MainActivity extends AppCompatActivity {
TextView tv_mvp, tv_arouter;
// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
ARouter.getInstance().inject(this);
// Example of a call to a native method
TextView tv = findViewById(R.id.sample_text);
tv_mvp = findViewById(R.id.tv_entry_mvp);
tv_arouter = findViewById(R.id.tv_entry_arouter);
tv.setText(JNIManage.getInstance().getNums());
setOnClicks();
}
private void setOnClicks() {
tv_mvp.setOnClickListener((view) -> {
Intent intent = new Intent(this, MVPActivity.class);
startActivity(intent);
});
tv_arouter.setOnClickListener((view -> {
//用过ARouter的同学应该知道,用ARouter启动Activity应该是下面这个写法:
ARouter.getInstance().build("/moduleone/ModulerMainActivity").navigation();
}));
}
}
@Route(path = "/moduleone/ModulerMainActivity")
public class ModulerMainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_moduler_main);
ARouter.getInstance().inject(this);
}
}
其中,build里面是页面的标签路径,对应的就是目标Activity的这里,也就是类注释标签路径要一致:
带参数的界面跳转
带参数的跳转是很常见的功能,Android可以通过Bundle去传递参数,如果使用ARouter框架,它传递参数通过以下去操作:
ARouter传递对象的时候,首先该对象需要Parcelable或者Serializable序列化,Android Studio已经有一些插件帮我们自动生成Parcelable序列化了(因为Android用Parcelable序列化优势会更加明显一些)
字符串、char、int等基本数据类型当然都是可以传递
当然,它也可以直接传Bundle、数组、列表等很多对象,
携带参数的界面跳转,简单使用如下
tv_arouter.setOnClickListener((view -> {
ARouter.getInstance().build("/moduleone/ModulerMainActivity")
.withString("name", "DeLux")
.withInt("age", 25)
.withParcelable("book", new Books("prince", 25, 20189, "HuNanPress"))
.navigation();
}));
其中,第一个参数代表的是参数的key,第二个参数对应的是我们要传递的属性值,也就是value
那么目标界面如何获取传递过来的值?
这个时候,我们需要在目标界面,使用Autowired注解
@Route(path = "/moduleone/ModulerMainActivity")
public class ModulerMainActivity extends AppCompatActivity {
private TextView mShowMsg;
@Autowired
String name;
@Autowired
int age;
@Autowired(name = "book")
Books books;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_moduler_main);
mShowMsg = findViewById(R.id.tv_module_two_show);
// ARouter会自动对字段进行赋值,无需主动获取
ARouter.getInstance().inject(this);
printMsg();
}
private void printMsg() {
mShowMsg.setText("name " + name + " age " + age + " book is " + books.toString());
}
}
所以为了规避每一个可能会遇到的风险,建议在@Autowired里面 都写上与之对应具体的key名。
界面跳转动画
直接调用withTransition,里面传入两个动画即可(R.anim.xxx,R.anim.xxx)
使用URI进行跳转
ARouter框架也可以使用URI进行匹配跳转,代码也很少,只需匹配路径一致即可完成跳转:
tv_arouter.setOnClickListener((view -> {
ARouter.getInstance().build(Uri.parse("/moduleone/ModulerMainActivity"))
.withString("name", "DeLux")
.withInt("age", 25)
.withParcelable("book", new Books("ShuiXu", 396, 3012345, "HuNanPress"))
.navigation();
}));
进阶用法之拦截器:
拦截器是ARouter这一款框架的亮点。ARouter中的拦截器就实现了这种功能,可以在跳转过程中添加自定义的功能,比如添加携带参数,判断是否需要登录等,是针对AOP切面编程思想的实现。
ARouter的拦截器,是通过实现 IInterceptor接口,重写init()和process()方法去完成拦截器内部操作的。
首先我们定义两个拦截器:
@Interceptor(priority = 7)
public class SaidInterceptor implements IInterceptor {
private static final String TAG = "LoginInterceptor";
private Context mContext;
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
Log.i(TAG, "SaidInterceptor process");
}
@Override
public void init(Context context) {
mContext = context;
Log.i(TAG, "SaidInterceptor init");
}
}
@Interceptor(priority = 1)
public class LoginInterceptor implements IInterceptor {
private static final String TAG = "LoginInterceptor";
private Context mContext;
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String name=Thread.currentThread().getName();
Log.i(TAG, "LoginInterceptor begain to process"+"thread name is"+name);
if (postcard.getPath().equals("/moduleone/ModulerMainActivity")){
Log.i(TAG, "LoginInterceptor process is interceptor ");
}
}
@Override
public void init(Context context) {
mContext = context;
Log.i(TAG, "LoginInterceptor init");
}
}
2-26 08:05:32.562 7115-7138/ming.com.andcode I/LoginInterceptor: LoginInterceptor init
12-26 08:05:32.562 7115-7138/ming.com.andcode I/LoginInterceptor: SaidInterceptor init
12-26 08:05:43.127 7115-7115/ming.com.andcode I/LoginInterceptor: Interceptor onFound
12-26 08:05:43.129 7115-7137/ming.com.andcode I/LoginInterceptor: LoginInterceptor begain to processthread name isARouter task pool No.1, thread No.3
12-26 08:05:43.129 7115-7137/ming.com.andcode I/LoginInterceptor: LoginInterceptor process is interceptor
根据实验得知,使用Interceptor类注解的priority数值越小,越先执行,优先级越高。(四大组件中的广播,优先级的取值是 -1000到1000,数值越大优先级越高)
通过Postcard可以获取到路径的组以及全路径,那么,路径的组(Group)又是什么?是这样,一般来说,ARouter在编译期框架扫描了所有的注册页面/字段/拦截器等,那么很明显运行期不可能一股脑全部加载进来,这样就太不和谐了。所以就使用分组来管理,我们的类标签里面的注释,对于group默认是 “ ”(空字符串)如下图:
在 Group简单使用 这张图上面,根据日志,打印了分组的信息,可以发现Group的值默认就是第一个 / /(两个分隔符) 之间的内容。
那么,我们也可以自定义分组,来进行界面跳转,所以ARouter又提供了一种解决方案:
自定义分组 实现跳转界面
如果使用自定义分组来跳转界面,只需要在源代码改动以下三个位置:
1:类注解新增 group,赋值我们自定义的组名
tv_arouter.setOnClickListener((view -> {
ARouter.getInstance().build("/moduleone/ModulerMainActivity", "module")
.withString("name", "DeLux")
.withInt("age", 25)
.withParcelable("book", new Books("ShuiXu", 396, 3012345, "HuNanPress"))
.navigation(MainActivity.this, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
String group = postcard.getGroup();
String path = postcard.getPath();
Log.i(TAG, "Interceptor onFound group is " + group + " path is " + path);
}
@Override
public void onLost(Postcard postcard) {
Log.i(TAG, "Interceptor onLost");
}
@Override
public void onArrival(Postcard postcard) {
Log.i(TAG, "Interceptor onArrival");
}
@Override
public void onInterrupt(Postcard postcard) {
Log.i(TAG, "Interceptor onInterrupt");
}
});
}));
@Route(path = "/app/MainActivity", group = "demo")
public class MainActivity extends AppCompatActivity {
}
@Route(path = "/moduleone/ModulerMainActivity",group = "module")
public class ModulerMainActivity extends AppCompatActivity {
}
12-26 08:26:20.646 7488-7511/ming.com.andcode I/LoginInterceptor: LoginInterceptor init
12-26 08:26:20.647 7488-7511/ming.com.andcode I/LoginInterceptor: SaidInterceptor init
12-26 08:26:28.948 7488-7488/ming.com.andcode I/LoginInterceptor: Interceptor onFound group is module path is /moduleone/ModulerMainActivity
12-26 08:26:28.950 7488-7512/ming.com.andcode I/LoginInterceptor: LoginInterceptor begain to processthread name isARouter task pool No.1, thread No.5
12-26 08:26:28.950 7488-7512/ming.com.andcode I/LoginInterceptor: LoginInterceptor process is interceptor
通过Postcard可以获取到路径的组以及全路径,那么,路径的组(Group)又是什么?是这样,一般来说,ARouter在编译期框架扫描了所有的注册页面/字段/拦截器等。
ARouter如何实现类似startActivityForResult()?
这种应用场景也是很常见的,那ARouter该如何实现?(需将项目中拦截器注释掉,因为本博客拦截器未做其他处理,将跳转拦截了)
第一步:为了方便看效果,我们在第一个Activity设置requestCode 为1314。
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
switch (requestCode) {
case 1314:
Log.i(TAG, "onActivityResult resultCode is " + requestCode+" resultCode is "+resultCode);
break;
default:
break;
}
}
第二步:需要在跳转的navigation方法(这是一个方法重载)里面的第二个参数,设置我们定义的requestCode,(通过匹配requestCode 来实现该功能)
tv_arouter.setOnClickListener((view -> {
ARouter.getInstance().build("/moduleone/ModulerMainActivity", "module")
.withString("name", "DeLux")
.withInt("age", 25)
.withParcelable("book", new Books("ShuiXu", 396, 3012345, "HuNanPress"))
.navigation(MainActivity.this,1314);
}));
第三步:在第二个界面的setResult方法里面,写上对应的resultCode,这里就不展示Intent数据了
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_moduler_main);
mShowMsg = findViewById(R.id.tv_module_two_show);
// ARouter会自动对字段进行赋值,无需主动获取
ARouter.getInstance().inject(this);
printMsg();
setResult(1314);
}
综合上面三个步骤,项目编译运行,跳转到第二个界面然后返回上一个界面,日志成功打印:
12-26 08:42:02.145 8060-8080/ming.com.andcode I/LoginInterceptor: LoginInterceptor init
12-26 08:42:02.146 8060-8080/ming.com.andcode I/LoginInterceptor: SaidInterceptor init
12-26 08:42:26.819 8060-8083/ming.com.andcode I/LoginInterceptor: LoginInterceptor begain to processthread name isARouter task pool No.1, thread No.4
12-26 08:42:26.819 8060-8083/ming.com.andcode I/LoginInterceptor: LoginInterceptor process is interceptor
12-26 08:43:48.675 8293-8293/ming.com.andcode I/LoginInterceptor: onActivityResult has receieve the msg requestCode is 1314 resultCode is 1314
ARouter路由框架的基本使用就介绍到这里,下一篇我们将仿照ARouter自定义一个路由框架,大家一起来嗨。
代码已上传github,点击此处即可到达