【笔记】【Android】ARouter
介绍
一个用于帮助 Android App 进行组件化改造的框架 —— 支持模块间的路由、通信、解耦
- 支持直接解析标准 URL 进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持 InstantRun
- 支持 MultiDex (Google 方案)
- 映射关系按组分类、多级管理,按需初始化
- 支持用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 支持多种方式配置转场动画
- 支持获取 Fragment
- 完全支持 Kotlin 以及混编
- 支持第三方 App 加固 (使用 arouter-register 实现自动注册)
- 支持生成路由文档
- 提供 IDE 插件便捷的关联路径和目标类
典型应用
- 从外部 URL 映射到内部页面,以及参数传递与解析
- 跨模块页面跳转,模块间解耦
- 拦截跳转过程,处理登陆、埋点等逻辑
- 跨模块 API 调用,通过控制反转来做组件解耦
优势
- 直接解析 URL 路由,解析参数并赋值到对应目标字段的页面中。
- 支持多模块项目,因为现在很少有 APP 是单模块的项目,一般都是多模块单工程的,由不同的团队负责不同的模块开发,这时候支持多模块项目开发就显得尤为重要
- 支持 InstantRun,目前很多路由框架并不支持 InstantRun,而 InstantRun 是 Google 在 AndroidStudio2.0 阿尔法版本中提供的新功能,其类似于代码的日更新,其只不过面向的是开发过程,这样做可以在开发的过程中减少开发和编译的次数,可以简单地将代码修改即时地同步到 APK 中,从而可以大规模降低开发复杂度。
- 允许自定义拦截器,ARouter 是支持拦截器的,而拦截器其实就是 AOP 的实现,可以自定义多个拦截器解决一些面向行为编程上出现的问题。
- ARouter 可以提供 IoC 容器,IoC 其实就是控制反转,这一部分做过服务端开发的朋友可能比较了解,因为服务端开发经常用到的 Spring 框架能够提供的一个非常重要的能力就是控制反转。
- 映射关系自动注册,在页面不是很多的小型 APP 上面,自动注册并不会体现出太大优势,但是对于大型 APP 而言,可能页面数量已经达到的几十个或者数百个,在这样的情况下,自动注册就显得非常重要了,因为不可能将每一个页面都通过代码的方式进行注册。
- 灵活的降级策略,ARouter 可以提供很多种降级策略供用户自行选择,而原生的路由方案存在无法灵活降级的问题,StartActivity () 一旦失败将会抛出运营级异常。
使用
基本使用及页面注册源码解析
- 在使用之前需要先配置 gradle,目前 api 最新的版本是 1.3.1,处理器的最新版本是 1.1.4
dependencies {
// 替换成最新版本, 需要注意的是api
// 要与compiler匹配使用,均使用最新版可以保证兼容
implementation 'com.alibaba:arouter-api:1.3.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.1.4'
...
}
- 接着需要尽早的初始化 ARouter,可以考虑在 Application 中进行初始化:
ARouter.init(mApplication);
- 在需要支持路由跳转的页面上添加 @Route 注解,路径 path 至少需要有两级,比如需要跳转到 LoginActivity, 那么需要在 Activity 上面配置 path, 例如 /activity/LoginActivity。
@Route(path = "/activity/LoginActivity")
public class LoginActivity extends BaseActivity< LoginActivityIMP> implements LoginActivityContract.View
- 然后直接像下面这样就就可以实现简单的跳转了
ARouter.getInstance().build("/activity/LoginActivity").navigation();
- 如果需要携带参数跳转的话就像下面这样
- ARouter 提供了很多的 withXX 方法,可以携带基本类型,Object,Parcelable 等等,里面的原理也是通过 Bundle 进行携带 mBundle.putXX 实现
ARouter.getInstance().build("/activity/LoginActivity").withString("key", "value").navigation();
- 如果需要实现类似 startActivityForResult, 只需要在 navigation 中传入两个参数,第一个就是 Context,第二个参数是 requestCode。
ARouter.getInstance().build("/activity/LoginActivity").navigation(this,1);
页面跳转源码分析
- ARouter.getInstance (), 是获得 ARouter.init (mApplication) 初始化的事例
public static ARouter getInstance() {
if (!hasInit) {
throw new InitException("ARouter::Init::Invoke init(context) first!");
} else {
if (instance == null) {
synchronized (ARouter.class) {
if (instance == null) {
instance = new ARouter();
}
}
}
return instance;
}
}
- 然后.build () 构造路由信息的容器 Postcard
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
- 得到 ARouter 实例后调用 build 方法,传入目标页面的 path (“/activity/LoginActivity”),我们来看看 build 的源码。其实是调用_ARouter 的 build 方法,这里需要提的一点是 ARouter.init 也是调用的_ARouter 的 init 方法
public static void init(Application application) {
if (!hasInit) {
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, "ARouter init start.");
hasInit = _ARouter.init(application);
if (hasInit) {
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, "ARouter init over.");
}
}
- 然后在看_ARouter 的 build 方法
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return build(path, extractGroup(path));
}
}
protected Postcard build(String path, String group) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
return new Postcard(path, group);
}
}
- 在 new Postcard (path, group) 中,第一个参数就是路径,第二个参数是组别信息。 比如 “/activity/LoginActivity” 来说 activity 就是 group。ARouter 框架是分组管理,按需加载。解释起来就是,在编译期框架扫描了所有的注册页面/服务/字段/拦截器等,那么很明显运行期不可能一股脑全部加载进来, 这样就太不和谐了。所以就分组来管理,ARouter 在初始化的时候只会一次性地加载所有的 root 结点,而不会加载任何一个 Group 结点,这样就会极大地降低初始化时加载结点的数量。 比如某些 Activity 分成一组,组名就叫 activity,然后在第一次需要加载组内的某个页面时再将 activity 这个组加载进来。
- 最后一个方法.navigation (), 方法最后会来到_ARouter 的 navigation 方法
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
……
if (null != callback) {//如果有回调就进行回调
callback.onLost(postcard);
} else { // No callback for this invoke, then we use the global degrade service.
DegradeService degradeService = ARouter.getInstance().navigation(DegradeService.class);
if (null != degradeService) {
degradeService.onLost(context, postcard);
}
}
return null;
}
if (null != callback) {
callback.onFound(postcard);
}
if (!postcard.isGreenChannel()) { //如果需要拦截
……
} else {//不需要拦截
return _navigation(context, postcard, requestCode, callback);
}
return null;
}
- 首先调用 LogisticsCenter.completion 完成 postcard 的补充
/**
* Completion the postcard by route metas
*
* @param postcard Incomplete postcard, should completion by this method.
*/
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) { // Maybe its does't exist, or didn't load.
Class< ? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
if (null == groupMeta) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
// Load route and cache it into memory, then delete from metas.
try {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] has already been loaded, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
completion(postcard); // Reload
}
} else {
postcard.setDestination(routeMeta.getDestination());
postcard.setType(routeMeta.getType());
postcard.setPriority(routeMeta.getPriority());
postcard.setExtra(routeMeta.getExtra());
Uri rawUri = postcard.getUri();
if (null != rawUri) { // Try to set params into bundle.
Map< String, String> resultMap = TextUtils.splitQueryParameters(rawUri);
Map< String, Integer> paramsType = routeMeta.getParamsType();
if (MapUtils.isNotEmpty(paramsType)) {
// Set value by its type, just for params which annotation by @Param
for (Map.Entry< String, Integer> params : paramsType.entrySet()) {
setValue(postcard,
params.getValue(),
params.getKey(),
resultMap.get(params.getKey()));
}
// Save params name which need auto inject.
postcard.getExtras().putStringArray(ARouter.AUTO_INJECT, paramsType.keySet().toArray(new String[]{}));
}
// Save raw uri
postcard.withString(ARouter.RAW_URI, rawUri.toString());
}
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must be implememt IProvider
Class< ? extends IProvider> providerMeta = (Class< ? extends IProvider>) routeMeta.getDestination();
IProvider instance = Warehouse.providers.get(providerMeta);
if (null == instance) { // There's no instance of this provider
IProvider provider;
try {
provider = providerMeta.getConstructor().newInstance();
provider.init(mContext);
Warehouse.providers.put(providerMeta, provider);
instance = provider;
} catch (Exception e) {
throw new HandlerException("Init provider failed! " + e.getMessage());
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
- 因为是按需加载,所以 RouteMeta routeMeta = Warehouse.routes.get (postcard.getPath ()); 一开始肯定是 null 的,所以需要到 Warehouse.groupsIndex 中找到组别的信息,这里的 postcard.getGroup () 就是 test.
Class< ? extends IRouteGroup> groupMeta = Warehouse.groupsIndex.get(postcard.getGroup()); // Load route meta.
- 然后通过反射加载这一组类别的映射关系。然后从仓库中删除这个组别信息节点,防止重复加载。
IRouteGroup iGroupInstance = groupMeta.getConstructor().newInstance();
iGroupInstance.loadInto(Warehouse.routes);
Warehouse.groupsIndex.remove(postcard.getGroup());
- 然后看看 apt 生成的文件 ARouter$$Group$$activity 的内容,包含有目标页面,类型,路径,组别,参数,优先级等信息。
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$Group$activity implements IRouteGroup {
@Override
public void loadInto(Map< String, RouteMeta> atlas) {
atlas.put("/activity/ClassifyActivity", RouteMeta.build(RouteType.ACTIVITY, ClassifyActivity.class, "/activity/classifyactivity", "activity", null, -1, -2147483648));
atlas.put("/activity/CreateNoteActivity", RouteMeta.build(RouteType.ACTIVITY, CreateNoteActivity.class, "/activity/createnoteactivity", "activity", new java.util.HashMap< String, Integer>(){{put("keyName", 8); put("mNote", 10); }}, -1, -2147483648));
atlas.put("/activity/LoginActivity", RouteMeta.build(RouteType.ACTIVITY, LoginActivity.class, "/activity/loginactivity", "activity", null, -1, -2147483648));
atlas.put("/activity/SearchNoteActivity", RouteMeta.build(RouteType.ACTIVITY, SearchNoteActivity.class, "/activity/searchnoteactivity", "activity", new java.util.HashMap< String, Integer>(){{put("keyName", 8); }}, -1, -2147483648));
atlas.put("/activity/TODOActivity", RouteMeta.build(RouteType.ACTIVITY, TODOActivity.class, "/activity/todoactivity", "activity", new java.util.HashMap< String, Integer>(){{put("keyName", 8); }}, -1, -2147483648));
}
}
- 接下来会递归调用 completion (postcard),现在 routeMeta 就不为空了,会走到 else 中,首先给 postcard 补充信息,有了这些信息 postcard 就可以愉快的工作了。我们这个栗子中 type 很明显是 activity,所以就走到 default 中 break 出来了。
- 然后最后到了真正的跳转,_navigation () 跳转,就是常见的 intent 和 startActivity、startActivityForResult
private Object _navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = null == context ? mContext : context;
switch (postcard.getType()) {
case ACTIVITY:
// Build intent
final Intent intent = new Intent(currentContext, postcard.getDestination());
intent.putExtras(postcard.getExtras());
// Set flags.
int flags = postcard.getFlags();
if (-1 != flags) {
intent.setFlags(flags);
} else if (!(currentContext instanceof Activity)) { // Non activity, need less one flag.
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Navigation in main looper.
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
if (requestCode > 0) { // Need start for result
ActivityCompat.startActivityForResult((Activity) currentContext, intent, requestCode, postcard.getOptionsBundle());
} else {
ActivityCompat.startActivity(currentContext, intent, postcard.getOptionsBundle());
}
if ((0 != postcard.getEnterAnim() || 0 != postcard.getExitAnim()) && currentContext instanceof Activity) { // Old version.
((Activity) currentContext).overridePendingTransition(postcard.getEnterAnim(), postcard.getExitAnim());
}
if (null != callback) { // Navigation over.
callback.onArrival(postcard);
}
}
});
break;
case PROVIDER:
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
Class fragmentMeta = postcard.getDestination();
try {
Object instance = fragmentMeta.getConstructor().newInstance();
if (instance instanceof Fragment) {
((Fragment) instance).setArguments(postcard.getExtras());
} else if (instance instanceof android.support.v4.app.Fragment) {
((android.support.v4.app.Fragment) instance).setArguments(postcard.getExtras());
}
return instance;
} catch (Exception ex) {
logger.error(Consts.TAG, "Fetch fragment instance error, " + TextUtils.formatStackTrace(ex.getStackTrace()));
}
case METHOD:
case SERVICE:
default:
return null;
}
return null;
Uri 跳转解析 (官方 demo)
- 官方demo的html.body
< body>
< h2>跳转测试< /h2>
< h2>自定义Scheme[通常来说都是这样的]< /h2>
< p>< a href="arouter://m.aliyun.com/test/activity1">arouter://m.aliyun.com/test/activity1< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity1?url=https%3a%2f%2fm.abc.com%3fa%3db%26c%3dd">测试URL Encode情况< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180&obj=%7b%22name%22%3a%22jack%22%2c%22id%22%3a666%7d">arouter://m.aliyun.com/test/activity1?name=alex&age=18&boy=true&high=180&obj={"name":"jack","id":"666"}< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity2">arouter://m.aliyun.com/test/activity2< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity2?key1=value1">arouter://m.aliyun.com/test/activity2?key1=value1< /a>< /p>
< p>< a href="arouter://m.aliyun.com/test/activity3?name=alex&age=18&boy=true&high=180">arouter://m.aliyun.com/test/activity3?name=alex&age=18&boy=true&high=180< /a>< /p>
< h2>App Links[防止被App屏蔽]< /h2>
< p>< a href="http://m.aliyun.com/test/activity1">http://m.aliyun.com/test/activity1< /a>< /p>
< p>< a href="http://m.aliyun.com/test/activity2">http://m.aliyun.com/test/activity2< /a>< /p>
< /body>
- ARouter 就是通过注册一个没有 UI 的界面来统一处理 scheme 是 arouter 的跳转请求,类似隐式注册
< activity android:name=".SchemeFilterActivity">
< 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>
< intent-filter android:autoVerify="true">
< action android:name="android.intent.action.VIEW"/>
< category android:name="android.intent.category.DEFAULT"/>
< category android:name="android.intent.category.BROWSABLE"/>
< data
android:host="m.aliyun.com"
android:scheme="http"/>
< data
android:host="m.aliyun.com"
android:scheme="https"/>
< /intent-filter>
< /activity>
- 在清单文件中注册了 SchemeFilterActivity 这个 activity,scheme 就是 arouter, host 就是 m.aliyun.com,其中有个 category 是 android.intent.category.BROWSABLE,这样就可以在 WebView 中跳转 SchemeFilterActivity 这个界面了。有个注意点,能跳转 SchemeFilterActivity 的必要条件就是需要匹配 intent-filter 中的 data, action, category 的标签,其中 category 匹配任意一个即可。
public class SchemeFilterActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 直接通过ARouter处理外部Uri
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation(this, new NavCallback() {
@Override
public void onArrival(Postcard postcard) {
finish();
}
});
}
}
- Uri uri = getIntent ().getData (); 获取到点击的那个值,例如 arouter://m.aliyun.com/test/activity2, 然后交给路由框架进行跳转
获取服务和 Fragment (官方 demo)
- 获取服务
- 这里说到的服务不是 Android 四大组件中的 Service,这里的服务是服务端开发的概念,就是将一部分功能和组件封装起来成为接口,以接口的形式对外提供能力,所以在这部分就可以将每个功能作为一个服务,而服务的实现就是具体的业务功能
- ARouter 发现服务主要有两种方式,ByName 和 ByType。先看看这两种方式分别是怎么使用。ByName 就是需要传递 path 路径来进行发现,ByType 就是通过服务 class 来进行查找。
case R.id.navByName:
((HelloService) ARouter.getInstance().build("/yourservicegroupname/hello").navigation()).sayHello("mike");
break;
case R.id.navByType:
ARouter.getInstance().navigation(HelloService.class).sayHello("mike");
break;
public interface HelloService extends IProvider {
void sayHello(String name);
}
@Route(path = "/yourservicegroupname/hello")
public class HelloServiceImpl implements HelloService {
Context mContext;
@Override
public void sayHello(String name) {
Toast.makeText(mContext, "Hello " + name, Toast.LENGTH_SHORT).show();
}
/**
* Do your init work in this method, it well be call when processor has been load.
*
* @param context ctx
*/
@Override
public void init(Context context) {
mContext = context;
Log.e("testService", HelloService.class.getName() + " has init.");
}
}
- 获取 Fragment
Fragment fragment = (Fragment) ARouter.getInstance().build("/test/fragment").navigation();
- ByName 和 Fragment 的获取和 Activity 的跳转很像。先看看 ByType 的获取过程。
ARouter.getInstance().navigation(HelloService.class);
- 跳到到_ARouter 的 navigation (Class< ? extends T> service) 中。
protected < T> T navigation(Class< ? extends T> service) {
try {
Postcard postcard = LogisticsCenter.buildProvider(service.getName());
// Compatible 1.0.5 compiler sdk.
if (null == postcard) { // No service, or this service in old version.
postcard = LogisticsCenter.buildProvider(service.getSimpleName());
}
LogisticsCenter.completion(postcard);
return (T) postcard.getProvider();
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
return null;
}
}
-上面代码主要就是两步,第一构造 postcard,第二 LogisticsCenter 跳转
- 构造 postcard
- 首先还是熟悉的先构造 postcard,只不过这里时直接在 navigation 中进行构造,之前 activity 或者 url 跳转都是通过 build 构造,意思都差不多。看看 LogisticsCenter.buildProvider
public static Postcard buildProvider(String serviceName) {
RouteMeta meta = Warehouse.providersIndex.get(serviceName);
if (null == meta) {
return null;
} else {
return new Postcard(meta.getPath(), meta.getGroup());
}
}
- 打个断点发现 meta 中有个 destination 属性可以拿到具体的实现类 HelloServiceImpl。

