最近对项目进行组件化重构,要进行组件化就少不了组件间通信的路由,于是就再次研究了一下阿里的ARouter,关于组件化可以查看我之前写的组件化架构系列文章,这里就不过多提及了。
使用详解
1. 添加依赖
在baseRouter module 的 build.gradle的dependencies中加入中添加依赖 (最好把路由相关放到单独的module中,并进行二次封装,方便日后的管理和替换)
// arouter-api要与arouter-compiler匹配使用,均使用最新版可以保证兼容
// 这里用api而不是implementation,方便其他组件依赖baseRouter后使用ARouter的功能
api \'com.alibaba:arouter-api:1.5.2\'
// 我用的是kotlin所以下面用了kapt,如果是java替换成annotationProcessor即可
kapt \'com.alibaba:arouter-compiler:1.5.2\'
在其他需要进行组件化通信的各模块的build.gradle的dependencies中加入
implementation project(path: \':baseRouter\')
kapt \'com.alibaba:arouter-compiler:1.5.2\'
设置AROUTER_MODULE_NAME
//1. kotlin
plugins {
id \'com.android.library\'
id \'kotlin-android\'
id \'kotlin-kapt\'
}
kapt {
arguments {
arg(\"AROUTER_MODULE_NAME\", project.getName())
}
}
android {
...
}
//2. java
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName()]
}
}
}
}
2. 初始化ARouter
有如下几种初始化方式:
1. 直接初始化
在app module中直接初始化(不推荐);
class AppModuleApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.isDebug){
ARouter.openLog();
ARouter.openDebug();
//需要在init之前配置才有效
}
ARouter.init(this)
}
}
2. 提供BaseRouterApplication被继承
在baseRouter module中创建一个Application的子类初始化ARouter,然后在app module的Application中继承(不推荐);
class BaseRouterApplication : Application() {
override fun onCreate() {
super.onCreate()
if (BuildConfig.isDebug){
ARouter.openLog();
ARouter.openDebug();
//需要在init之前配置才有效
}
ARouter.init(this)
}
}
class AppModuleApplication : BaseRouterApplication() {
override fun onCreate() {
super.onCreate()
}
}
3. 提供静态方法initRouter
在二次封装的Router工具类中创建一个初始化Router的init方法再在app module的Application中调用
fun initRouter(application: Application,isDebug: Boolean){
if (isDebug){
ARouter.openLog();
ARouter.openDebug();
//需要在init之前配置才有效
}
ARouter.init(application);
}
class AppModuleApplication : Application() {
override fun onCreate() {
super.onCreate()
initRouter(this,BuildConfig.DEBUG)
}
}
通过ContentProvider初始化
在baseRouter module中自定义BaseRouterProvider并初始化ARouter,然后在baseRouter module的AndroidManifest.xml中配置
class BaseRouterProvider : ContentProvider() {
override fun onCreate(): Boolean {
//ContentProvider中也可以取得Context
val application = context?.applicationContext as Application?
application?.let {
if (BuildConfig.DEBUG) {
ARouter.openLog()
ARouter.openDebug()
}
ARouter.init(application)
}
return true
}
//其他方法用不到,直接return null 或 return -1 即可
...
}
<application>
<provider
android:name=\".BaseRouterProvider\"
android:authorities=\"${applicationId}.BaseRouterProvider\"
android:exported=\"false\" />
...
</application>
5. 通过Jetpack组件AppStartup来初始化
添加依赖
implementation \"androidx.startup:startup-runtime:1.0.0\"
创建Initializer的实现类,并初始化ARouter
class BaseRouterInitializer : Initializer<Unit> {
//在create方法中执行要初始化的代码
override fun create(context: Context) {
val application = context.applicationContext as Application?
application?.let {
if (BuildConfig.DEBUG) {
ARouter.openLog()
ARouter.openDebug()
}
ARouter.init(application)
}
}
//dependencies方法用于配置当前LjyToastInitializer是否还依赖于其他Initializer
//有的话在此配置,没有就return emptyList()即可
override fun dependencies(): List<Class<out Initializer<*>>> {
return emptyList()
}
}
AndroidMainfest.xml中配置
<provider
android:name=\"androidx.startup.InitializationProvider\"
android:authorities=\"${applicationId}.androidx-startup\"
android:exported=\"false\"
tools:node=\"merge\">
<meta-data
android:name=\".BaseRouterInitializer\"
android:value=\"androidx.startup\" />
</provider>
3. 简单使用
1. 定义路由路径管理类
在baseRouter module中创建ARouterPath.kt用于存放各模块的路由路径,这里的路径需要注意的是至少需要有两级(/xx/xx)
/**
* app主模块入口
*/
const val PATH_ACTIVITY_MAIN = \"/app/MainActivity\"
/**
* 登录模块入口
*/
const val PATH_ACTIVITY_LOGIN = \"/login/LoginActivity\"
/**
* 网页加载模块入口
*/
const val PATH_ACTIVITY_WEB = \"/web/WebActivity\"
2. 添加@Route注解
创建上面对应的module及Activity,添加注解@Route,并设置对应的路由路径,例如LoginActivity设置如下
@Route(path = PATH_ACTIVITY_LOGIN)
class LoginActivity : AppCompatActivity() {
}
3. 发起路由跳转
仅跳转
ARouter.getInstance().build(PATH_ACTIVITY_LOGIN).navigation()
携带参数,有很多withXXX方法基本都是见明知意
ARouter.getInstance().build(PATH_ACTIVITY_LOGIN)
.withLong(\"key1\", 666L)
.withString(\"key2\", \"888\")
.navigation()
4. 接收参数
使用@Autowired注解标注字段即可支持自动注入,并在onCreate中调用inject方法(原理类似ButterKnife)
kotlin中需要额外使用lateinit修饰符或者添加@JvmField注解,否则会编译失败;
但是需要注意的是lateinit修饰的字段是延迟初始化,跳转时一定要传递,否则会报错UninitializedPropertyAccessException: lateinit property name has not been initialized,所以还是用@JvmField注解吧;
@Route(path = PATH_ACTIVITY_LOGIN)
class LoginActivity : AppCompatActivity() {
@Autowired
@JvmField
var key1: Int? = -1
@Autowired(name = \"key2\", required = true, desc = \"userName field\")
lateinit var name: String
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
ARouter.getInstance().inject(this)
log(\"key1=$key1\")
log(\"name=$name\")
}
}
5. 添加混淆规则(如果使用了Proguard)
-keep public class com.alibaba.android.arouter.routes.**{*;}
-keep public class com.alibaba.android.arouter.facade.**{*;}
-keep class * implements com.alibaba.android.arouter.facade.template.ISyringe{*;}
# 如果使用了 byType 的方式获取 Service,需添加下面规则,保护接口
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# 如果使用了 单类注入,即不定义接口实现 IProvider,需添加下面规则,保护实现
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
6. 使用 Gradle 插件实现路由表的自动加载 (可选)
实现路由表自动加载,这个功能虽然是选项配置,但是对于arouter启动优化(sdk初始化速度)有很大的作用,在编译期通过gradle插装把需要依赖arouter注解的类自动扫描到arouter的map管理器里面,
//根gradle中
buildscript {
...
dependencies {
...
classpath \"com.alibaba:arouter-register:1.0.2\"
}
}
//app module 的gradle中
plugins {
...
id \'com.alibaba.arouter\'
}
4. 进阶用法
进行二次封装
将进阶用法之前我们先对ARouter进行一下封装,调用起来更简洁一定,也方便做替换,新建RouterUtil.kt,代码如下
/**
* 标准使用
* @path 路由路径
* @params 所传参数
*/
fun routerNavigation(path: String, params: Map<String, Any?>? = null) {
buildParams(ARouter.getInstance().build(path), params).navigation()
}
/**
* 解析params放到Postcard中
*/
private fun buildParams(postcard: Postcard, params: Map<String, Any?>?): Postcard {
return postcard.apply {
if (params != null && params.isNotEmpty())
for ((k, v) in params)
when (v) {
is String -> withString(k, v)
is Int -> withInt(k, v)
is Boolean -> withBoolean(k, v)
is Long -> withLong(k, v)
is Float -> withFloat(k, v)
is Double -> withDouble(k, v)
is ByteArray -> withByteArray(k, v)
is Bundle -> with(v)
is List<*>, is Map<*, *>, is Set<*> -> withObject(k, v)
is Parcelable -> withParcelable(k, v)
is Serializable -> withSerializable(k, v)
else -> withObject(k, v)
}
}
}
/**
* 进阶使用方法
* @path 路由路径
* @params 所传参数
* @activity 当前activity,配合requestCode或callback使用
* @requestCode 当需要startActivityForResult时
* @callback 使用NavigationCallback处理跳转结果(局部降级策略)
* @group 一般不传,默认就是 path 的第一段相同
* @uri 通过uri跳转时
* @flag 当需要指定flag时Intent.FLAG_ACTIVITY_CLEAR_TOP
* @enterAnim 页面进入动画
* @exitAnim 页面退出动画
* @compat ActivityOptionsCompat动画
* @greenChannel 是否使用绿色通道(跳过所有的拦截器)
*/
fun routerNavigation(
path: String,
params: Map<String, Any?>? = null,
activity: Activity? = null,
requestCode: Int = -1,
callback: NavigationCallback? = null,
group: String? = null,
uri: Uri? = null,
flag: Int? = null,
enterAnim: Int? = null,
exitAnim: Int? = null,
compat: ActivityOptionsCompat? = null,
greenChannel: Boolean = false,
) {
if (path.isNullOrEmpty()) {
buildParams(ARouter.getInstance().build(uri), params)
} else {
val postcard =
if (group.isNullOrEmpty()) ARouter.getInstance().build(path)
else ARouter.getInstance().build(path, group)
buildParams(
postcard.apply {
if (flag != null)
postcard.withFlags(flag)
if (enterAnim != null && exitAnim != null)//转场动画(常规方式)
postcard.withTransition(enterAnim, exitAnim)
if (compat != null)// 转场动画(API16+)
postcard.withOptionsCompat(compat)
if (greenChannel)//使用绿色通道(跳过所有的拦截器)
postcard.greenChannel()
}, params
)
}.navigation(activity, requestCode, callback)
}
1. 更多类型的参数
传递参数
data class TestObj(val name:String?,val age:Int?,val email:String?)
val testObj = TestObj(\"ljy\", 18, \"xxx@qq.com\")
val testObj2 = TestObj(\"yang\", 20, \"www@qq.com\")
val list = listOf(testObj, testObj2)
val testSerializable = TestSerializable(\"serial\", 12)
val testParcelable = TestParcelable(\"parcel\", 11)
routerNavigation(
PATH_ACTIVITY_LOGIN,
mapOf(
\"key1\" to 123,
\"key2\" to \"aaa\",
\"key3\" to \"bbb\",
\"testObj\" to testObj,
\"list\" to list,
\"testSerializable\" to testSerializable,
\"testParcelable\" to testParcelable
)
)
接收参数
@Autowired
lateinit var testObj: TestObj
@Autowired
lateinit var list: List<TestObj?>
@Autowired
lateinit var testSerializable: TestSerializable
@Autowired
lateinit var testParcelable: TestParcelable
2. 解析自定义对象
上面TestObj是自定义对象,要想传递自定义对象还需要实现一个SerializationService
通过Gson或Fastjson解析
@Route(path = \"/serialization/gson\")//这里和之前的路由定义规则一样,自己控制好group不要重复就行
class GsonSerializationServiceImpl : SerializationService {
override fun init(context: Context?) {
log(\"GsonSerializationServiceImpl.init\")
}
override fun <T : Any?> json2Object(input: String?, clazz: Class<T>?): T? {
log(\"GsonSerializationServiceImpl.json2Object\")
return GsonUtils.fromJson(input, clazz)
}
override fun object2Json(instance: Any?): String {
log(\"GsonSerializationServiceImpl.object2Json\")
return GsonUtils.toJson(instance)
}
override fun <T : Any?> parseObject(input: String?, clazz: Type?): T? {
log(\"GsonSerializationServiceImpl.parseObject\")
return GsonUtils.fromJson(input, clazz)
}
}
3. 通过URL跳转
baseRouter module中新建一个Activity用于监听Scheme事件,之后直接把url传递给ARouter即可
class SchemeFilterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val uri = intent.data
ARouter.getInstance().build(uri).navigation()
}
}
在baseRouter module的AndroidManifest.xml中注册,分别定义Scheme和App Links协议
<activity android:name=\".ui.SchemeFilterActivity\">
<!-- Scheme -->
<intent-filter>
<data
android:host=\"test.ljy.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>
<!-- App Links -->
<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=\"test.ljy.com\"
android:scheme=\"http\" />
<data
android:host=\"test.ljy.com\"
android:scheme=\"https\" />
</intent-filter>
</activity>
跳转
routerNavigation( uri = Uri.parse(\"arouter://test.ljy.com$PATH_ACTIVITY_LOGIN\"))
4. 使用拦截器
要想定义拦截器需要实现IInterceptor,添加@Interceptor,如登录校验,运行时权限等
关于注解的参数priority: 拦截器可以定义优先级,如果有多个拦截器,会依次执行拦截器
以运行时权限校验为例,代码如下
@Interceptor(priority = 0)
class PermissionInterceptor : IInterceptor {
private var context: Context? = null
private var postcard: Postcard? = null
private var callback: InterceptorCallback? = null
override fun init(context: Context) {
this.context = context
log(\"PermissionInterceptor.init\")
}
override fun process(postcard: Postcard, callback: InterceptorCallback) {
log(\"PermissionInterceptor.process\")
this.postcard = postcard
this.callback = callback
if (postcard.path == PATH_ACTIVITY_WEB) {
log(\"PermissionInterceptor.process: path匹配,开始校验运行时权限\")
requestMyPermissions(
arrayOf(
Manifest.permission.WRITE_EXTERNAL_STORAGE,
Manifest.permission.READ_EXTERNAL_STORAGE,
)
) {
if (it) {
log(\"允许了权限申请\")
callback.onContinue(postcard)
} else {
log(\"拒绝了权限申请\")
}
}
}else{
log(\"PermissionInterceptor.process: path不匹配,无需拦截\")
callback.onContinue(postcard)
}
}
}
5. 降级策略
即在找不到的路由表的时候,要如何处理
全局降级策略
实现DegradeService,重写onLost方法
@Route(path = \"/degrade/Test\")
class TestDegradeServiceImpl : DegradeService {
override fun onLost(context: Context?, postcard: Postcard?) {
log(\"TestDegradeServiceImpl.onLost:没有找到该路由地址:${postcard?.path}\")
// do something:可以提供一个错误页进行跳转
routerNavigation(PATH_ACTIVITY_ERROR)
}
override fun init(context: Context?) {
log(\"TestDegradeServiceImpl.init\")
}
}
局部降级策略
就是通过NavigationCallback监听单次路由跳转
routerNavigation(\"/aaa/bbb\", callback = object : NavigationCallback {
override fun onFound(postcard: Postcard?) {
log(\"NavigationCallback.onFound-找到了:$postcard\")
}
override fun onLost(postcard: Postcard?) {
log(\"NavigationCallback.onLost-找不到了:$postcard\")
routerNavigation(PATH_ACTIVITY_ERROR)
}
override fun onArrival(postcard: Postcard?) {
log(\"NavigationCallback.onArrival-跳转完了:$postcard\")
}
override fun onInterrupt(postcard: Postcard?) {
log(\"NavigationCallback.onInterrupt-被拦截了:$postcard\")
}
})
6. 通过依赖注入解耦:服务管理
1. 提供服务
定义服务接口接口,继承IProvider
interface HelloService:IProvider {
fun sayHello(msg:String)
}
创建服务接口的实现类, 如下提供了两个实现类
@Route(path = PATH_SERVICE_HELLO, name = \"hello service\")
class HelloServiceImpl:HelloService {
override fun sayHello(msg: String) {
log(\"Hello $msg\")
}
override fun init(context: Context?) {
log(\"HelloServiceImpl.init\")
}
}
@Route(path = PATH_SERVICE_NIHAO, name = \"nihao service\")
class NiHaoServiceImpl : HelloService {
override fun sayHello(msg: String) {
log(\"你好 $msg\")
}
override fun init(context: Context?) {
log(\"NiHaoServiceImpl.init\")
}
}
2. 发现服务
使用依赖注入的方式发现服务(推荐)
通过注解标注字段, 即可使用,无需主动获取Autowired注解中标注name之后,将会使用byName的方式注入对应的字段,
不设置name属性,会默认使用byType的方式发现服务(当同一接口有多个实现的时候,必须使用byName的方式发现服务)
//注解字段
//byType
@Autowired
lateinit var helloService1: HelloService
//byName
@Autowired(name = PATH_SERVICE_HELLO)
lateinit var helloService2: HelloService
//直接使用
helloService1.sayHello(\"msg1\")
helloService2.sayHello(\"msg2\")
使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是byName和byType
//声明变量时未使用注解
var helloService3: HelloService? = null
var helloService4: HelloService? = null
//byType
helloService3 = ARouter.getInstance().navigation(HelloService::class.java)
helloService3?.sayHello(\"msg3\")
//byName
helloService4 = ARouter.getInstance().build(PATH_SERVICE_HELLO).navigation() as HelloService?
helloService4?.sayHello(\"msg4\")
7. 获取Fragment
先定义一个Fragment,并用@Route注解添加path
@Route(path = PATH_FRAGMENT_DIALOG_TEST)
class TestDialogFragment : AppCompatDialogFragment() {
...
}
获取Fragment实例
val fragment: AppCompatDialogFragment =ARouter.getInstance()
.build(PATH_FRAGMENT_DIALOG_TEST).navigation() as AppCompatDialogFragment
(fragment as TestDialogFragment).setConfirmCallback { log(\"MainActivity confirmCallback \") }
fragment.show(supportFragmentManager, \"TestDialogFragment\")
8. 预处理服务
在路由navigation之前进行干扰路由,需要实现PretreatmentService接口,
重写onPretreatment方法,跳转前预处理,如果需要自行处理跳转,该方法返回 false 即可;
拦截器功能和预处理服务功能是有点像的,只不过预处理服务是早于拦截器;
@Route(path = \"/pretreatment/test\")
class TestPretreatmentServiceImpl : PretreatmentService {
override fun init(context: Context?) {
log(\"TestPretreatmentServiceImpl.init\")
}
override fun onPretreatment(context: Context?, postcard: Postcard?): Boolean {
log(\"TestPretreatmentServiceImpl.onPretreatment\")
if (postcard?.path == PATH_ACTIVITY_WEB) {
if (!ApplicationUtil.instance.isLogin) {
Toast.makeText(
ApplicationUtil.instance.getAppContext(),
\"还没有登录哦\",
Toast.LENGTH_SHORT
).show()
routerNavigation(PATH_ACTIVITY_LOGIN)
return false
}
}
return true
}
}
9. 重定义URL跳转
对要跳转的path或uri进行处理,如重定向等,需要实现PathReplaceService接口
@Route(path = \"/pathReplace/test\")
class TestPathReplaceServiceImpl : PathReplaceService {
override fun init(context: Context?) {
log(\"TestPathReplaceServiceImpl.init\")
}
override fun forString(path: String): String {
log(\"TestPathReplaceServiceImpl.replacePath\")
// 按照一定的规则处理之后返回处理后的结果
return if (path == PATH_ACTIVITY_MAIN) PATH_ACTIVITY_LOGIN else path
}
override fun forUri(uri: Uri?): Uri? {
log(\"TestPathReplaceServiceImpl.replaceUri\")
return uri // 按照一定的规则处理之后返回处理后的结果
}
}
10. 动态注册路由信息
适用于部分插件化架构的App以及需要动态注册路由信息的场景,可以通过 ARouter 提供的接口实现动态注册 路由信息,目标页面和服务可以不标注 @Route 注解,注意:同一批次仅允许相同 group 的路由信息注册
//1. 一个没有被@Route注解的Activity
class RegisterActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_register)
}
}
//2. addRouteGroup 动态注册路由
ARouter.getInstance().addRouteGroup {
it[PATH_ACTIVITY_REGISTER] = RouteMeta.build(
RouteType.ACTIVITY,// 路由信息
Class.forName(\"com.jinyang.login.RegisterActivity\"),// 目标的 Class
PATH_ACTIVITY_REGISTER, // Path
PATH_ACTIVITY_REGISTER.split(\"/\")[1],// Group, 尽量保持和 path 的第一段相同
0, // 优先级,暂未使用
0// Extra,用于给页面打标
)
}
//3. 进行路由跳转
routerNavigation(PATH_ACTIVITY_REGISTER)
11. 更多的初始化配置
private fun initRouter() {
if (BuildConfig.DEBUG) {
// 开启日志
ARouter.openLog()
// 使用InstantRun的时候,需要打开该开关,上线之后关闭,否则有安全风险
ARouter.openDebug()
// 打印日志的时候打印线程堆栈
ARouter.printStackTrace();
//使用自己的日志工具打印日志
ARouter.setLogger(myLogger)
// 使用自己提供的线程池
ARouter.setExecutor(DispatcherExecutor.getCPUExecutor())
}
//需要在init之前配置才有效
ARouter.init(this)
}
val myLogger = object : ILogger {
private var mIsMonitorMode = false
private var mIsShowStackTrace: Boolean = false
override fun showLog(isShowLog: Boolean) {
LjyLogUtil.setIsLog(isShowLog)
}
override fun showStackTrace(isShowStackTrace: Boolean) {
mIsShowStackTrace = isShowStackTrace
}
override fun debug(tag: String?, message: String?) {
LjyLogUtil.d(\"$tag --> $message\")
}
override fun info(tag: String?, message: String?) {
LjyLogUtil.i(\"$tag --> $message\")
}
override fun warning(tag: String?, message: String?) {
LjyLogUtil.w(\"$tag --> $message\")
}
override fun error(tag: String?, message: String?) {
LjyLogUtil.e(\"$tag --> $message\")
}
override fun error(tag: String?, message: String?, e: Throwable?) {
LjyLogUtil.e(\"$tag --> message=$message,e=${e?.message}\")
}
override fun monitor(message: String?) {
if (LjyLogUtil.isLog() && isMonitorMode) {
val stackTraceElement = Thread.currentThread().stackTrace[3]
LjyLogUtil.d(\"$defaultTag::monitor\", message + DefaultLogger.getExtInfo(stackTraceElement))
}
}
override fun isMonitorMode(): Boolean {
return mIsMonitorMode
}
override fun getDefaultTag(): String {
return \"LJY_LOG\"
}
}
12. 生成路由文档
// 更新 build.gradle, 添加参数 AROUTER_GENERATE_DOC = enable
// 生成的文档路径 : build/generated/source/apt/(debug or release)/com/alibaba/android/arouter/docs/arouter-map-of-${moduleName}.json
android {
defaultConfig {
...
javaCompileOptions {
annotationProcessorOptions {
arguments = [AROUTER_MODULE_NAME: project.getName(), AROUTER_GENERATE_DOC: \"enable\"]
}
}
}
}
13.拦截器和服务的异同
拦截器和服务所需要实现的接口不同,但是结构类似,都存在 init(Context context) 方法,但是两者的调用时机不同;
拦截器因为其特殊性,会被任何一次路由所触发,拦截器会在ARouter初始化的时候异步初始化,如果第一次路由的时候拦截器还没有初始化结束,路由会等待,直到初始化完成;
服务没有该限制,某一服务可能在App整个生命周期中都不会用到,所以服务只有被调用的时候才会触发初始化操作;
到此已经跟着官方文档走了一边,各种使用方法基本都列全了,下面我们来掀开源码的面纱,看看他到底是如何实现的吧
源码解析
路由原理
主要是通过编译的时候通过APT扫描注解,并进行相应处理,通过javapoet库生成Java代码;
关于APT和javapoet我在之前的探索Android开源框架 - 6. ButterKnife使用及源码解析中有介绍过,APT技术被广泛的运用在Java框架中,ButterKnife,EventBus,Dagger2以及ARouter等都运用到APT;
主要步骤如下:
调用ARouter.init方法,在LogisticsCenter中生成三个文件,Group(IRouteGroup),Providers(IProviderGroup),Root(IRouteRoot),使用Warehouse将文件保存到三个不同的HashMap中, Warehouse就相当于路由表, 保存着全部模块的跳转关系;
通过ARouter.navigation封装postcard对象;
通过ARouter索引传递到LogisticsCenter(路由中转站),询问是否存在跳转对象;
判断是否绿色通行和是否能通过拦截服务;
全部通过就会调用ActivityCompat.startActivity方法来跳转到目的Activity;
所以,ARouter实际还是使用原生的Framework机制startActivity,只是通过apt注解的形式制造出跳转规则,并人为的拦截跳转和设置跳转条件;
ARouter.init
ARouter.init代码如下
public static void init(Application application) {
if (!hasInit) {//防止重复初始化
logger = _ARouter.logger;
_ARouter.logger.info(Consts.TAG, \"ARouter init start.\");
//真正的初始化方法:_ARouter.init
hasInit = _ARouter.init(application);
if (hasInit) {
//初始化成功后调用
_ARouter.afterInit();
}
_ARouter.logger.info(Consts.TAG, \"ARouter init over.\");
}
}
_ARouter.init代码如下,主要是调用LogisticsCenter.init和创建mHandler
protected static synchronized boolean init(Application application) {
mContext = application;
LogisticsCenter.init(mContext, executor);
logger.info(Consts.TAG, \"ARouter init success!\");
hasInit = true;
mHandler = new Handler(Looper.getMainLooper());
return true;
}
LogisticsCenter.init
LogisticsCenter.init代码如下,主要是负责加载路由表
public synchronized static void init(Context context, ThreadPoolExecutor tpe) throws HandlerException {
mContext = context;
executor = tpe;
try {
long startInit = System.currentTimeMillis();
//判断是不是通过arouter-register自动加载路由表
loadRouterMap();
if (registerByPlugin) {
logger.info(TAG, \"Load router map by arouter-auto-register plugin.\");
} else {
//不是通过插件加载路由表
//声明路由表routerMap
Set<String> routerMap;
// 如果调用了openDebug,或有新的版本
if (ARouter.debuggable() || PackageUtils.isNewVersion(context)) {
//遍历dex中所有的className,过滤出前缀为com.alibaba.android.arouter.routes,放到set集合里面
routerMap = ClassUtils.getFileNameByPackageName(mContext, ROUTE_ROOT_PAKCAGE);
//将routerMap放到sp中,方便下次直接取
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 {
//直接在sp中取缓存
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)) {
//将前缀ARouter$$Root的class放到Warehouse.groupsIndex
((IRouteRoot) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.groupsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_INTERCEPTORS)) {
//将前缀ARouter$$Interceptors的class放到Warehouse.interceptorsIndex
((IInterceptorGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.interceptorsIndex);
} else if (className.startsWith(ROUTE_ROOT_PAKCAGE + DOT + SDK_NAME + SEPARATOR + SUFFIX_PROVIDERS)) {
//将前缀ARouter$$Providers的class放到Warehouse.providersIndex
((IProviderGroup) (Class.forName(className).getConstructor().newInstance())).loadInto(Warehouse.providersIndex);
}
}
}
} catch (Exception e) {
throw new HandlerException(TAG + \"ARouter init logistics center exception! [\" + e.getMessage() + \"]\");
}
}
_ARouter.afterInit
static void afterInit() {
// 这里是获取拦截器服务实例
interceptorService = (InterceptorService) ARouter.getInstance().build(\"/arouter/service/interceptor\").navigation();
}
ARouter.build
再来看看跳转时调用的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 {
//调用navigation生成PathReplaceService实例,还记得我们之前实现的TestPathReplaceServiceImpl么,
PathReplaceService pService = ARouter.getInstance().navigation(PathReplaceService.class);
//如果用户实现了PathReplaceService,则调用其对跳转的path或uri进行处理
if (null != pService) {
//通过PathReplaceService处理path
path = pService.forString(path);
}
//extractGroup就是截取出group
return build(path, extractGroup(path), true);
}
}
下面看看三个参数的build,可以看到和上面的build一样也是先看看有没有实现PathReplaceService,是否需要对path或uri进行处理,然后new了一个Postcard
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);
}
}
return new Postcard(path, group);
}
}
Postcard保存了路由跳转需要的所有信息,并且有一系列withXXX方法供我们设置
public final class Postcard extends RouteMeta {
// Base
private Uri uri;
private Object tag; // A tag prepare for some thing wrong. inner params, DO NOT USE!
private Bundle mBundle; // Data to transform
private int flags = 0; // Flags of route
private int timeout = 300; // Navigation timeout, TimeUnit.Second
private IProvider provider; // It will be set value, if this postcard was provider.
private boolean greenChannel;
private SerializationService serializationService;
private Context context; // May application or activity, check instance type before use it.
private String action;
// Animation
private Bundle optionsCompat; // The transition animation of activity
private int enterAnim = -1;
private int exitAnim = -1;
...
}
ARouter.navigation
build完之后就要调用navigation来执行跳转了,有一系列的重载方法,最终都是调用到_ARouter.getInstance().navigation
public Object navigation() {
return navigation(null);
}
public Object navigation(Context context) {
return navigation(context, null);
}
public Object navigation(Context context, NavigationCallback callback) {
return ARouter.getInstance().navigation(context, this, -1, callback);
}
public Object navigation(Context mContext, Postcard postcard, int requestCode, NavigationCallback callback) {
return _ARouter.getInstance().navigation(mContext, postcard, requestCode, callback);
}
_ARouter.getInstance().navigation代码如下
protected Object navigation(final Context context, final Postcard postcard, final int requestCode, final NavigationCallback callback) {
//获取PretreatmentService实例
PretreatmentService pretreatmentService = ARouter.getInstance().navigation(PretreatmentService.class);
//如果PretreatmentService实例存在,即用户实现了预处理服务,并且onPretreatment返回了false,则拦截本次跳转
if (null != pretreatmentService && !pretreatmentService.onPretreatment(context, postcard)) {
// Pretreatment failed, navigation canceled.
return null;
}
// Set context to postcard.
postcard.setContext(null == context ? mContext : context);
try {
//最主要是调用了这一行
LogisticsCenter.completion(postcard);
} catch (NoRouteFoundException ex) {
logger.warning(Consts.TAG, ex.getMessage());
//openDebug则toast一些提示
if (debuggable()) {
// Show friendly tips for user.
runInMainThread(new Runnable() {
@Override
public void run() {
Toast.makeText(mContext, \"There\'s no route matched!\\n\" +
\" Path = [\" + postcard.getPath() + \"]\\n\" +
\" Group = [\" + postcard.getGroup() + \"]\", Toast.LENGTH_LONG).show();
}
});
}
//如果NavigationCallback存在则调用其onLost,就是我们之前讲的局部降级
if (null != callback) {
callback.onLost(postcard);
} else {
// NavigationCallback不存在则看看有没有实现DegradeService,就是之前讲的全局降级
// 所以局部降级存在,则全局降级就在本次路由就不会生效了
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()) { // It must be run in async thread, maybe interceptor cost too mush time made ANR.
//如果没有开启绿色通道,则调用拦截器服务,
//拦截器服务是在上面的_ARouter.afterInit中初始化的
interceptorService.doInterceptions(postcard, new InterceptorCallback() {
/**
* Continue process
*
* @param postcard route meta
*/
@Override
public void onContinue(Postcard postcard) {
_navigation(postcard, requestCode, callback);
}
/**
* Interrupt process, pipeline will be destory when this method called.
*
* @param exception Reson of interrupt.
*/
@Override
public void onInterrupt(Throwable exception) {
if (null != callback) {
callback.onInterrupt(postcard);
}
logger.info(Consts.TAG, \"Navigation failed, termination by interceptor : \" + exception.getMessage());
}
});
} else {
return _navigation(postcard, requestCode, callback);
}
return null;
}
LogisticsCenter.completion
public synchronized static void completion(Postcard postcard) {
if (null == postcard) {
throw new NoRouteFoundException(TAG + \"No postcard!\");
}
//根据path获取RouteMeta信息
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())) {
//如果group没找到
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()));
}
//在groupIndex中找对应的groupMeta
addRouteGroupDynamic(postcard.getGroup(), null);
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());
}
//如果type是Fragment或者IProvider则开启greenChannel,也就是不用拦截器
switch (routeMeta.getType()) {
case PROVIDER: // if the route is provider, should find its instance
// Its provider, so it must implement 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) {
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;
}
}
}
真正的跳转方法
之前的_navigation最后一行调用了_navigation(postcard, requestCode, callback),代码如下,可以看到其是真正处理跳转的方法
private Object _navigation(final Postcard postcard, final int requestCode, final NavigationCallback callback) {
final Context currentContext = postcard.getContext();
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 in main looper.
runInMainThread(new Runnable() {
@Override
public void run() {
startActivity(requestCode, currentContext, intent, postcard, callback);
}
});
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;
}
InterceptorServiceImpl
上面的拦截器服务interceptorService实现类为InterceptorServiceImpl,在其init方法中,通过线程池遍历了 Warehouse.interceptorsIndex : Map<Integer, Class<? extends IInterceptor>>创建拦截器实例放到集合List<IInterceptor>中
public void init(final Context context) {
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
for (Map.Entry<Integer, Class<? extends IInterceptor>> entry : Warehouse.interceptorsIndex.entrySet()) {
Class<? extends IInterceptor> interceptorClass = entry.getValue();
try {
IInterceptor iInterceptor = interceptorClass.getConstructor().newInstance();
iInterceptor.init(context);
Warehouse.interceptors.add(iInterceptor);
} catch (Exception ex) {
throw new HandlerException(TAG + \"ARouter init interceptor error! name = [\" + interceptorClass.getName() + \"], reason = [\" + ex.getMessage() + \"]\");
}
}
interceptorHasInit = true;
logger.info(TAG, \"ARouter interceptors init over.\");
synchronized (interceptorInitLock) {
interceptorInitLock.notifyAll();
}
}
}
});
}
在doInterceptions中通过CountDownLatch(可以理解为一个递减锁存器的计数,如果计数到达零,则释放所有等待的线程)调用来_execute,
@Override
public void doInterceptions(final Postcard postcard, final InterceptorCallback callback) {
if (MapUtils.isNotEmpty(Warehouse.interceptorsIndex)) {
checkInterceptorsInitStatus();
//如果拦截器list没有初始化
if (!interceptorHasInit) {
callback.onInterrupt(new HandlerException(\"Interceptors initialization takes too much time.\"));
return;
}
LogisticsCenter.executor.execute(new Runnable() {
@Override
public void run() {
CancelableCountDownLatch interceptorCounter = new CancelableCountDownLatch(Warehouse.interceptors.size());
try {
_execute(0, interceptorCounter, postcard);
//拦截器如果超时会回调callback.onInterrupt
interceptorCounter.await(postcard.getTimeout(), TimeUnit.SECONDS);
if (interceptorCounter.getCount() > 0) { // Cancel the navigation this time, if it hasn\'t return anythings.
callback.onInterrupt(new HandlerException(\"The interceptor processing timed out.\"));
} else if (null != postcard.getTag()) { // Maybe some exception in the tag.
callback.onInterrupt((Throwable) postcard.getTag());
} else {
callback.onContinue(postcard);
}
} catch (Exception e) {
callback.onInterrupt(e);
}
}
});
} else {
callback.onContinue(postcard);
}
}
_execute中遍历了拦截器集合
private static void _execute(final int index, final CancelableCountDownLatch counter, final Postcard postcard) {
if (index < Warehouse.interceptors.size()) {
IInterceptor iInterceptor = Warehouse.interceptors.get(index);
iInterceptor.process(postcard, new InterceptorCallback() {
@Override
public void onContinue(Postcard postcard) {
// Last interceptor excute over with no exception.
counter.countDown();
_execute(index + 1, counter, postcard); // When counter is down, it will be execute continue ,but index bigger than interceptors size, then U know.
}
@Override
public void onInterrupt(Throwable exception) {
// Last interceptor execute over with fatal exception.
postcard.setTag(null == exception ? new HandlerException(\"No message.\") : exception); // save the exception message for backup.
counter.cancel();
// Be attention, maybe the thread in callback has been changed,
// then the catch block(L207) will be invalid.
// The worst is the thread changed to main thread, then the app will be crash, if you throw this exception!
// if (!Looper.getMainLooper().equals(Looper.myLooper())) { // You shouldn\'t throw the exception if the thread is main thread.
// throw new HandlerException(exception.getMessage());
// }
}
});
}
}
APT编译时注解
再来看看我们之前用的几个注解是如何生成类文件的,
ARouter总共有三个自定义注解:Route,Autowired,Interceptor,对应有三个注解处理器RouteProcessor,AutowiredProcessor,InterceptorProcessor
以其中RouteProcessor为例,代码如下,其实APT的使用我在之前的探索Android开源框架 - 6. ButterKnife使用及源码解析中有过详细介绍了,这里就不过多说明了,有兴趣的话可以看看下面代码的实现
@AutoService(Processor.class)
@SupportedAnnotationTypes({ANNOTATION_TYPE_AUTOWIRED})
public class AutowiredProcessor extends BaseProcessor {
private Map<TypeElement, List<Element>> parentAndChild = new HashMap<>(); // Contain field need autowired and his super class.
private static final ClassName ARouterClass = ClassName.get(\"com.alibaba.android.arouter.launcher\", \"ARouter\");
private static final ClassName AndroidLog = ClassName.get(\"android.util\", \"Log\");
@Override
public synchronized void init(ProcessingEnvironment processingEnvironment) {
super.init(processingEnvironment);
logger.info(\">>> AutowiredProcessor init. <<<\");
}
@Override
public boolean process(Set<? extends TypeElement> set, RoundEnvironment roundEnvironment) {
if (CollectionUtils.isNotEmpty(set)) {
try {
logger.info(\">>> Found autowired field, start... <<<\");
categories(roundEnvironment.getElementsAnnotatedWith(Autowired.class));
generateHelper();
} catch (Exception e) {
logger.error(e);
}
return true;
}
return false;
}
private void generateHelper() throws IOException, IllegalAccessException {
TypeElement type_ISyringe = elementUtils.getTypeElement(ISYRINGE);
TypeElement type_JsonService = elementUtils.getTypeElement(JSON_SERVICE);
TypeMirror iProvider = elementUtils.getTypeElement(Consts.IPROVIDER).asType();
TypeMirror activityTm = elementUtils.getTypeElement(Consts.ACTIVITY).asType();
TypeMirror fragmentTm = elementUtils.getTypeElement(Consts.FRAGMENT).asType();
TypeMirror fragmentTmV4 = elementUtils.getTypeElement(Consts.FRAGMENT_V4).asType();
// Build input param name.
ParameterSpec objectParamSpec = ParameterSpec.builder(TypeName.OBJECT, \"target\").build();
if (MapUtils.isNotEmpty(parentAndChild)) {
for (Map.Entry<TypeElement, List<Element>> entry : parentAndChild.entrySet()) {
// Build method : \'inject\'
MethodSpec.Builder injectMethodBuilder = MethodSpec.methodBuilder(METHOD_INJECT)
.addAnnotation(Override.class)
.addModifiers(PUBLIC)
.addParameter(objectParamSpec);
TypeElement parent = entry.getKey();
List<Element> childs = entry.getValue();
String qualifiedName = parent.getQualifiedName().toString();
String packageName = qualifiedName.substring(0, qualifiedName.lastIndexOf(\".\"));
String fileName = parent.getSimpleName() + NAME_OF_AUTOWIRED;
logger.info(\">>> Start process \" + childs.size() + \" field in \" + parent.getSimpleName() + \" ... <<<\");
TypeSpec.Builder helper = TypeSpec.classBuilder(fileName)
.addJavadoc(WARNING_TIPS)
.addSuperinterface(ClassName.get(type_ISyringe))
.addModifiers(PUBLIC);
FieldSpec jsonServiceField = FieldSpec.builder(TypeName.get(type_JsonService.asType()), \"serializationService\", Modifier.PRIVATE).build();
helper.addField(jsonServiceField);
injectMethodBuilder.addStatement(\"serializationService = $T.getInstance().navigation($T.class)\", ARouterClass, ClassName.get(type_JsonService));
injectMethodBuilder.addStatement(\"$T substitute = ($T)target\", ClassName.get(parent), ClassName.get(parent));
// Generate method body, start inject.
for (Element element : childs) {
Autowired fieldConfig = element.getAnnotation(Autowired.class);
String fieldName = element.getSimpleName().toString();
if (types.isSubtype(element.asType(), iProvider)) { // It\'s provider
if (\"\".equals(fieldConfig.name())) { // User has not set service path, then use byType.
// Getter
injectMethodBuilder.addStatement(
\"substitute.\" + fieldName + \" = $T.getInstance().navigation($T.class)\",
ARouterClass,
ClassName.get(element.asType())
);
} else { // use byName
// Getter
injectMethodBuilder.addStatement(
\"substitute.\" + fieldName + \" = ($T)$T.getInstance().build($S).navigation()\",
ClassName.get(element.asType()),
ARouterClass,
fieldConfig.name()
);
}
// Validater
if (fieldConfig.required()) {
injectMethodBuilder.beginControlFlow(\"if (substitute.\" + fieldName + \" == null)\");
injectMethodBuilder.addStatement(
\"throw new RuntimeException(\\\"The field \'\" + fieldName + \"\' is null, in class \'\\\" + $T.class.getName() + \\\"!\\\")\", ClassName.get(parent));
injectMethodBuilder.endControlFlow();
}
} else { // It\'s normal intent value
String originalValue = \"substitute.\" + fieldName;
String statement = \"substitute.\" + fieldName + \" = \" + buildCastCode(element) + \"substitute.\";
boolean isActivity = false;
if (types.isSubtype(parent.asType(), activityTm)) { // Activity, then use getIntent()
isActivity = true;
statement += \"getIntent().\";
} else if (types.isSubtype(parent.asType(), fragmentTm) || types.isSubtype(parent.asType(), fragmentTmV4)) { // Fragment, then use getArguments()
statement += \"getArguments().\";
} else {
throw new IllegalAccessException(\"The field [\" + fieldName + \"] need autowired from intent, its parent must be activity or fragment!\");
}
statement = buildStatement(originalValue, statement, typeUtils.typeExchange(element), isActivity);
if (statement.startsWith(\"serializationService.\")) { // Not mortals
injectMethodBuilder.beginControlFlow(\"if (null != serializationService)\");
injectMethodBuilder.addStatement(
\"substitute.\" + fieldName + \" = \" + statement,
(StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name()),
ClassName.get(element.asType())
);
injectMethodBuilder.nextControlFlow(\"else\");
injectMethodBuilder.addStatement(
\"$T.e(\\\"\" + Consts.TAG + \"\\\", \\\"You want automatic inject the field \'\" + fieldName + \"\' in class \'$T\' , then you should implement \'SerializationService\' to support object auto inject!\\\")\", AndroidLog, ClassName.get(parent));
injectMethodBuilder.endControlFlow();
} else {
injectMethodBuilder.addStatement(statement, StringUtils.isEmpty(fieldConfig.name()) ? fieldName : fieldConfig.name());
}
// Validator
if (fieldConfig.required() && !element.asType().getKind().isPrimitive()) { // Primitive wont be check.
injectMethodBuilder.beginControlFlow(\"if (null == substitute.\" + fieldName + \")\");
injectMethodBuilder.addStatement(
\"$T.e(\\\"\" + Consts.TAG + \"\\\", \\\"The field \'\" + fieldName + \"\' is null, in class \'\\\" + $T.class.getName() + \\\"!\\\")\", AndroidLog, ClassName.get(parent));
injectMethodBuilder.endControlFlow();
}
}
}
helper.addMethod(injectMethodBuilder.build());
// Generate autowire helper
JavaFile.builder(packageName, helper.build()).build().writeTo(mFiler);
logger.info(\">>> \" + parent.getSimpleName() + \" has been processed, \" + fileName + \" has been generated. <<<\");
}
logger.info(\">>> Autowired processor stop. <<<\");
}
}
private String buildCastCode(Element element) {
if (typeUtils.typeExchange(element) == TypeKind.SERIALIZABLE.ordinal()) {
return CodeBlock.builder().add(\"($T) \", ClassName.get(element.asType())).build().toString();
}
return \"\";
}
private String buildStatement(String originalValue, String statement, int type, boolean isActivity) {
switch (TypeKind.values()[type]) {
case BOOLEAN:
statement += (isActivity ? (\"getBooleanExtra($S, \" + originalValue + \")\") : (\"getBoolean($S)\"));
break;
case BYTE:
statement += (isActivity ? (\"getByteExtra($S, \" + originalValue + \")\") : (\"getByte($S)\"));
break;
case SHORT:
statement += (isActivity ? (\"getShortExtra($S, \" + originalValue + \")\") : (\"getShort($S)\"));
break;
case INT:
statement += (isActivity ? (\"getIntExtra($S, \" + originalValue + \")\") : (\"getInt($S)\"));
break;
case LONG:
statement += (isActivity ? (\"getLongExtra($S, \" + originalValue + \")\") : (\"getLong($S)\"));
break;
case CHAR:
statement += (isActivity ? (\"getCharExtra($S, \" + originalValue + \")\") : (\"getChar($S)\"));
break;
case FLOAT:
statement += (isActivity ? (\"getFloatExtra($S, \" + originalValue + \")\") : (\"getFloat($S)\"));
break;
case DOUBLE:
statement += (isActivity ? (\"getDoubleExtra($S, \" + originalValue + \")\") : (\"getDouble($S)\"));
break;
case STRING:
statement += (isActivity ? (\"getExtras() == null ? \" + originalValue + \" : substitute.getIntent().getExtras().getString($S, \" + originalValue + \")\") : (\"getString($S)\"));
break;
case SERIALIZABLE:
statement += (isActivity ? (\"getSerializableExtra($S)\") : (\"getSerializable($S)\"));
break;
case PARCELABLE:
statement += (isActivity ? (\"getParcelableExtra($S)\") : (\"getParcelable($S)\"));
break;
case OBJECT:
statement = \"serializationService.parseObject(substitute.\" + (isActivity ? \"getIntent().\" : \"getArguments().\") + (isActivity ? \"getStringExtra($S)\" : \"getString($S)\") + \", new \" + TYPE_WRAPPER + \"<$T>(){}.getType())\";
break;
}
return statement;
}
/**
* Categories field, find his papa.
*
* @param elements Field need autowired
*/
private void categories(Set<? extends Element> elements) throws IllegalAccessException {
if (CollectionUtils.isNotEmpty(elements)) {
for (Element element : elements) {
TypeElement enclosingElement = (TypeElement) element.getEnclosingElement();
if (element.getModifiers().contains(Modifier.PRIVATE)) {
throw new IllegalAccessException(\"The inject fields CAN NOT BE \'private\'!!! please check field [\"
+ element.getSimpleName() + \"] in class [\" + enclosingElement.getQualifiedName() + \"]\");
}
if (parentAndChild.containsKey(enclosingElement)) { // Has categries
parentAndChild.get(enclosingElement).add(element);
} else {
List<Element> childs = new ArrayList<>();
childs.add(element);
parentAndChild.put(enclosingElement, childs);
}
}
logger.info(\"categories finished.\");
}
}
}
参考
ARouter源码
阿里 ARouter 全面解析,总有你没了解的
ARouter解析