ARouter 入门指南:从基本跳转到对象传递

配置

在模块级的 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 请求。

相关推荐
dalancon27 分钟前
VSYNC 信号流程分析 (Android 14)
android
dalancon37 分钟前
VSYNC 信号完整流程2
android
dalancon39 分钟前
SurfaceFlinger 上帧后 releaseBuffer 完整流程分析
android
用户69371750013842 小时前
不卷AI速度,我卷自己的从容——北京程序员手记
android·前端·人工智能
程序员Android2 小时前
Android 刷新一帧流程trace拆解
android
墨狂之逸才3 小时前
解决 Android/Gradle 编译报错:Comparison method violates its general contract!
android
阿明的小蝴蝶3 小时前
记一次Gradle环境的编译问题与解决
android·前端·gradle
汪海游龙4 小时前
开源项目 Trending AI 招募 Google Play 内测人员(12 名)
android·github
qq_283720055 小时前
MySQL技巧(四): EXPLAIN 关键参数详细解释
android·adb
没有了遇见5 小时前
Android 架构之网络框架多域名配置<三>
android