配置
在模块级的 build.gradle.kts 文件中进行配置:
kotlin
plugins {
id("kotlin-kapt")
}
android {
buildFeatures {
buildConfig = true
}
}
dependencies {
implementation("com.alibaba:arouter-api:1.5.2")
kapt("com.alibaba:arouter-compiler:1.5.2")
}
kapt {
arguments {
// 每个模块必须配置模块名,用于编译期生成 Group 映射文件
arg("AROUTER_MODULE_NAME", project.name)
}
}
如果出现了依赖性冲突,可以在 gradle.properties 文件中添加这两行:
kotlin
android.useAndroidX=true
android.enableJetifier=true // 让第三方库迁移到 AndroidX
初始化
配置完成后,在 Application 中初始化 ARouter。
kotlin
class BaseApplication : Application() {
override fun onCreate() {
super.onCreate()
initARouter()
}
private fun initARouter() {
// 必须在 init 之前配置
if (BuildConfig.DEBUG) {
ARouter.openLog() // 打印日志
ARouter.openDebug() // 开启调试模式
}
ARouter.init(this)
}
}
开启调试模式是为了让每次代码修改都能立即生效。因为 ARouter 在应用版本号不变时,会使用本地缓存,这就可能导致了路由路径修改后,运行时出现"找不到路径"的问题。
因此,我们强制它每次启动时都重新加载路由表,不使用缓存,这样每次拿到的都是最新构建的路由表。
路由与注入
页面定义与跳转
定义页面 (Destination) 的规范为:/组名/页面名。
ARouter 会根据组名进行内存懒加载。
创建一个 UserProfileActivity,并定义它的路由。
kotlin
@Route(path = "/user/profile") // 注意路径不能少于两级
class UserProfileActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_profile)
}
}
接着在 MainActivity 中实现最简单的跳转:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.go_btn).setOnClickListener {
ARouter.getInstance().build("/user/profile").navigation(this)
}
}
}
运行结果:

参数传递与自动注入
需要传递参数的话,只需调用 withXxx 方法即可。
例如:
kotlin
// MainActivity 的 onCreate 方法中
findViewById<Button>(R.id.go_btn).setOnClickListener {
ARouter.getInstance().build("/user/profile")
.withString("name", "Steven")
.withLong("id", 10086L)
.navigation(this)
}
接收方代码:
kotlin
@Route(path = "/user/profile")
class UserProfileActivity : AppCompatActivity() {
@JvmField
@Autowired(name = "id")
var userId: Long = 0L
@JvmField
@Autowired(name = "name")
var userName: String? = null
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_profile)
// 必须进行注入,否则所有字段都是默认值
ARouter.getInstance().inject(this)
Toast
.makeText(
this,
"id: $userId, name: $userName",
Toast.LENGTH_SHORT
)
.show()
}
}
运行结果:

