Android路由+注解
今日核心目标
- 掌握ARouter路由框架的基础用法(页面跳转、参数传递、拦截器)。
- 理解Hilt依赖注入的核心原理,实现接口通信的依赖注入优化,替代手动注册接口的繁琐操作。
- 进一步提升组件通信的灵活性、可扩展性和解耦程度,规避路由和依赖注入的高频踩坑点。
ARouter路由框架
围绕以下几点
- ARouter路由框架认知与基础配置
- ARouter实战
ARouter路由框架认知与基础配置
ARouter路由框架价值
- 解决了原生Intent通信的痛点。
- 类名反射繁琐
- 组件间依赖隐患
- 跳转逻辑分散
- 参数传递复杂
- 实现了组件间无依赖跳转,支持页面跳转、传参、拦截、降级策略。
- 替代原生Intent的复杂跳转场景,尤其适合大型App组件化开发。
- 例:电商App、工具类App
适用场景
- 多组件复杂页面跳转。
- 跨组件参数传递(复杂实体类)。
- 跳转拦截(登录拦截)。
- 路由降级(跳转失败处理)。
基础配置
- 1.根目录
build.gradle.kts中引入ksp插件用于ARouter注解- 需与项目的Gradle、Kotlin插件版本适配
bash
plugins {
id("com.android.application") version "8.7.2" apply false
// 用于支持kotlin的插件,包含编译kotlin代码、kotlin语言的特性支持等
id("org.jetbrains.kotlin.android") version "2.2.20" apply false
// ksp插件
id("com.google.devtools.ksp") version "2.2.20-2.0.4" apply false
...
}
- 2.各组件模块的
build.gradle.kts中引入ARouter相关依赖
scss
plugins {
...
// 启用KSP
id("com.google.devtools.ksp")
}
android {
...
defaultConfig {
...
// ARouter
ksp {
arg("AROUTER_MODULE_NAME", project.name)
}
}
}
dependencies {
...
implementation("com.alibaba:arouter-api:1.5.2") {
// 排除依赖包中的传递项,true or false
// isTransitive = false
// 排除依赖传递
exclude(group = "com.android.support", module = "support-v4")
}
// 适用于ksp注解,官方的未适配ksp
ksp("com.github.JailedBird:ArouterKspCompiler:1.9.20-1.0.7")
}
- 3.壳工程Application中初始化ARouter
kotlin
class App: Application() {
override fun onCreate() {
super.onCreate()
...
// 初始化ARouter(debug模式开启日志,release模式关闭)
if (BuildConfig.DEBUG) {
ARouter.openLog() // 开启日志
ARouter.openDebug() // 开启调试模式(避免混淆导致路由失效)
}
ARouter.init(this) // 初始化ARouter
}
}
- 4.配置ARouter混淆,避免开启混淆后路由失效
kotlin
// app模块的proguard-rules.pro
-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{*;}
# If you use the byType method to obtain Service, add the following rules to protect the interface:
-keep interface * implements com.alibaba.android.arouter.facade.template.IProvider
# If single-type injection is used, that is, no interface is defined to implement IProvider, the following rules need to be added to protect the implementation
# -keep class * implements com.alibaba.android.arouter.facade.template.IProvider
// 如果使用了Autowired注解,需保留相关方法
-keepclassmembers class * {
@com.alibaba.android.arouter.facade.annotation.Autowired *;
}
-keepclassmembers class * {
@com.alibaba.android.arouter.facade.annotation.Route public *;
}
ARouter实战
围绕以下几点
- 页面跳转+基础参数传递
- 复杂实体类参数传递
- 拦截器
页面跳转+基础参数传递
- 1.组件的Activity添加
@Route("")注解,指定路由唯一路径- 路由路径格式:/模块名/Activity类名,全局唯一
kotlin
@Route(path = "/user/UserActivity")
class UserActivity : AppCompatActivity() {
// 使用@Autowired注解接收参数(无需手动获取Intent数据,ARouter自动注入)
@Autowired
lateinit var userId: String // 基础类型参数
@Autowired
lateinit var userName: String // 基础类型参数
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// ARouter注入参数(必须调用,否则参数无法获取)
ARouter.getInstance().inject(this)
...
}
...
}
- 2.使用ARouter实现跳转,替代Intent反射
arduino
ARouter.getInstance()
.build("/user/UserActivity") // 路由路径
.withString("userId", "value") // key对应@Autowired注解的变量名
.withString("userName", "value") // key对应@Autowired注解的变量名
.navigation() // 跳转
复杂实体类参数传递
- 1.base模块中定义实体类,需实现序列化
kotlin
@Parcelize
data class UserInfo(
val userId: String = "123",
val userName: String = "dcx",
val userAvatar: String = "https://xxx"
) : Parcelable
- 2.传递实体类参数
less
ARouter.getInstance()
.build("/user/UserActivity") // 路由路径
.withString("userId", "value") // key对应@Autowired注解的变量名
.withString("userName", "value") // key对应@Autowired注解的变量名
.withString("userName", "value") // key对应@Autowired注解的变量名
.withParcelable("userInfo", UserInfo()) // 实体类传递,key对应@Autowired注解的变量名
.navigation() // 跳转
- 3.目标Activity接收实体类
kotlin
@Route(path = "/user/UserActivity")
class UserActivity : AppCompatActivity() {
...
@Autowired // 接收实体类参数
lateinit var userInfo: UserInfo
...
}
拦截器:登录拦截器
拦截器是ARouter的核心功能之一,用于跳转拦截(例:未登录拦截、权限拦截)。
- 1.给需要拦截的页面添加拦截标识,用于拦截器中判断是否需要拦截
kotlin
@Route(path = "/user/UserActivity", extras = 0x01) // extras:拦截器标记
class UserActivity : AppCompatActivity() {
...
}
- 2.base模块中创建登录拦截器,实现IInterceptor
- 先根据标识判断是否需要拦截,再判断登录状态,登录成功继续跳转,未登录跳转登录页面
kotlin
/**
* 登录拦截器
*
* 拦截器优先级,数值越小优先级越高(0-10)
*/
@Interceptor(priority = 8, name = "登录拦截器")
class LoginInterceptor: IInterceptor {
// 拦截逻辑,跳转前执行
override fun process(
postcard: Postcard?,
callback: InterceptorCallback?
) {
// 判断页面是否需要拦截,拦截页面会注解:extras = 0x01
val needIntercept = postcard?.extra == 0x01
if (!needIntercept) {
// 不需要拦截,继续跳转
callback?.onContinue(postcard)
return
}
val isLogin = FlowBusManager.userStateFlow.value != null
if (isLogin) {
// 已登录,继续跳转
callback?.onContinue(postcard)
} else {
// 未登录,跳转登录页面
ARouter.getInstance()
.build("/user/LoginActivity")
.withString("targetPath", postcard?.path) // 原目标路径
.navigation()
// 中断跳转
callback?.onInterrupt(null)
}
}
// 初始化方法,可初始化拦截器所需资源
override fun init(context: Context?) {
}
}
- 3.登录页修改,实现登录后跳转目标页面
kotlin
@Route(path = "/user/LoginActivity")
class LoginActivity : AppCompatActivity() {
// 使用@Autowired注解接收参数(无需手动获取Intent数据,ARouter自动注入)
@Autowired
var targetPath: String? = null // 接收目标页面路径
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_user)
// ARouter注入参数(必须调用,否则参数无法获取)
ARouter.getInstance().inject(this)
...
}
// 登录成功后,判断是否有目标页面,有则跳转
fun jumpTargetPage() {
targetPath?.let { path ->
ARouter.getInstance().build(path).navigation()
} ?: run {
// 无目标页,关闭当前页面
finish()
}
}
...
}
Hilt依赖注入
Hilt是Google官方推荐,基于Dagger2封装,更简洁、更贴合Android开发,是主流的选择。
本节围绕以下几点
- 价值
- 原理
- 基础配置
- 实战:优化接口通信
价值
Hilt是Google推出的Android专属依赖注入框架,基于Dagger2封装,简化了依赖注入的配置流程,无需手动创建Component接口,解决依赖管理繁琐的问题。 替代手动创建实例、手动注册接口的操作,实现依赖的自动注入,降低组件间耦合,提升代码可测试性和可维护性,是当前Android组件化开发的主流依赖注入框架。
原理
- 基于Dagger2的注解机制,通过简化的注解自动生成依赖注入代码,无需手动创建Component,自动管理Android生命周期,实现依赖的生命周期管理。
- 注解:@HiltAndroidApp、@Inject、@Module、@InstallIn
- 生命周期:Application、Activity
- 无需手动创建对象或注册接口。
- 遵循依赖倒置原则,实现依赖注入而非依赖实例化。
- 让开发者聚焦核心业务逻辑,而非依赖管理的繁琐代码。
基础配置
- 1.项目根目录
build.gradle.kts中,添加Hilt插件依赖
arduino
plugins {
...
// hilt依赖注入
id("com.google.dagger.hilt.android") version "2.57.2" apply false
// 启用KSP,用于hilt注解生成代码
id("com.google.devtools.ksp") version "2.2.20-2.0.4" apply false
}
- 2.组件或模块
build.gradle.kts中添加Hilt依赖并应用插件
scss
plugins {
...
// 应用hilt插件
id("dagger.hilt.android.plugin")
// 启用KSP,用于Hilt生成代码
id("com.google.devtools.ksp")
}
android {
...
}
dependencies {
...
// 依赖注入
implementation("com.google.dagger:hilt-android:2.57.2")
ksp("com.google.dagger:hilt-android-compiler:2.57.2")
}
- 3.自定义Application类,添加@HiltAndroidApp注解,Hilt入口,开启Hilt依赖注入
kotlin
// Hilt核心注解,标记Application为依赖注入入口,自动生成组件
@HiltAndroidApp
class HiltApp: Application() {
}
实战:优化接口通信
完善上一节的ServiceManager的手动注册和获取,实现IUserService接口的自动注入。手动注册示例
- 1.给IUserService的实现类构造方法添加@Inject注解(标记为可被注入的实例)
- user组件中实现IUserService接口
kotlin
// 给IUserService的实现类添加@Inject注解(标记可被注入的实例)
class UserServiceImpl: IUserService {
// 添加@Inject注解,标记构造方法,让Hilt自动创建该类实例
@Inject
constructor()
override fun getUserInfo(): UserInfo {
...
}
override suspend fun getAsyncUserInfo(): UserInfo {
...
}
}
- 2.创建Module类,用于提供依赖实例(即接口实现),并指定安装范围
- user组件中创建Module类
- Hilt的@Singleton与SingletonComponent绑定,无需手动创建Scope注解,比Dagger2更简洁
- @InstallIn()注解
- SingletonComponent:与Application生命周期一致
- ServiceComponent:与Service生命周期一致
- ActivityComponent:与Activity生命周期一致
- FragmentComponent:与Fragment生命周期一致
- ViewComponent:与View生命周期一致
less
// 使用@Module和@InstallIn指定安装到Application级别
@Module
@InstallIn(SingletonComponent::class) // 安装到单例组件,与Application生命周期一致
class UserModule {
// 提供IUserService的实现类,供Hilt注入
@Provides
fun provideUserService(): IUserService = UserServiceImpl()
}
- 3.在使用该接口的Activity中,使用Hilt注入IUserService,替代ServiceManager注册和获取
- 此处以壳工程MainActivity为例
kotlin
// 给Activity添加@AndroidEntryPoint注解,开启Hilt依赖注入
@AndroidEntryPoint // Hilt注解,标记Activity可接收依赖注入
class MainActivity: AppCompatActivity() {
...
// 使用@Inject注解,标记需要注入的依赖IUserService,Hilt自动注入
@Inject
lateinit var userService: IUserService
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
// 无需手动初始化Hilt(@HiltAndroidApp已自动初始化),直接使用注入的依赖
// 接口调用:直接使用注入的userService,无需手动获取
// 同步调用接口
val userInfo = userService.getUserInfo()
}
...
}
- 4.附base模块IUserService接口代码,至此接口通信优化后代码提供完毕
kotlin
data class UserInfo(val data: String)
// 定义在base模块,由user组件实现的接口
interface IUserService {
// 获取用户信息
fun getUserInfo(): UserInfo
// 异步方法:结合协程+Flow
suspend fun getAsyncUserInfo(): UserInfo
}
ARouter、Hilt复盘
复盘ARouter和Hilt的实战代码,修复可能出现的报错(如路由失效、依赖注入失败、拦截器不生效),确保所有实战代码可正常运行、无报错。
复盘1:ARouter实战
- 检查路由路径是否全局唯一,至少为两级路径,即/xxx/xxx。
- @Route注解是否正确添加
- ARouter是否初始化
- 参数注入是否调用inject方法。
- 使用@Autowired注解接收参数(无需手动获取Intent数据,ARouter自动注入)。
- 修复"路由跳转失败""参数获取为null""拦截器不生效"等问题
复盘2:Hilt实战
- 检查注解是否正确(@HiltAndroidApp、@Inject、@Module、@InstallIn)。
- Module是否安装到正确的Component。
- Activity是否添加@AndroidEntryPoint注解,开启Hilt注入。
- 修复"依赖注入失败""userService为null"等问题,确保接口调用正常。
踩坑点
坑点1:ARouter跳转失败,提示"no route found"
- 原因
- 路由路径不唯一
- @Route注解未添加
- ARouter未初始化或调试模式未开启
- 修复
- 确保路由路径全局唯一
- 添加@Route注解
- 在Application中初始化ARouter,debug模式开启openDebug
坑点2:ARouter参数获取为null,使用@Autowired注解接收参数
- 原因
- 未调用ARouter.getInstance().inject(this)
- 参数名与传递时不一致
- 实体类未实现Parcelable
- 修复
- 在Activity#onCreate中调用inject方法
- 核对ARouter跳转时参数名,参数名需与接收页@Autowired注解变量名保持一致
- 给实体类添加@Parcelize注解实现序列化
坑点3:Hilt依赖注入失败,提示"Cannot find symbol Hilt_AppApplication"
- 原因
- 未给Application添加
@HiltAndroidApp注解 - 未应用Hilt插件,未添加ksp或kapt依赖(本节都使用ksp,kapt是另一种添加注解依赖方式)
- 项目同步出错
- 未给Application添加
- 修复
- Application添加
@HiltAndroidApp注解 - 应用Hilt与ksp(kapt)插件
- 重新同步项目并clean项目
- Application添加
坑点4:Hilt注入的IUserService为null
- 原因
- Activity未添加
@AndroidEntryPoint注解 @Inject注解未正确添加到UserServicesImpl构造方法或Activity中注入的实例对象变量- Module未安装到正确的Component
- Activity未添加
- 修复
- Activity添加
@AndroidEntryPoint注解 - 检查
@Inject注解配置 - 确保Module的
@InstallIn与依赖生命周期匹配(正确安装到指定级别)
- Activity添加
坑点5:ARouter拦截器不生效
- 原因
- 拦截器类未添加
@Interceptor注解 - 优先级配置错误
- 未给页面添加拦截标识
- 拦截器类未添加
- 修复
- 给拦截器类添加
@Interceptor注解 - 合理配置优先级(0-10),数值越小优先级越高
- 给需要拦截的页面添加
extras标识
- 给拦截器类添加
坑点6:依赖冲突(ARouter、Hilt与其他依赖冲突)
- 原因
- 依赖版本不统一
- Hilt插件与Android Gradle插件版本不兼容
- 重复引入依赖
- 修复
- 使用版本控制工具,统一管理依赖版本
- 确保Hilt插件与Android Gradle插件兼容
- 避免组件单独引入依赖