- 上面的 meta 是直接从仓库 Warehouse 中获取服务,仓库的 providersIndex 就是在_ARouter.init () 方法里的 LogisticsCenter .init () 中加载进来的
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//billy.qi modified at 2017-12-06
//load by plugin first
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, "Load router map by arouter-auto-register plugin.");
} else {
Set< String> routerMap;
// It will rebuild router map every times when debuggable.
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
logger.info(TAG, "Run with debug mode or new install, rebuild router map.");
// These class was generated by arouter-compiler.
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
if (!routerMap.isEmpty()) {
context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).edit().putStringSet(AROUTER_SP_KEY_MAP, routerMap).apply();
}
PackageUtils.updateVersion(context); // Save new version name when router map update finishes.
} else {
logger.info(TAG, "Load router map from cache.");
routerMap = new HashSet<>(context.getSharedPreferences(AROUTER_SP_CACHE_KEY, Context.MODE_PRIVATE).getStringSet(AROUTER_SP_KEY_MAP, new HashSet< String>()));
}
logger.info(TAG, "Find router map finished, map size = " + routerMap.size() + ", cost " + (System.currentTimeMillis() - startInit) + " ms.");
startInit = System.currentTimeMillis();
for (String className : routerMap) {
if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_ROOT)) {
// This one of root elements, load root.
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
// Load interceptorMeta
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
// Load providerIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
logger.info(TAG, "Load root element finished, cost " + (System.currentTimeMillis() - startInit) + " ms.");
if (Warehouse.groupsIndex.size() == 0) {
logger.error(TAG, "No mapping files were found, check your configuration please!");
}
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "LogisticsCenter has already been loaded, GroupIndex[%d], InterceptorIndex[%d], ProviderIndex[%d]", Warehouse.groupsIndex.size(), Warehouse.interceptorsIndex.size(), Warehouse.providersIndex.size()));
}
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
- 在 apt 生成的文件中找到 ARouter$$Providers$$app, 其中就有我们上面使用到的 HelloService,对应的注册类就是 HelloServiceImpl .class
public class ARouter$Providers$app implements IProviderGroup {
@Override
public void loadInto(Map< String, RouteMeta> providers) {
providers.put("com.alibaba.android.arouter.demo.testservice.HelloService", RouteMeta.build(RouteType.PROVIDER, HelloServiceImpl.class, "/yourservicegroupname/hello", "yourservicegroupname", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.facade.service.SerializationService", RouteMeta.build(RouteType.PROVIDER, JsonServiceImpl.class, "/yourservicegroupname/json", "yourservicegroupname", null, -1, -2147483648));
providers.put("com.alibaba.android.arouter.demo.testservice.SingleService", RouteMeta.build(RouteType.PROVIDER, SingleService.class, "/yourservicegroupname/single", "yourservicegroupname", null, -1, -2147483648));
}
}
@Autowired 注解与依赖注入
- 使用 @Autowired 注解接收跳转传递过来的值
(CreateNoteActivity)
@Autowired
String keyName;//分组类别名
@Autowired
Note mNote;
ARouter.getInstance().build("/activity/CreateNoteActivity").withString("keyName","Note").navigation();
ARouter.getInstance().build("/activity/CreateNoteActivity")
.withObject("mNote",mDatas.get(position))
.navigation(this,Constant.EDITE_NOTE_CODE);
- @Autowired String keyName 的变量名要和传递来的 key 名一样,或者是用 @Autowired (name = “keyName”) String suibian 才能接收到值
- 在传递对象的时候,报了这个错误
java.lang.NullPointerException: Attempt to invoke interface method 'java.lang.String com.alibaba.android.arouter.facade.service.SerializationService.object2Json(java.lang.Object)' on a null object reference
at com.alibaba.android.arouter.facade.Postcard.withObject(Postcard.java:244)
- 关于对象的传递有两种,一种是 withParcelable () 方法,不过此方法需要传递的对象实现 Parcelable 接口,达到序列化的目的;另外一种是 withObject () 方法,此方法的原理是将实体类转换成 json 字符串,通过 String 的方式进行传递,而且使用这种方式需要实现 SerializationService
@Route(path = "/ARouter/SerializationService")
public class JsonServiceImp implements SerializationService {
@Override
public < T> T json2Object(String input, Class< T> clazz) {
return new Gson().fromJson(input,clazz);
}
@Override
public String object2Json(Object instance) {
return new Gson().toJson(instance);
}
@Override
public < T> T parseObject(String input, Type clazz) {
return new Gson().fromJson(input,clazz);
}
@Override
public void init(Context context) {
}
}
- @Autowired 还可以注入对象
(CreateNoteActivity)
@Autowired
DBManager mDBManager;
@Route(path = "/utils/DBManager")
public class DBManager implements IProvider {
public DBManager(){
}
@Override
public void init(Context context) {
}
}
- 对应的类一定要实现 IProvider 接口并有空构造函数
- 最后,要 @Autowired 注解生效还要在使用注解的地方加入 ARouter.getInstance ().inject (this);
- 来到 ARouter 的代理类_ARouter 中,首先通过以来查找获取 AutowiredService 的具体实现类
static void inject(Object thiz) {
AutowiredService autowiredService = ((AutowiredService) ARouter.getInstance().build("/arouter/service/autowired").navigation());
if (null != autowiredService) {
autowiredService.autowire(thiz);
}
}
@Route(path = "/arouter/service/autowired")
public class AutowiredServiceImpl implements AutowiredService {
private LruCache< String, ISyringe> classCache;
private List< String> blackList;
@Override
public void init(Context context) {
classCache = new LruCache<>(66);
blackList = new ArrayList<>();
}
@Override
public void autowire(Object instance) {
String className = instance.getClass().getName();
try {
if (!blackList.contains(className)) {
ISyringe autowiredHelper = classCache.get(className);
if (null == autowiredHelper) { // No cache.
autowiredHelper = (ISyringe) Class.forName(instance.getClass().getName() + SUFFIX_AUTOWIRED).getConstructor().newInstance();
}
autowiredHelper.inject(instance);
classCache.put(className, autowiredHelper);
}
} catch (Exception ex) {
blackList.add(className); // This instance need not autowired.
}
}
}
- 通过反射得到辅助注入类 CreateNoteActivity$$ARouter$$Autowired 的实例后就调用 inject 方法注入实例。我们来看下这个类的代码。到这里就水落石出了,在跳转时将需要传递的参数写入 postcard 的 bundle 中,然后成功跳转到目标页面后就可以通过 getIntent 取出参数,然后分别给目标页面的每个需要注入的成员变量赋值。
public class CreateNoteActivity$ARouter$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
CreateNoteActivity substitute = (CreateNoteActivity)target;
substitute.keyName = substitute.getIntent().getStringExtra("keyName");
if (null != serializationService) {
substitute.mNote = serializationService.parseObject(substitute.getIntent().getStringExtra("mNote"), new com.alibaba.android.arouter.facade.model.TypeWrapper< Note>(){}.getType());
} else {
Log.e("ARouter::", "You want automatic inject the field 'mNote' in class 'CreateNoteActivity' , then you should implement 'SerializationService' to support object auto inject!");
}
}
}

