Arouter简介
ARouter是阿里开源的一款路由框架,为组件化提供解决方案,它支持解析标准URL进行跳转,并自动注入参数到目标页面中;支持添加多个拦截器,自定义拦截顺序。
所谓组件化就是将APP按照一定的功能和业务拆分成多个小组件,不同的组件由不同的开发小组来负责,这样就可以解决大型APP开发过程中的开发与协作的问题,将这些问题分散到小的APP中。
(1)原生路由方案:使用显示Intent和隐式Intent跳转
- 显示Intent存在直接依赖,耦合度过高;
- 隐式Intent存在规则集中式管理Path,导致协作困难;
- 原生路由跳转一旦调用startActivity,后面的流程将无法进行控制拦截;
(2)ARouter自定义路由方案:使用解析标准URL跳转
- 通过解析URL索引进行跳转,解决依赖的问题;
- 通过分布式管理页面配置,解决隐式Intent中集中式管理Path的问题;
- 通过AOP方式支持添加多个拦截器并自定义拦截顺序,解决跳转过程中无法控制的问题;
- 整个路由跳转过程透明,拥有更好的扩展性;
2. ARouter基本使用
ARouter Github地址: https://github.com/alibaba/ARouter
(1)配置app的build.gradle;
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
dependencies {
// Replace with the latest version
implementation 'com.alibaba:arouter-api:1.5.1'
annotationProcessor 'com.alibaba:arouter-compiler:1.5.1' // 注解处理器
...
}
如果使用Kotlin开发,将上述配置改为:
// 添加'kotlin-kapt'插件
apply plugin: 'kotlin-kapt'
android {
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
}
dependencies {
implementation 'com.alibaba:arouter-api:1.5.1'
kapt 'com.alibaba:arouter-compiler:1.5.1' // 注解处理器
...
}
(2)初始化SDK,释放ARouter资源?;
public WanFlutterApplication extends Application {
private boolean isDebug = true;
@Override
public void onCreate() {
if (isDebug) { // 在init方法之前调用,否则不起作用
ARouter.openLog(); // 打印日志
ARouter.openDebug(); // InstantRun mode需开启调试模式,但是线上需将其关闭
}
ARouter.init(this);
}
}
(3)使用@Router注解定义一个路由页面;
// Router path至少需要两级
// "/xx/xxx"
@Router("/app/HomeActivity")
public HomeActivity extends AppcompactActivity {
// 如果name与属性名称一致
// 可以不指定@Autowired注解的name值
@Autowired
Int id;
@Autowired(name="key2")
String userName;
@Autowired(name="test")
Test test;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// ARouter inject注入
// 注意:如果没有参数传递,这句可以不写
// 注意:需要在Application中调用ARouter.getInstance.destory()释放资源
ARouter.getInstance().inject(this);
}
}
(4)Activity/Fragment路由跳转,传参数,获取返回值;
// a. 不传递参数跳转
ARouter.getInstance().build("/app/HomeActivity").navigation();
// b. 使用URL跳转
// 我们可以在清单文件中配置<data></data>标签指定uri的值
// <intent-filter>
// <data
// android:host="m.aliyun.com"
// android:scheme="arouter"/>
// ...
// </intent-filter>
Uri uri = getIntent().getData();
ARouter.getInstance().build(uri).navigation();
// c. 传递参数跳转
// 支持基本数据类型、Bundle、数组、列表、Serializable、Parcelable
// 跳转目标页面使用@Autowired注解获取
ARouter.getInstance().build("/app/HomeActivity")
.withInt("id", 88888)
.withString("name", "jiangdg")
.withObject("test", new Test("Jack", "Rose"))
.navigation();
// d. 跳转动画
ARouter.getInstance().build("/app/HomeActivity")
.withTransition(R.anim.in, R.anim.out)
.navigation();
// e. Fragment跳转
Fragment fragment = (Fragment)ARouter.getInstance().build("/app/TestFragment").navigation()
// f. 获取返回值
int requestCode = 1;
ARouter.getInstance().build("/app/HomeActivity").navigation(context, requestCode);
(5)ARouter拦截器实现
- a. 使用@Interceptor注解定义拦截器,并实现
IInterceptor
接口实现;
// priority值表示拦截器优先级,越小优先级越大;
// name值表示拦截器的名称;
@Interceptor(priority=1, name="test")
public class UseIIterceptor implements IInterceptor {
@Override
public void init(Context context) {
// 拦截器初始化
}
@Override
public void process(Postcard postCard, InterceptorCallback callback) {
// 拦截器处理逻辑
// ...
// 如:当跳转到"/app/HomeActivity"页面时,作相关的处理
if (postCard.getPath().equal("/app/HomeActivity")) {
Log.d(TAG, "I Get it");
}
}
}
- b. 使拦截器生效,通过注册路由监听器实现;
ARouter.getInstance().build("/app/HomeActivity")
.navigation(context, new NavigationCallback() {
@Override
public void onFound(Postcard postcard) {
// 路由目标被发现时调用
}
@Override
public void onArrival(Postcard postcard) {
// 路由达到之后调用
}
@Override
public void onLost(Postcard postcard) {
// 路由被丢失时调用
}
@Override
public void onInterrupt(Postcard postcard) {
// 路由被拦截时调用
}
});
Postcard
是一个包含路线图(roadmap
)的容器,它允许让我们获取路径的组(group
)以及全路径等信息。
拦截器最终执行顺序: init->onFound->process->onLost(丢失)/onInterrupt(拦截)/onArrival(到达)
(6)自定义分组
ARouter使用分组管理路由的跳转,防止应用在运行时一次性加载所有路由信息造成资源浪费。
// 目的路由新增group值
@Router(path = "/app/HomeActivity", group="home")
public HomeActivity extends AppcompactActivity {
}
// 跳转指定group
ARouter.getInstance().build("/app/HomeActivity", "home").navigation();
(7)支持服务管理,通过依赖注入的方式实现不同组件之间解耦;
ARouter提供了Service management
功能,支持不同组件(模块)交互进一步解耦,即其他组件只要获取目标组件(模块)的Service实例,
就可以通过它调用该组件暴露出来的接口,这个功能通常被用在组件化各个组件之间的通信。
- 首先,定义一个继承于IProvider的接口Service,它用于像外界暴露组件提供服务的接口;
// 定义接口
public interface UserSerive extends IProvider {
UserInfo getUserInfo();
// 前往完善个人信息页面
void toFillUserProfileActivity(Context context);
// 前往登录页面
void toLoginActivity(Context context);
}
// 实现接口
@Route(path="/service/user", name = "UserService")
public class UserServiceImpl implements UserService {
@Override
UserInfo getUserInfo() {
return UserInfo("jiangdg", 18, "male");
}
@Override
void toFillUserProfileActivity(Context context) {
// 注:这里也可以使用ARouter进行跳转
Intent intent = new Intent(context, FillUserProfileActivity.class);
context.startActivity(intent);
}
@Override
void toLoginActivity(Context context) {
// 注:这里也可以使用ARouter进行跳转
Intent intent = new Intent(context, LoginActivity.class);
context.startActivity(intent);
}
}
// 其次,外界组件(模块)获取目标组件暴露的Service实例;
// 方式一:使用依赖注入或注解获取
@Autowired
UserService userService;
@Autowired(name = "/service/user")
UserService userSerivce;
// 方式二:使用依赖关系查找
UserService userSerivce = ARouter.getInstance().navigation(UserSerive.class);
UserService userSerivce = (UserService)ARouter.getInstance().build("/service/user").navigation();
注意:ARouter还提供了降低策略和预处理,分别是DegradeService
、PretreatmentService
。
ARouter的工作原理为在编译阶段根据注解解释器对路由注解,拦截器注解以及自动装配注解注解进行解释并生成辅助代码,待运行期与API接口一起提供给宿主APP使用
。此外,ARouter框架使用了外观模式
进行封装,隐藏了具体的实现细节,提供了一个统一的入口供外界使用。
1. ARouter初始化过程分析
ARouter初始化过程主要是获取注解处理器生成的辅助类存储的路由信息,并将其加载到内存中的过程。
ARouter初始化入口为ARouter.init()
,它最终会调用LogisticsCenter.init()
方法完成初始化工作。
具体源码如下:
// 线程池
// 核心线程大小:CPU_COUNT + 1
// 最大线程数量:CPU_COUNT + 1
// 阻塞队列大小:64
private volatile static ThreadPoolExecutor executor = DefaultPoolExecutor.getInstance();
protected static synchronized boolean init(Application application) {
mContext = application;
// 外观模式
// 执行实际的ARouter初始化逻辑,参数为上下文和线程池对象
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, "ARouter init success!");
hasInit = true;
// 主线程Handler
mHandler = new Handler(Looper.getMainLooper());
return true;
}
LogisticsCenter.init()的初始化过程,分为两步:
- 首先,获取编译器生成的所有类的classname,它们位于包"com.alibaba.android.arouter.routes"下;
- 其次,根据classname得到注解处理器生成的路由信息并缓存到内存中,即
Warehouse
对应的字段中;
具体源码如下:
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
...
Set<String> routerMap;
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.
// 1. 加载注解处理器生成的路由信息,包名为"com.alibaba.android.arouter.routes"
// 缓存到内存中WareHouse
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>()));
}
// 2. 加载注解处理器生成的路由信息,缓存到内存中WareHouse
// 如 ARouter$$Root$$模块名称
// ARouter$$Providers$$模块名称
// ARouter$$Interceptors$$模块名称
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);
}
}
}
...
} catch (Exception e) {
throw new HandlerException(TAG + "ARouter init logistics center exception! [" + e.getMessage() + "]");
}
}
- Warehouse
Warehouse
相当于ARouter框架的“数据仓库”,它将被用于缓存编译处理器生成的路由信息(RouteMeta
)。
class Warehouse {
/**
* groupsIndex缓存路列表
*
* key 路由分组;value 注解处理器自动生成的路由组,如ARouter$$Group$$service(service分组)
*/
static Map<String, Class<? extends IRouteGroup>> groupsIndex = new HashMap<>();
/**
* routes 缓存所有路由的元数据(不区分Group)
*
* key 路由路径;value 路由元数据,如RouteMeta.build(RouteType.ACTIVITY, ....)
*/
static Map<String, RouteMeta> routes = new HashMap<>();
/**
* 缓存provider(服务)列表
*
* key 服务class;value 注解处理器自动生成的服务组,如ARouter$$Providers$$camera(service分组)
*/
static Map<Class, IProvider> providers = new HashMap<>();
static Map<String, RouteMeta> providersIndex = new HashMap<>();
/**
* 缓存拦截器列表
*
* key 拦截器优先级;value 注解处理器自动生成拦截器,如ARouter$$Interceptor$$..
*/
static Map<Integer, Class<? extends IInterceptor>> interceptorsIndex = new UniqueKeyTreeMap<>("More than one interceptors use same priority [%s]");
static List<IInterceptor> interceptors = new ArrayList<>();
static void clear() {
routes.clear();
groupsIndex.clear();
providers.clear();
providersIndex.clear();
interceptors.clear();
interceptorsIndex.clear();
}
}
- RouteMeta
RouteMeta
表示路由元数据,它存储了路由的类型、路径、分组、优先级等信息。
public class RouteMeta {
private RouteType type; // 路由类型,如ACTIVITY、SERVICE、FRAGMENT、PROVIDER等
private Element rawType; // 路由的原始类型,如ElementKind.CLASS表示是一个类
private Class<?> destination; // 被@Route注解的class对象
private String path; // 路由路径
private String group; // 路由分组名称
private int priority = -1; // 优先级,数字越小优先级越高
private int extra; // 额外数据
private Map<String, Integer> paramsType; // 参数类型
private String name;
private Map<String, Autowired> injectConfig; // 自定装配参数集合
...
}
2. ARouter路由跳转过程分析
ARouter路由跳转过程主要是借助了Postcard
类来封装执行路由跳转所需的数据,并调用其navigation()方法执行跳转。
Postcard的创建过程如下,具体源码为:
// ARouter#build()方法
public Postcard build(String path) {
return _ARouter.getInstance().build(path);
}
// _ARouter#build()方法
protected Postcard build(String path) {
if (TextUtils.isEmpty(path)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
// 1. 获取PathReplaceService,如果用户有重写PathReplaceService
// 否则为null。注:PathReplaceService即替换路由路径
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
// 2. 创建路由图Postcard
// 获取默认group,即如果没有指定group,则将/xx/xxx,xx则为默认group
return build(path, extractGroup(path), true);
}
}
// _ARouter#build()方法
protected Postcard build(String path, String group, Boolean afterReplace) {
if (TextUtils.isEmpty(path) || TextUtils.isEmpty(group)) {
throw new HandlerException(Consts.TAG + "Parameter is invalid!");
} else {
if (!afterReplace) {
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
if (null != pService) {
path = pService.forString(path);
}
}
// 实例化一个Postcard对象
// 将路由路由和分组信息保存
return new Postcard(path, group);
}
}
接下来,我们分析下通过Postcard是如何完成最后的跳转逻辑的。通过分析Postcard#navigation()可知,它又继续调用了
_ARouter的navigation()方法,在该方法中执行具体的操作。源码如下所示:
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
// 1. 获取pretreatmentService(预处理)服务对象
// 如果用户实现了的话,返回false说明用户取消了跳转
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
return null;
}
// 2. 设置context到Postcard
postcard.setContext(null == context ? mContext : context);
// 3. 完善对Postcard数据的填充
// 如果没有发现对应的路由信息,报“There's no route matched!”异常
// 并回调NavigationCallback的onLost方法(如果有的话)或者降级处理
try {
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
// 回调callback的onLost或者DegradeService的onLost
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;
}
// 4. 路由信息匹配成功,触发onFound回调
if (null != callback) {
callback.onFound(postcard);
}
// 5. 根据greenChannel标志,执行拦截器(拦截器代码需异步执行,防止ANR)
// 默认为false,但如果routeType是Provider或Fragment,会将其置为true(Logistics.completion)
// 因此拦截器只针对Activity,进入InterceptorServiceImpl.doInterceptions
if (!postcard.isGreenChannel()) {
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
// 继续执行跳转逻辑
// 即根据路由的类型,执行最终的跳转,intent...
@Override
public void onContinue(Postcard postcard) {
_navigation(postcard, requestCode, callback);
}
// 跳转被拦截
@Override
public void onInterrupt(Throwable exception) {
// 回调NavigationCallback的onInterrupt方法
if (null != callback) {
callback.onInterrupt(postcard);
}
}
});
} else {
return _navigation(postcard, requestCode, callback);
}
return null;
}
_ARouter#navigation主要做了以下几件事情:
(1)判断用户是否实现了PretreatmentService,如果返回false,说明用户取消了接下来的跳转;
(2)将WareHouse缓存的路由信息填充到PostCard中,通过调用LogisticsCenter.completion()
实现;
(3)判断路由类型,如果是Activity,进入拦截器处理逻辑(线程池),并决定拦截还是继续执行;
(4)否则,继续执行真正的跳转或其他操作。
下面我们重点分析下(2)、(3)阶段:
- LogisticsCenter.completion()
该方法的作用就是读取WareHouse缓存的路由信息填充到PostCard中。
//
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + "No postcard!");
}
// 3-1 通过路由path获取路由的元数据RouteMeta
// (1)如果获取元数据失败,则
// 如果没有找到(路由分组,如ARouter$$Group$$service,不存在),抛出NoRouteFoundException
// 否则,尝试动态加载Group,然后再尝试completion
RouteMeta routeMeta = Warehouse.routes.get(postcard.getPath());
if (null == routeMeta) {
// Maybe its does't exist, or didn't load.
if (!Warehouse.groupsIndex.containsKey(postcard.getGroup())) {
throw new NoRouteFoundException(TAG + "There is no route match the path [" + postcard.getPath() + "], in group [" + postcard.getGroup() + "]");
} else {
try {
if (ARouter.debuggable()) {
logger.debug(TAG, String.format(Locale.getDefault(), "The group [%s] starts loading, trigger by [%s]", postcard.getGroup(), postcard.getPath()));
}
// 动态加载group
// if (Warehouse.groupsIndex.containsKey(groupName)){
// // If this group is included, but it has not been loaded
// // load this group first, because dynamic route has high priority.
// Warehouse.groupsIndex.get(groupName).getConstructor().newInstance().loadInto(Warehouse.routes);
// Warehouse.groupsIndex.remove(groupName);
// }
addRouteGroupDynamic(postcard.getGroup(), null);
} catch (Exception e) {
throw new HandlerException(TAG + "Fatal exception when loading group meta. [" + e.getMessage() + "]");
}
completion(postcard); // Reload
}
}
// (2)获取路由元数据routeMeta成功,将routeMeta的数据依次填充的Postcard相关字段
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)) {
// 获取所有传递参数,保存到Bundle
// 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());
}
// b. 根据路由类型决定是否屏蔽拦截器
// 如果是Provider,通过反射实例化一个对象保存到PostCard中,然后将greenChannel标志置为true
// 如果是Fragment,将greenChannel标志置为true
// ps:_ARouter.navigation方法最终根据greenChannel决定是否执行拦截器
switch (routeMeta.getType()) {
case PROVIDER:
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) {
logger.error(TAG, "Init provider failed!", e);
throw new HandlerException("Init provider failed!");
}
}
postcard.setProvider(instance);
postcard.greenChannel(); // Provider should skip all of interceptors
break;
case FRAGMENT:
postcard.greenChannel(); // Fragment needn't interceptors
default:
break;
}
}
}
- ARouter#_navigation()
该方法将根据路由类型,执行实际上的跳转。
(1)如果是Activity,调用startActivity跳转,参数从PostCard中获取;
(2)如果是IProvider(服务),直接返回IProvicer对象;
(3)如果是Broadcast、ContentProvider、Fragment,通过反射实例化一个对象并返回;
具体源码如下:
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
// 1 获取上下文
final Context currentContext = postcard.getContext();
// 2 根据路由类型,执行实际上的跳转
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 (0 != flags) {
intent.setFlags(flags);
}
// Non activity, need FLAG_ACTIVITY_NEW_TASK
if (!(currentContext instanceof Activity)) {
intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
}
// Set Actions
String action = postcard.getAction();
if (!TextUtils.isEmpty(action)) {
intent.setAction(action);
}
// 主线程执行跳转
// 因为_navigation有可能处于线程池中(拦截器是在线程池中执行)
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
break;
case PROVIDER:
// 返回IProvider对象
return postcard.getProvider();
case BOARDCAST:
case CONTENT_PROVIDER:
case FRAGMENT:
// 返回Broadcast、Content Provider、Fragment对象
// 如果是Fragment, 还需要setArguments,即设置传递参数
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;
}