ARouter 是阿里开源的,可以看成是 Android 平台中对页面、服务提供路由功能的中间件。
ARouter 直接翻译过来就是路由,可以用来映射页面关系,实现跳转相关的功能。在 Android 中,常被用来进行组件化通讯。
为什么要使用 ARouter我们知道 Android 中默认为我们提供了跳转的功能,比如 startActivity,startService 等。那为什么还需要路由框架呢?在我看来,主要有以下几点吧:
-
在一些复杂的业务场景下(比如电商),灵活性比较强,很多功能都是运营人员动态配置的,比如下发一个活动页面,我们事先并不知道具体的目标页面,但如果事先做了约定,提前做好页面映射,便可以自由配置。
-
随着业务量的增长,客户端必然随之膨胀,开发人员的工作量越来越大,比如64K问题,比如协作开发问题。App一般都会走向组件化、插件化的道路,而组件化、插件化的前提就是解耦,那么我们首先要做的就是解耦页面之间的依赖关系。
ARouter github 地址: ARouter
ARouter 的接入也非常简单,一般来说,需要以下几个步骤。
第一步:配置 gradle 文件android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [ moduleName : project.getName() ]
}
}
}
}
dependencies {
//阿里路由
implementation 'com.alibaba:arouter-api:1.4.0'
annotationProcessor 'com.alibaba:arouter-compiler:1.2.1'
}
第二步:初始化 SDK
if (isDebug()) { // These two lines must be written before init, otherwise these configurations will be invalid in the init process
ARouter.openLog(); // Print log
ARouter.openDebug(); // Turn on debugging mode (If you are running in InstantRun mode, you must turn on debug mode! Online version needs to be closed, otherwise there is a security risk)
}
ARouter.init(mApplication); // As early as possible, it is recommended to initialize in the Application
第三步:添加 @Route 注解
// Add annotations on pages that support routing (required)
// The path here needs to pay attention to need at least two levels : /xx/xx
@Route(path = ARouterConstants.COM_ACTIVITY1)
public class ActivityOne extends AppCompatActivity {
-----
}
public static final String COM_ACTIVITY1 = COM + "Activity1";
第四步:调用跳转的代码
ARouter.getInstance().build(ARouterConstants.COM_ACTIVITY1).navigation();
Activity 之间的跳转
假设我们现在要从 A 页面跳转到 B 页面,那我们要怎么办呢?
第一步:在目标页面使用 @Route 注解,并指定 path
@Route(path = ARouterConstants.COM_ACTIVITY1)
public class ActivityOne extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_one);
}
}
第二步:调用 navigation 方法实现跳转
ARouter.getInstance().build(ARouterConstants.COM_ACTIVITY1).navigation();
这样,从 A 跳转到 B 的功能便实现了。
传递参数Arouter 的跳转页非常简单,我们可以调用 PostCard 的 withX 等方法传递相应的参数
比如,我们想传递 String,可以调用 withString,想传递 int,可以调用 withInt ,想传递 Parceable 对象,可以调用 withParcelable。
ARouter.getInstance().build(ARouterConstants.COM_PARSE_ACTIVITY).withString(NAME,"jun")
.withInt(AGE,1).withParcelable(PERSON,person).withObject(TEST_OBJ,testObj)
同时 ARouter 还支持传递 Object 对象,只需调用 withObject 方法,同时需要在我们的 moudle 下面增加相关的类。实际上,它的原理是通过将 object 转化成 String,然后存进 intent 中,在解析参数的时候,再通过相应的 key 去除 String,然后转化成 object。
如果你的项目使用的是 Gson,那可以使用下面的类
@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
private Gson mGson;
@Override
public void init(Context context) {
mGson = new Gson();
}
@Override
public T json2Object(String text, Class clazz) {
checkJson();
return mGson.fromJson(text, clazz);
}
@Override
public String object2Json(Object instance) {
checkJson();
return mGson.toJson(instance);
}
@Override
public T parseObject(String input, Type clazz) {
checkJson();
return mGson.fromJson(input, clazz);
}
public void checkJson() {
if (mGson == null) {
mGson = new Gson();
}
}
}
如果你的项目使用的是阿里巴巴的 fastjson,那可以在你的项目增加该类。
@Route(path = "/service/json")
public class JsonServiceImpl implements SerializationService {
@Override
public void init(Context context) {
}
@Override
public T json2Object(String text, Class clazz) {
return JSON.parseObject(text, clazz);
}
@Override
public String object2Json(Object instance) {
return JSON.toJSONString(instance);
}
@Override
public T parseObject(String input, Type clazz) {
return JSON.parseObject(input, clazz);
}
}
解析参数
在 ActivityB 中获取参数有两种方式
- 一种是普通 Activity 那样 getIntent().getXXX,这里就不展开了
- 另外一种是使用 @Autowired 注解的方式,记得需要在接收参数地方ARouter.getInstance.inject(thhis)
@Route(path = ARouterConstants.COM_PARSE_ACTIVITY)
public class ParseActivity extends AppCompatActivity {
private static final String TAG = "ParseActivity";
@Autowired
String name;
@Autowired
int age;
@Autowired
Person person;
@Autowired
TestObj mTestObj;
@Autowired // 注意字段的名称必须与 withObject 的 key 一致
TestObj testObj;
private android.widget.TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_parse);
// 调用 inject 方法,如果传递过来的参数含有,这样使用 @Autowired 的会自动解析
ARouter.getInstance().inject(this);
}
实现跳转并获取返回结果
在 activity 的跳转中,我们知道,我们可以用 startActivityForResult 来获取返回结果,那在 ARouter 中要怎么实现呢。
ARouter 中并没有提供这样的接口,但是我们可以采用曲线救国的原理,通过 Postcard 实现
Postcard postcard = ARouter.getInstance().build(ARouterConstants.COM_ACTIVITY_RESULT);
LogisticsCenter.completion(postcard);
Class destination = postcard.getDestination();
这里得到的 destination 类就是我们要跳转的类,这样 fragment 的 startActivityForResult 就好办了
Intent intent = new Intent(getContext(),destination);
startActivityForResult(intent,requestCode);
暴露服务
这里说到的服务不是 Android 四大组件中的 Service,这里的服务是接口开发的概念。即把部分功能或者业务封装起来。
比如,我们想调用某个接口,一般来说,可以这样做。
- 首先我们定义一个接口,并实现该接口,并采用 @Route 注解指定相应的 path:
public interface HelloService extends IProvider {
String sayHello(String name);
}
// 实现接口
@Route(path = ARouterConstants.SERVICE_HELLO, name = "test service")
public class HelloServiceImpl implements HelloService {
private Context mContext;
@Override
public String sayHello(String name) {
Toast.makeText(mContext,this.getClass().getSimpleName()+": sayHello"+" "+name,Toast.LENGTH_SHORT).show();
return "hello, " + name;
}
@Override
public void init(Context context) {
mContext = context;
}
}
- 接着调用 Poatcard 的 navigation 方法获取到我们的实例:
HelloService helloService = (HelloService) ARouter.getInstance().build(ARouterConstants.SERVICE_HELLO).navigation();
String result = helloService.sayHello("xujun");
URL 跳转
我们先来看一下我们 URL 跳转的设计
从图中可以看到,我们是用一个中间跳转页面来管理所有 Activity 的跳转的,当接受到跳转指令的时候,中转 Activity 会进行相应的处理,从而跳转到响应的页面。
这样设计的好处是:
- 我们的目标 Activity(页面 A,页面 B 等)不需要对外暴露
中转 Activity:
public class UrlSchemeActivity extends AppCompatActivity {
private static final String TAG = "UrlSchemeActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_url);
// 直接通过ARouter处理外部Uri
final Uri uri = getIntent().getData();
Log.i(TAG, "onCreate: uri=" + uri);
ARouter.getInstance().build(uri).navigation(this, new NavCallback() {
@Override
public void onArrival(Postcard postcard) {
finish();
}
@Override
public void onLost(Postcard postcard) {
super.onLost(postcard);
Log.i(TAG, "onLost: uri=" + uri);
// Toast.makeText(UrlSchemeActivity.this,String.format("找不到可以处理该
// URI %s 的 Activity",uri),Toast.LENGTH_SHORT).show();
// 找不到的时候 finish 掉当前 activity
finish();
}
});
}
}
当我们接收到跳转 uri 的时候,我们将它交给路由 ARouter,去进行分发。
接下来我们来看一下我们在 AndroidManifest 的配置
这里面的 host 、scheme 字段很重要。点击 url 会根据这两个字段会调起本地的 Activity 。
接下来,看一下我们的 HTML
跳转测试
自定义Scheme[通常来说都是这样的]
arouter://m.aliyun.com/test/activity1
测试URL Encode情况
arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180&
obj={"name":"jack","id":"666"}
注意 a 标签里面的 arouter://m.aliyun.com 分别代表着 scheme 、host ;/com/URLActivity1 就是目标 Activity 的注解。
如果需要接收 URL 中的参数,需要在 Activity 调用自动注入初始化方法;
ARouter.getInstance().inject(this);
需要注意的是,如果不使用自动注入,那么可以不写 ARouter.getInstance().inject(this),但是需要取值的字段仍然需要标上 @Autowired 注解,因为 只有标上注解之后,ARouter 才能知道以哪一种数据类型提取 URL 中的参数并放入 Intent 中,这样您才能在 intent 中获取到对应的参数
其他用法 监听 ARouter 的执行过程,两种方式:监听单一路由执行:
/**
* 测试单一路由,降级监听,例如单一路由失败的话进行后续处理
*/
private void ARouterCallBack() {
ARouter.getInstance()
.build("/com/hq")
.navigation(this, new NavCallback() {
@Override
public void onFound(Postcard postcard) {
Log.e(TAG, "onArrival: 找到了 ");
}
@Override
public void onLost(Postcard postcard) {
Log.e(TAG, "onArrival: 找不到了 ");
Toast.makeText(MainActivity.this, "未找到目标页面 path=" + postcard.getPath() + " group=" + postcard.getGroup() +
" 做降级处理,5s后跳转降级页", Toast.LENGTH_LONG).show();
jumpDegradePage(MainActivity.this, postcard);
}
@Override
public void onArrival(Postcard postcard) {
Log.e(TAG, "onArrival: 跳转完了 ");
}
@Override
public void onInterrupt(Postcard postcard) {
Log.e(TAG, "onArrival: 被拦截了 ");
}
});
}
在当前的 moudle 中,如果有找到 @Route(path=ARouterConstants.COM_ACTIVITY1) 注解的目标 activity,会先后回调 onFound,onArrival;如果找不到的话,会回调 onLost;如果被拦截了,会回调 onInterrupt 方法。
执行全局路由监听:
首先注册需要跳转的页面:
/**
* 测试全局降级监听,全局路由监听,当找不到目标页面时,会调用DefaultDegrade
* 走onLost方法,处理找不到页面的后续操作
*/
private void ARouterDownDelegate() {
ARouter.getInstance().build("/com/hq").navigation();
}
然后实现DegradeService,在这里进行全局路由监听:
/**
* 全局降级监听,当找不到目标页面时,会调用这里
*/
@Route(path = "/hehe/1a")
public class DefaultDegrade implements DegradeService {
@Override
public void onLost(Context context, Postcard postcard) {
Log.e("buder", "onLost");
if (context instanceof Activity) {
Toast.makeText(context, "未找到目标页面 path=" + postcard.getPath() +
" group=" + postcard.getGroup() + " 做降级处理,5s后跳转降级页", Toast.LENGTH_LONG).show();
}
jumpDegradePage(context, postcard);
}
@Override
public void init(Context context) {
Log.e("buder", "init");
//跳转目标activity未找到时,创建此降级服务实例,懒加载
}
private void jumpDegradePage(final Context context, final Postcard postcard) {
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
ARouter.getInstance().build(ARouterConstants.DEGRADE)
.navigation(context);
}
}, 5 * 1000);
}
}
Interceptor 拦截器
@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
}
}
拦截器的使用面向切面编程
// 在跳转过程中处理登陆事件,这样就不需要在目标页重复做登陆检查
// 拦截器会在跳转之间执行,多个拦截器会按优先级顺序依次执行
@Interceptor(name = "login", priority = 6)
public class LoginInterceptorImpl implements IInterceptor {
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
String path = postcard.getPath();
LogUtils.e(path);
boolean isLogin = SPUtils.getInstance().getBoolean(RoutePath.SP_IS_LOGIN, false);
if (isLogin) { // 如果已经登录不拦截
callback.onContinue(postcard);
} else { // 如果没有登录
switch (path) {
// 不需要登录的直接进入这个页面
case RoutePath.LOGIN_PATH:
case RoutePath.FIRST_PATH:
callback.onContinue(postcard);
break;
// 需要登录的直接拦截下来
default:
callback.onInterrupt(null);
break;
}
}
}
@Override
public void init(Context context) {//此方法只会走一次
LogUtils.e("路由登录拦截器初始化成功");
}
}
//启动Activity
ARouter.getInstance().build(RoutePath.SECOND_PATH)
.withString("msg", "ARouter传递过来的需要登录的参数msg")
.navigation(this,new LoginNavigationCallbackImpl());//第二个参数是路由跳转的回调
public class LoginNavigationCallbackImpl implements NavigationCallback {
@Override //找到了
public void onFound(Postcard postcard) {
}
@Override //找不到了
public void onLost(Postcard postcard) {
}
@Override //跳转成功了
public void onArrival(Postcard postcard) {
}
@Override
public void onInterrupt(Postcard postcard) {
String path = postcard.getPath();
LogUtils.v(path);
Bundle bundle = postcard.getExtras();
// 被登录拦截了下来了
// 需要调转到登录页面,把参数跟被登录拦截下来的路径传递给登录页面,登录成功后再进行跳转被拦截的页面
ARouter.getInstance().build(RoutePath.LOGIN_PATH)
.with(bundle)
.withString(RoutePath.PATH, path)
.navigation();
}
}
第一篇参考:https://www.jianshu.com/p/a57dd8c8f10e
https://blog.csdn.net/gdutxiaoxu/article/details/81036719
第二篇参考:https://www.jianshu.com/p/d5a83ccf1135
项目代码:
ARouter的基础使用 : https://github.com/buder-cp/DesignPattern/tree/master/ARounterDemo-master
ARouter拦截器使用:https://github.com/buder-cp/DesignPattern/tree/master/ARouteLogin-master