探索Android开源框架 - ARouter使用及源码解析

  • 最近对项目进行组件化重构,要进行组件化就少不了组件间通信的路由,于是就再次研究了一下阿里的ARouter,关于组件化可以查看我之前写的组件化架构系列文章,这里就不过多提及了。

使用详解

1. 添加依赖

  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'
  1. 在其他需要进行组件化通信的各模块的build.gradle的dependencies中加入

implementation project(path: ':baseRouter')
kapt  'com.alibaba:arouter-compiler:1.5.2'
  1. 设置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)
    }
}
  1. 通过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来初始化

  1. 添加依赖

implementation "androidx.startup:startup-runtime:1.0.0"
  1. 创建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()
    }
}
  1. 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跳转

  1. 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()
    }
}
  1. 在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>
  1. 跳转

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. 发现服务

  1. 使用依赖注入的方式发现服务(推荐)

    • 通过注解标注字段, 即可使用,无需主动获取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")
  1. 使用依赖查找的方式发现服务,主动去发现服务并使用,下面两种方式分别是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

  1. 先定义一个Fragment,并用@Route注解添加path

@Route(path = PATH_FRAGMENT_DIALOG_TEST)
class TestDialogFragment : AppCompatDialogFragment() {
    ...
}
  1. 获取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) 方法,但是两者的调用时机不同;

  1. 拦截器因为其特殊性,会被任何一次路由所触发,拦截器会在ARouter初始化的时候异步初始化,如果第一次路由的时候拦截器还没有初始化结束,路由会等待,直到初始化完成;

  2. 服务没有该限制,某一服务可能在App整个生命周期中都不会用到,所以服务只有被调用的时候才会触发初始化操作;

  • 到此已经跟着官方文档走了一边,各种使用方法基本都列全了,下面我们来掀开源码的面纱,看看他到底是如何实现的吧

源码解析

路由原理

  • 主要是通过编译的时候通过APT扫描注解,并进行相应处理,通过javapoet库生成Java代码;

  • 关于APT和javapoet我在之前的探索Android开源框架 - 6. ButterKnife使用及源码解析中有介绍过,APT技术被广泛的运用在Java框架中,ButterKnife,EventBus,Dagger2以及ARouter等都运用到APT;

  • 主要步骤如下:

  1. 调用ARouter.init方法,在LogisticsCenter中生成三个文件,Group(IRouteGroup),Providers(IProviderGroup),Root(IRouteRoot),使用Warehouse将文件保存到三个不同的HashMap中, Warehouse就相当于路由表, 保存着全部模块的跳转关系;

  2. 通过ARouter.navigation封装postcard对象;

  3. 通过ARouter索引传递到LogisticsCenter(路由中转站),询问是否存在跳转对象;

  4. 判断是否绿色通行和是否能通过拦截服务;

  5. 全部通过就会调用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解析

转自:探索Android开源框架 - ARouter使用及源码解析

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值