Android开发[5]:组件化之路由+注解

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添加@HiltAndroidApp注解
    • 应用Hilt与ksp(kapt)插件
    • 重新同步项目并clean项目

坑点4:Hilt注入的IUserService为null

  • 原因
    • Activity未添加@AndroidEntryPoint注解
    • @Inject注解未正确添加到UserServicesImpl构造方法Activity中注入的实例对象变量
    • Module未安装到正确的Component
  • 修复
    • Activity添加@AndroidEntryPoint注解
    • 检查@Inject注解配置
    • 确保Module的@InstallIn与依赖生命周期匹配(正确安装到指定级别)

坑点5:ARouter拦截器不生效

  • 原因
    • 拦截器类未添加@Interceptor注解
    • 优先级配置错误
    • 未给页面添加拦截标识
  • 修复
    • 给拦截器类添加@Interceptor注解
    • 合理配置优先级(0-10),数值越小优先级越高
    • 给需要拦截的页面添加extras标识

坑点6:依赖冲突(ARouter、Hilt与其他依赖冲突)

  • 原因
    • 依赖版本不统一
    • Hilt插件与Android Gradle插件版本不兼容
    • 重复引入依赖
  • 修复
    • 使用版本控制工具,统一管理依赖版本
    • 确保Hilt插件与Android Gradle插件兼容
    • 避免组件单独引入依赖
相关推荐
随遇丿而安1 小时前
Android全功能终极创作
android
随遇丿而安1 小时前
第1周:别小看 `TextView`,它其实是 Android 页面里最常被低估的组件
android
summerkissyou19875 小时前
Android-基础-SystemClock.elapsedRealtime和System.currentTimeMillis区别
android
ian4u5 小时前
车载 Android C++ 完整技能路线:从基础到进阶
android·开发语言·c++
学习使我健康7 小时前
Android 中 Service 用法
android·kotlin
2601_949816687 小时前
MySQL 数据库连接池爆满问题排查与解决
android·数据库·mysql
Tangsong4048 小时前
以Termius的方式进行安卓设备调试?试试【easyadb】| 多功能可视化adb工具
android·adb
zzb15809 小时前
「Kotlin 泛型深度图解:从入门到实战 + 委托框架揭秘」
开发语言·windows·kotlin