@Interceptor 拦截器使用
- 拦截器注册
- 拦截器使用和 Activity@Route 差不多,只不过拦截器是使用另外一个注解 @Interceptor。有两个变量,priority 是拦截器的优先级,值越小优先级越高,会优先拦截。
@Interceptor(priority = 1)
public class LoginInterceptor implements IInterceptor {
Context mContext;
List< String> mNeedLoginActivityList;
@Override
public void process(Postcard postcard, InterceptorCallback callback) {
if (mNeedLoginActivityList.contains(postcard.getPath())){
if ("".equals(SPUtil.getString("uId"))||
SPUtil.getString("uId")==null||
"".equals(SPUtil.getString("userName"))||
SPUtil.getString("userName")==null){
ToastUtil.show(mContext.getString(R.string.plz_first_login));
Log.i("binbin", "process: 被拦截了");
callback.onInterrupt(null);
ARouter.getInstance().build(Constant.LOGIN_ACTIVITY).navigation();
}else{
callback.onContinue(postcard);
}
}else{
callback.onContinue(postcard);
//postcard.withString("extra", "我是在拦截器中附加的参数");
//callback.onContinue(postcard);
}
}
@Override
public void init(Context context) {
this.mContext=context;
//需要登录的活动
String[] array={Constant.SS_ACTIVITY};
mNeedLoginActivityList= Arrays.asList(array);
}
}
- 拦截器都默认实现 IInterceptor 接口,在拦截器实现类使用注解 @Interceptor 会自动注册
public interface IInterceptor extends IProvider {
/**
* The operation of this interceptor.
*
* @param postcard meta
* @param callback cb
*/
void process(Postcard postcard, InterceptorCallback callback);
}
降级策略 (官方 demo)
- 降级策略的实现方式
- 一、单独降级 – 回调的方式
ARouter.getInstance().build("/xxx/xxx").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("/xxx/xxx").navigation();
- 这种方式从跳转上看不出来,主要的处理逻辑在内部,这种降级策略主要是实现服务接口 DegradeService,就一个方法就是 onLost
public interface DegradeService extends IProvider {
/**
* Router has lost.
*
* @param postcard meta
*/
void onLost(Context context, Postcard postcard);
}