跳转并获取返回结果
如果要进行带结果的跳转,只需在跳转时,传入 requestCode 参数即可。
kotlin
// MainActivity
companion object {
private const val REQUEST_CODE_PROFILE = 100
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.go_btn).setOnClickListener {
ARouter.getInstance().build("/user/profile")
.withString("name", "Steven")
.withLong("id", 10086L)
.navigation(this, REQUEST_CODE_PROFILE)
}
}
在目标页面使用原生的 setResult 方法返回结果:
kotlin
// UserProfileActivity
lifecycleScope.launch {
delay(2000)
val intent = Intent().apply {
putExtra("updated_name", "Steven_666")
}
setResult(RESULT_OK, intent)
finish()
}
在最初的页面重写 onActivityResult 方法来接收返回的数据:
kotlin
override fun onActivityResult(
requestCode: Int,
resultCode: Int,
data: Intent?
) {
super.onActivityResult(requestCode, resultCode, data)
if (requestCode == REQUEST_CODE_PROFILE && resultCode == RESULT_OK) {
val updatedName = data?.getStringExtra("updated_name")
Toast.makeText(this, "用户名已更新为:$updatedName", Toast.LENGTH_SHORT).show()
}
}
拦截器
我们可以在跳转前进行拦截、修改和重定向。
比如在进到用户个人中心前进行拦截,我们要检查该用户的信息完整性,如果缺少,就拦截该跳转,引导到完善信息页面;如果完整,就放行。
kotlin
@Interceptor(priority = 10, name = "用户资料完整性检查")
class UserProfileInterceptor : IInterceptor {
private var mContext: Context? = null
/**
* 注意:该方法运行在子线程
*/
override fun process(postcard: Postcard, callback: InterceptorCallback) {
if (postcard.path == "/user/profile") {
Log.i("ARouter", "正在检查进入个人中心的权限...")
//检查本地是否有昵称
val hasNickname = fakeCheckUserNicknameFromDb()
if (hasNickname) {
// 通过,继续跳转
callback.onContinue(postcard)
} else {
// 失败,进行拦截
callback.onInterrupt(RuntimeException("用户信息不完整"))
//切换到主线程来显示 Toast 或跳转
MainLooper.runOnUiThread {
Toast.makeText(mContext, "请先完善用户信息", Toast.LENGTH_SHORT).show()
// 重定向到完善信息页
ARouter.getInstance().build("/user/complete_info").navigation()
}
// 流程结束
callback.onInterrupt(null)
}
} else {
// 如果不是我们要拦截的路径,直接放行
callback.onContinue(postcard)
}
}
/**
* 模拟从数据库查询用户昵称
*/
private fun fakeCheckUserNicknameFromDb(): Boolean {
val context = mContext ?: return false
try {
Log.d("ARouter", "正在查询数据库...")
Thread.sleep(300)
} catch (e: InterruptedException) {
e.printStackTrace()
}
val sp = context.getSharedPreferences("user_data", Context.MODE_PRIVATE)
val nickname = sp.getString("nickname", "")
// 如果昵称不为空,则视为信息完整
return !nickname.isNullOrEmpty()
}
/**
* 初始化方法
* 在 ARouter 初始化时被调用,运行在主线程
*/
override fun init(context: Context) {
// 这里可以做一些静态资源的加载,或者注册广播等(非耗时操作)
this.mContext = context.applicationContext
Log.i("ARouter", "UserProfileInterceptor 已加载")
}
}
// 切换主线程的简单封装
object MainLooper {
private val handler = Handler(Looper.getMainLooper())
fun runOnUiThread(action: () -> Unit) {
if (Looper.myLooper() == Looper.getMainLooper()) {
action()
} else {
handler.post(action)
}
}
}
实战结构参考
在大型项目中,我们不会硬编码路由字符串,也不允许让跳转逻辑分散。
我们会创建路由常量:
kotlin
object RoutePath {
private const val GROUP = "/user"
const val USER_PROFILE = "$GROUP/profile"
const val COMPLETE_USER_INFO = "$GROUP/complete_info"
}
然后封装一个导航中心 NavigationHelper:
kotlin
object NavigationHelper {
/**
* 通用跳转
*/
fun startActivity(path: String, bundle: Bundle? = null) {
ARouter.getInstance().build(path)
.with(bundle)
.navigation()
}
/**
* 带结果的跳转
*/
fun startForResult(activity: Activity, path: String, requestCode: Int) {
ARouter.getInstance().build(path)
.navigation(activity, requestCode)
}
/**
* 导航用户详情页
*/
fun toUserProfile(userId: Long, userName: String?) {
ARouter.getInstance().build(RoutePath.USER_PROFILE)
.withLong(UserProfileActivity.USER_ID, userId)
.withString(UserProfileActivity.USER_NAME, userName ?: "")
.navigation()
}
}
现在我们无需知道参数 Key 的值,甚至无需知道用户详情页的路由路径。
现在的 MainActivity:
kotlin
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
findViewById<Button>(R.id.go_btn).setOnClickListener {
NavigationHelper.toUserProfile(10086L, "Steven")
}
}
}
现在的 UserProfileActivity:
kotlin
@Route(path = RoutePath.USER_PROFILE)
class UserProfileActivity : AppCompatActivity() {
@JvmField
@Autowired(name = USER_ID)
var userId: Long = 0L
@JvmField
@Autowired(name = USER_NAME)
var userName: String? = null
companion object {
const val USER_ID = "id"
const val USER_NAME = "name"
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user_profile)
ARouter.getInstance().inject(this)
Toast
.makeText(
this,
"id: $userId, name: $userName",
Toast.LENGTH_SHORT
)
.show()
}
}
Fragment 与对象传递
获取 Fragment 实例
kotlin
@Route(path = "/home/fragment")
class HomeFragment : Fragment() {
}
加载 Fragment,也可以通过 ARouter 获取其实例。
kotlin
// MainActivity
val fragment = ARouter.getInstance().build("/home/fragment").navigation() as? Fragment
if (fragment != null) {
// 添加到 FragmentManager
supportFragmentManager.beginTransaction()
.add(R.id.xxx, fragment)
.commit()
}
JSON 服务具体实现
现在,我们使用 .withObject 传递自定义对象时,会报错:java.lang.NullPointerException
因为没有进行序列化,接收端拿到的数据是 null。我们可以配置序列化服务来解决。
添加依赖:
implementation("com.google.code.gson:gson:2.13.2")// Gson
kotlin
@Route(path = "/service/json") // Path 可以自定义
class JsonServiceImpl : SerializationService {
private var gson: Gson? = null
override fun init(context: Context?) {
gson = Gson()
}
@Deprecated("Deprecated in Java")
override fun <T : Any?> json2Object(
input: String?,
clazz: Class<T?>?
): T? {
return input?.let { gson?.fromJson(it, clazz) }
}
override fun object2Json(instance: Any?): String {
return gson?.toJson(instance) ?: ""
}
override fun <T : Any?> parseObject(
input: String?,
clazz: Type?
): T? {
return input?.let { gson?.fromJson(it, clazz) }
}
}
之后,ARouter 会自动调用 JsonServiceImpl 来处理所有的 withObject 请求。