ARouter
帮助Android App进行组件化改造的框架,支持模块间路由、通信、解耦
支持功能:
- 支持直接解析标准URL进行跳转,并自动注入参数到目标页面中
- 支持多模块工程使用
- 支持添加多个拦截器,自定义拦截顺序
- 支持依赖注入,可单独作为依赖注入框架使用
- 支持InstantRun
- 支持MultiDex(Google方案)
- 映射关系按组分类、多级管理,按需初始化
- 用户指定全局降级与局部降级策略
- 页面、拦截器、服务等组件均自动注册到框架
- 多种方式配置跳转动画
- 获取Fragment
- 完全支持Kotlin以及混编
- 第三方App加固(使用arouter-register实现自动注册)
- 生成路由文档
- IDE插件便捷关联路径和目标类
应用
- 从外部URL映射到内部页面,参数传递和解析
- 跨模块页面跳转,模块解耦
- 拦截跳转过程,处理登陆、埋点
- 跨模块API调用,控制反转来组件解耦
依赖(在app、子module等需要路由跳转的模块添加):
plugins {
// module如下,如果是app(id 'com.android.application')
id 'com.android.library'
// kotlin 扩展
id 'kotlin-android'
id 'kotlin-android-extensions'
id 'kotlin-kapt'
}
// kotlin模块(和javamodule模块不一样注意)
kapt {
arguments {
arg("AROUTER_MODULE_NAME", project.getName())
}
}
dependencies{
implementation 'com.alibaba:arouter-api:1.5.0'
kapt 'com.alibaba:arouter-compiler:1.2.2'
}
初始化:在Application中进行初始化
class BaseApplication :Application(){
override fun onCreate() {
super.onCreate()
if (BuildConfig.DEBUG){
ARouter.openDebug()
ARouter.openLog()
}
ARouter.init(this)
}
}
一个包含多个module项目,名为user的module中存在一个UserHomeActivity,路由路径:/account/userHome。从其他module跳转该页面,指定path来跳转。
// 主app里的
@Route(path = "/account/userHome")
class UserHomeActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_home)
}
}
// module里的
@Route(path = "/library1/test")
class Library1Activity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_library1)
tvTest.setOnClickListener{
// 跨模块跳转
ARouter.getInstance().build("/account/userHome").navigation()
}
}
}
编译阶段生成辅助代码来实现path跳转。需要拿到Activity的Class对象才行。编译阶段,ARouter根据我们设定的路由跳转规则来自动生成映射文件,包含path和ActivityClass之间的对应关系。
UserHomeActivity编译阶段自动生成辅助java文件ARouter$$Group$$account类中就将path和ActivityClass作为键值对保存到了Map中,ARouter依靠此进行跳转。
public class ARouter$$Group$$account implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
}
}
这类自动生成的文件包名路径都是com.alibaba.android.arouter.routes,类名前缀特定规则。虽然ARouter$$Group$$account类实现了对应关系保存到Map中,loadInto方法还是需要有ARouter运行时来调用,ARouter就需要拿到ARouter$$Group$$account这个类才行,ARouter通过扫描com.alibaba.android.arouter.routes这个包名路径来获取所有辅助文件。
1、程序员自己维护特定path和特定目标类之间对应关系,ARouter只要求开发者使用包含path的@Route 注解修饰目标类
2、ARouter在编译阶段通过注解处理器自动生成path和特定的目标类之间的对应关系,path作为key,将目标类Class对象为value之一存到Map中
3、在运行阶段,通过path来发起请求,ARouter根据path从map中取值,拿到目标类
ARouter类使用单例模式,暴露外部调用的API。实现逻辑交给_ARouter来完成。
_ARouter类包私有权限,使用了单例,通过init(Application)-->LogisticsCenter.init(mContext, executor)
LogisticsCenter实现扫描特定包名路径拿到所有自动生成的辅助文件的逻辑。在进行初始化的时候,加载到当前项目一共包含所有group,以及每个group对应的路由信息表。
1、如果开启debug模式或通过本地SP缓存判断出app的版本前后发生变化,需要重新获取路由信息,否则从使用之前缓存到SP中的数据。
2、获取全局路由信息是耗时操作,所以ARouter通过将全局路由信息缓存到SP中来实现。开发阶段可能随时添加路由表,每次发布新的版本正常都会加大应用版本号,所以ARouter只开启了debug模式或者版本号发生变化的时候才会重新获取路由信息
3、获取路由信息包含了com.alibaba.android.arouter.routes包下自动生成的辅助文件的全路径,判断路径名前缀字符串,知道该类什么类型文件,通过反射构建不同类型对象,调用对象的方法将路由信息存到Warehouse的Map中。
UserHomeActivity,其path为/account/userHome,ARouter默认会将path的第一个单词account作为group,而UserHomeActivity放到名为app(module名字)的module中
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Root$$app implements IRouteRoot {
@Override
public void loadInto(Map<String, Class<? extends IRouteGroup>> routes) {
routes.put("account", ARouter$$Group$$account.class);
}
}
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/userHome", RouteMeta.build(RouteType.ACTIVITY, UserHomeActivity.class, "/account/userhome", "account", null, -1, -2147483648));
}
}
LogisticsCenter的init文件名前缀ARouter$$Root$$定位到ARouter$$Root$$app这个类,然后通过反射构建出该对象,通过调用loadInto的页面时,反射调用ARouter$$Group$$account的loadInto方法,按需加载,等到需要的时候再获取详细的路由对应信息。
ARouter.getInstance().build(RoutePath.USER_HOME).navigation()
build()通过ARouter中转调用_ARouter的build()返回Postcard对象,用于传入跳转配置参数,例如:mBundle、开启绿色通道greenChannel、跳转动画optionsCompat等
Postcard的navigation()方法会调用_ARouter完成Activity跳转。
navigation方法的重点在于LogisticsCenter.completion(postcard)。按需加载反射调用ARouteraccount 的 loadInto 方法。completion方法就获取详细路由信息。通过postcard携带的path和group信息从Warehouse取值,如果不是null信息保存到postcard中,为null则抛出NoRouteFoundException
跳转到Activity并注入参数
// 带参数跳转
ARouter.getInstance()
.build("/account/userHome")
.withLong("userId",13)
.withString("userName","小明")
.navigation()
// 参数接收
@Route(path = "/account/userHome")
class UserHomeActivity : AppCompatActivity() {
private val TAG = "UserHome-"
@Autowired(name = "userId")
@JvmField
var userId:Long = 0
@Autowired(name = "userName")
@JvmField
var userName = ""
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_home)
// 需要注册,否则无法接收到参数传递
ARouter.getInstance().inject(this)
Log.d(TAG, "onCreate: userId:$userId userName:$userName")
}
}
通过@Autowired注解修饰变量。声明name参数,传递键值对的key对应。通过ARouter.getInstance().inject(this)后,完成参数赋值。
生产辅助代码:
public class UserHomeActivity$$ARouter$$Autowired implements ISyringe {
private SerializationService serializationService;
@Override
public void inject(Object target) {
serializationService = ARouter.getInstance().navigation(SerializationService.class);
UserHomeActivity substitute = (UserHomeActivity)target;
substitute.userId = substitute.getIntent().getLongExtra("userId", substitute.userId);
substitute.userName = substitute.getIntent().getStringExtra("userName");
}
}
携带的参数放到Intent,inject方法实现了从Intent取值向变量赋值,要求变量是public的,kotlin需要同时向变量加上@JvmField注解的原因。参数注解:ARouter.getInstance().inject(this)
ARouter通过控制反转拿到AutowiredService对应实现类AutowiredServiceImpl的对象,调用autowire方法完成参数注入。
由于参数注入辅助类的类名具有固定的包名和类名,目标类类名+$$ARouter$$Autowired, 所以AutowiredServiceImpl传入的instance参数和反射过来生成的对象,最终调用inject方法完成参数注入。
控制反转
跳转Activity并自动注入参数属于依赖注入的一种,ARouter同时也支持控制反转:通过接口获取其实现类实例
存在一个ISayHelloService接口,需要拿到其实现类实例,但是不希望使用的时候和特定实现类SayHelloService绑定在一起造成耦合,使用ARouter控制反转功能,要求ISayHelloService接口继承了IProvider接口才行
注意:Route(path = "/account/sayhello")在实现的接口和调用的类path要一致,才能实现反转控制。
// 接口
interface ISayHelloService :IProvider {
fun sayHello()
}
// 具体实现
@Route(path = "/account/sayhello")
class SayHelloService :ISayHelloService{
private val TAG = "SayHello-"
override fun sayHello() {
Log.d(TAG, "sayHello: ")
}
override fun init(context: Context?) {
Log.d(TAG, "init: ")
}
}
// 具体使用
@Route(path = "/account/sayhello")
class MainActivity : AppCompatActivity() {
private val TAG = "Main-网络测试"
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
textNet.setOnClickListener {
ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
}
}
}
在使用的时候直接传递ISayHelloService的Class对象即可,ARouter会将SayHelloService以单例形式返回,无需开发者手动构建SayHelloService对象,从而达到解耦。
ARouter.getInstance().navigation(ISayHelloService::class.java).sayHello()
和实现Activity跳转一样,ARouter自动生成几个文件,包含路由表映射关系。
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Group$$account implements IRouteGroup {
@Override
public void loadInto(Map<String, RouteMeta> atlas) {
atlas.put("/account/sayhello", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
}
}
/**
* DO NOT EDIT THIS FILE!!! IT WAS GENERATED BY AROUTER. */
public class ARouter$$Providers$$app implements IProviderGroup {
@Override
public void loadInto(Map<String, RouteMeta> providers) {
providers.put("com.george.learnretrofit.test.ISayHelloService", RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648));
}
}
LogisticsCenter实现扫描特定包名路径拿到所有自动生成的辅助文件逻辑。最终Warehouse中就会在初始化的时候拿到数据
Warehouse.groupsIndex:
- account--->class com.alibaba.android.arouter.routes.ARouter$$Group$$account
Warehouse.providersIndex:
com.george.learnretrofit.test.ISayHelloService--->RouteMeta.build(RouteType.PROVIDER, SayHelloService.class, "/account/sayhello", "account", null, -1, -2147483648)
LogisticsCenter.completion(postcard)获取对象实例时候同时将实例缓存起来,以后复用。
拦截器
可以通过拦截器来判断用户是否处于登陆状态,还未登陆的话,拦截请求,打开登陆界面。
可以添加多个拦截器,每个拦截器设置不同优先级
@Interceptor(priority = 100,name="啥也不做的拦截器")
class NothingInterceptor :IInterceptor{
private val TAG = "Nothing-1-"
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
// 不拦截,任其跳转
Log.d(TAG, "process: ")
callback?.onContinue(postcard)
}
override fun init(context: Context?) {
Log.d(TAG, "init: ")
}
}
@Interceptor(priority = 200, name = "登陆拦截器")
class LoginInterceptor: IInterceptor{
private val TAG = "Nothing-2-"
override fun process(postcard: Postcard?, callback: InterceptorCallback?) {
if (postcard?.path == "/account/userHome"){
// 拦截
Log.d(TAG, "process: 拦截-到主页")
callback?.onInterrupt(null)
ARouter.getInstance().build("/account/sayhello").navigation()
}else{
Log.d(TAG, "process: 不拦截")
// 不拦截,任其跳转
callback?.onContinue(postcard)
}
}
override fun init(context: Context?) {
Log.d(TAG, "init: ")
}
}
priority越小优先级越高(越先执行)。
_ARouter的navigation方法,转交给interceptorService,判断有没有开启绿色通道模式。InterceptorServiceImpl实现类,ARouter初始化过程中通过控制反转拿到interceptorService实例。
1、第一次获取InterceptorServiceImpl实例时候,其init方法会马上被调用,交由线程池来处理。通过反射生成每个拦截器对象,并调用每个拦截器的init方法完成拦截器初始化,将每个拦截器对象都存到Warehouse.interceptors中。如果初始化完成,则唤醒等待的interceptorInitLock上的线程。
2、doInterceptions方法被调用,如果第一个步骤未执行完,则通过checkInterceptorsInitStatus()方法等待第一次步骤完成。如果10s内都未完成,则走失败流程直接返回。
3、在线程池中遍历拦截器列表,如果某个拦截器拦截请求则调用callback.onInterrupt方法通知内部,否则调用callback.onContinue()方法继续跳转逻辑。
注解处理器
依靠注解器生成辅助文件,ARouter才能完成参数自动注入功能。
APT(Annotation Processing Tool)注解处理器,用在编译期扫描和处理注解,通过注解生成Java文件。即注解作为桥梁,预先设定好的代码生成规则,自动生成Java文件。例如:ButterKnife/Dragger2/EventBus等
Java API已经提供了扫描源码并解析注解的框架,通过继承AbstractProcessor类来实现自己的注解解析逻辑。APT的原理就是在注解了某些代码元素(如字段、函数、类等)后。在编译时编辑器会检查AbstractProcess的子类,并且自动调用process()方法,然后将指定注解的所有代码元素作为参数传递给该方法,开发者根据注解元素在编译期输出Java代码。
ARouter源码中和注解处理器相关的module
- router-annotation。Java Module,包含了Autowired、Interceptor这些注解及RouteMeta等JavaBean
- arouter-compiler。Android Module,包含了AbstractProcessor的实现类用于生产代码