Android 关于compose的一些坑和理解
** 1.如何在 WindowManager.addView 中使用 Jetpack Compose**
一、引出问题
Android 开发中,很常见的一个场景,通过 WindowManager.addView() 添加一个 View 到屏幕上。Android 最新的视图框架 Jetpack Compose,如何应用进来。这个被添加的 View 如何使用 Compose 编写视图呢?
二、探究问题
有的朋友肯定会马上想到使用 ComposeView 作为桥梁。没错,WindowManager.addView 方法,就接收一个 View 类型的参数。那肯定是要借助 ComposeView 了。但是,经过试验,直接使用 ComposeView 是行不通的。
看代码:
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
val composeView: ComposeView = ComposeView(this).apply {
setContent {
Text(text = “I’m be added”)
}
}
windowManager.addView(composeView, params)
上面代码,编译没有问题,运行时会报错:
FATAL EXCEPTION: main
Process: xxxxxxxx
java.lang.IllegalStateException: ViewTreeLifecycleOwner not found from androidx.compose.ui.platform.ComposeView{8285855 V.E… …I. 0,0-0,0}
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer(WindowRecomposer.android.kt:352)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.createLifecycleAwareWindowRecomposer d e f a u l t ( W i n d o w R e c o m p o s e r . a n d r o i d . k t : 325 ) a t a n d r o i d x . c o m p o s e . u i . p l a t f o r m . W i n d o w R e c o m p o s e r F a c t o r y default(WindowRecomposer.android.kt:325) at androidx.compose.ui.platform.WindowRecomposerFactory default(WindowRecomposer.android.kt:325)atandroidx.compose.ui.platform.WindowRecomposerFactoryCompanion$LifecycleAware 1. c r e a t e R e c o m p o s e r ( W i n d o w R e c o m p o s e r . a n d r o i d . k t : 168 ) a t a n d r o i d x . c o m p o s e . u i . p l a t f o r m . W i n d o w R e c o m p o s e r P o l i c y . c r e a t e A n d I n s t a l l W i n d o w R e c o m p o s e r 1.createRecomposer(WindowRecomposer.android.kt:168) at androidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposer 1.createRecomposer(WindowRecomposer.android.kt:168)atandroidx.compose.ui.platform.WindowRecomposerPolicy.createAndInstallWindowRecomposerui_release(WindowRecomposer.android.kt:224)
at androidx.compose.ui.platform.WindowRecomposer_androidKt.getWindowRecomposer(WindowRecomposer.android.kt:300)
at androidx.compose.ui.platform.AbstractComposeView.resolveParentCompositionContext(ComposeView.android.kt:244)
at androidx.compose.ui.platform.AbstractComposeView.ensureCompositionCreated(ComposeView.android.kt:251)
at androidx.compose.ui.platform.AbstractComposeView.onAttachedToWindow(ComposeView.android.kt:283)
at android.view.View.dispatchAttachedToWindow(View.java:22065)
at android.view.ViewGroup.dispatchAttachedToWindow(ViewGroup.java:3553)
…
看这个错误信息:
应该是从 ComposeView 中没有找到 ViewTreeLifecycleOwner, 其实很好理解。 View 的生命周期依赖于 ViewTreeLifecycleOwner, ComposeView 依赖于一个 ViewCompositonStrategy。核心问题是,ComposeView 需要一个 Lifecycle。
三、解决问题
有了思路自然就尝试解决问题。
首先定义一个 LifecycleOwner ,
import android.view.View
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.LifecycleRegistry
import androidx.lifecycle.ViewModelStore
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.setViewTreeLifecycleOwner
import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.SavedStateRegistry
import androidx.savedstate.SavedStateRegistryController
import androidx.savedstate.SavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
class MyComposeViewLifecycleOwner:
LifecycleOwner, ViewModelStoreOwner, SavedStateRegistryOwner {
private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
private val savedStateRegistryController = SavedStateRegistryController.create(this)
private val store = ViewModelStore()override val lifecycle: Lifecycleget() = lifecycleRegistry
override val savedStateRegistry: SavedStateRegistryget() = savedStateRegistryController.savedStateRegistry
override val viewModelStore: ViewModelStoreget() = storefun onCreate() {savedStateRegistryController.performRestore(null)lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_CREATE)
}fun onStart() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_START)
}fun onResume() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_RESUME)
}fun onPause() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_PAUSE)
}fun onStop() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_STOP)
}fun onDestroy() {lifecycleRegistry.handleLifecycleEvent(Lifecycle.Event.ON_DESTROY)store.clear()
}/*** Compose uses the Window's decor view to locate the* Lifecycle/ViewModel/SavedStateRegistry owners.* Therefore, we need to set this class as the "owner" for the decor view.*/
fun attachToDecorView(decorView: View?) {decorView?.let {it.setViewTreeViewModelStoreOwner(this)it.setViewTreeLifecycleOwner(this)it.setViewTreeSavedStateRegistryOwner(this)} ?: return
}
}
再看看使用:
private var lifecycleOwner: MyComposeViewLifecycleOwner? = null
val params = WindowManager.LayoutParams(
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.WRAP_CONTENT,
WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY,
WindowManager.LayoutParams.FLAG_NOT_FOCUSABLE,
PixelFormat.TRANSLUCENT
)
val composeView: ComposeView = ComposeView(this).apply {
setContent {
Text(text = “I’m be added”)
}
}
// 注意,在 调用 addView 之前:
lifecycleOwner = MyComposeViewLifecycleOwner().also {
it.onCreate() // 注意
it.attachToDecorView(composeView)
}
windowManager.addView(composeView, params)
windowManager.removeViewImmediate(composeView)
lifecycleOwner?.onDestroy()
lifecycleOwner = null
OK,再次运行。成功~
做完这些后,你会发现还有个坑,state 不刷新状态,你需要手动的模拟调用lifecycler生命周期方法。除了oncreate 还要调用onstart onresume,否则不生效。
2.Android compose不是会让代码冗余在一起吗?
Android Compose 的代码冗余问题需要结合其设计理念和使用方式辩证看待。以下从技术原理、实际应用场景和最佳实践角度综合分析:
一、Compose 的设计目标与冗余控制
声明式 UI 的简化逻辑
Compose 通过声明式编程范式,允许开发者直接描述 UI 的最终状态,无需手动处理视图更新逻辑。传统命令式代码中维护视图状态和同步的冗余操作(如 findViewById 和手动更新视图)被框架自动处理47。
组件化与复用机制
Compose 的 @Composable 函数天然支持模块化拆分,小型 UI 组件(如按钮、卡片)可独立封装并通过参数配置复用,减少重复代码16。例如:
kotlin
Copy Code
@Composable
fun CustomButton(text: String, onClick: () -> Unit) {
Button(onClick = onClick) { Text(text) }
}
二、可能导致冗余的场景与解决方案
不合理的设计模式
问题:未遵循分层架构(如 MVVM)时,可能将业务逻辑与 UI 代码混杂在 Composable 中,导致代码臃肿38。
解决方案:使用状态管理工具(如 ViewModel + StateFlow)分离逻辑层,保持 Composable 仅关注 UI 渲染38。
过度组合与嵌套
问题:嵌套过多 Composable 函数可能降低可读性(如深度布局嵌套),但属于代码结构问题而非冗余15。
解决方案:通过 @Stable 注解优化重组性能,或拆分复杂布局为子组件8。
三、与传统视图系统的对比
对比维度 传统 View/XML 方式 Jetpack Compose
代码量 需在 XML 和 Kotlin/Java 间切换,模板代码多 纯 Kotlin 代码,减少 30%-50% 的代码量47
状态管理 手动同步视图与数据状态(易出错) 自动响应状态变化(声明式驱动)47
组件复用 依赖自定义 View 或 include 标签 原生函数级组合,参数化配置更灵活16
四、最佳实践建议
遵循单一职责原则
每个 Composable 函数仅实现一个明确功能(如 UserProfileCard 仅处理头像和名称展示)38。
分层架构强化
结合 Clean Architecture 或 MVI 模式,通过 Repository 和 UseCase 隔离数据源与 UI 层38。
工具链支持
利用 Android Studio 的实时预览和重组计数检查(Layout Inspector),快速定位冗余代码17。
结论
Compose 本身通过声明式设计和组件化机制显著降低了代码冗余,但实际开发中是否冗余取决于架构设计和开发者对组合模式的合理运用13。遵循分层架构和模块化拆分原则,Compose 可成为高效且简洁的 UI 开发方案。
3.那怎么使用状态管理工具(如 ViewModel + StateFlow)分离逻辑层,保持 Composable 仅关注 UI 渲染呢?
好的!我们通过一个简单的 计数器应用 来演示如何用 ViewModel + StateFlow 实现逻辑与 UI 的分离,让 Composable 只负责渲染。
完整示例代码
kotlin
Copy Code
// 1️⃣ 数据层/状态定义
data class CounterState(
val count: Int = 0,
val isLoading: Boolean = false
)
// 2️⃣ 逻辑层:ViewModel 处理业务逻辑
class CounterViewModel : ViewModel() {
// 私有状态,通过 StateFlow 对外暴露
private val _state = MutableStateFlow(CounterState())
val state: StateFlow = _state.asStateFlow()
// 事件处理:纯逻辑,不涉及 UI
fun increment() {_state.update { it.copy(count = it.count + 1) }
}fun reset() {_state.update { it.copy(count = 0) }
}
}
// 3️⃣ UI 层:Composable 仅负责显示和事件传递
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val state by viewModel.state.collectAsState()
Column(modifier = Modifier.fillMaxSize(),verticalArrangement = Arrangement.Center,horizontalAlignment = Alignment.CenterHorizontally
) {Text(text = "Count: ${state.count}", fontSize = 24.sp)Button(onClick = { viewModel.increment() }) { // ❗️ 事件传递给 ViewModelText("Add +1")}Button(onClick = { viewModel.reset() }) {Text("Reset")}
}
}
关键解析
-
数据层(CounterState)
定义所有 UI 需要显示的状态(如 count 和 isLoading)。
作用:集中管理 UI 状态,避免分散在 Composable 中。 -
逻辑层(CounterViewModel)
职责:处理业务逻辑(如计算、网络请求等)。
StateFlow:通过 StateFlow 暴露状态,确保 UI 能监听状态变化。
事件处理:increment() 和 reset() 方法只修改状态,不涉及 UI 操作。 -
UI 层(CounterScreen)
仅做三件事:
收集状态:通过 collectAsState() 监听 StateFlow。
渲染 UI:根据 state 显示内容。
传递事件:用户点击时调用 viewModel.increment(),不直接修改状态。
无业务逻辑:UI 不关心如何计算,只负责显示结果。
对比传统错误写法
kotlin
Copy Code
// ❌ 错误示例:逻辑与 UI 混杂
@Composable
fun BadCounter() {
var count by remember { mutableStateOf(0) }Column(…) {
Text(“Count: $count”)
Button(onClick = { count++ }) { // ❗️ 业务逻辑直接写在 UI 层
Text(“Add +1”)
}
}
}
问题:业务逻辑(计数器逻辑)直接嵌入 UI 层,无法复用且难以测试。
关键原则
单向数据流:
UI → 发送事件 → ViewModel → 更新状态 → UI 响应
UI 无状态化:
Composable 只持有 View 相关的临时状态(如动画进度),业务状态全部交给 ViewModel。
可测试性:
可单独测试 ViewModel 的逻辑,无需依赖 UI 框架。
进阶场景
如果应用需要处理异步操作(如网络请求),进一步分层:
kotlin
Copy Code
class UserViewModel(
private val userRepository: UserRepository // 数据源抽象
) : ViewModel() {
private val _state = MutableStateFlow(UserState())
val state: StateFlow = _state.asStateFlow()
fun loadUser(userId: String) {viewModelScope.launch {_state.update { it.copy(isLoading = true) }val user = userRepository.getUser(userId) // 调用仓库层_state.update { it.copy(user = user, isLoading = false) }}
}
}
通过这种分层架构,Compose 代码保持简洁,逻辑冗余和维护成本将显著降低!
那如果在上面的例子中的数据层加入网络请求,该怎么写?
以下是在原有计数器示例中加入网络请求层的完整实现,采用 Repository 模式 + ViewModel + StateFlow 架构,结合搜索结果中提到的协程与 Retrofit 实践:
分层架构与代码实现
-
网络请求层(Retrofit 接口)
kotlin
Copy Code
// 📁 network/ApiService.kt
interface ApiService {
@GET(“api/counter”)
suspend fun getCounter(): Response // 模拟获取初始值@POST(“api/counter”)
suspend fun saveCounter(@Body count: Int): Response // 模拟保存
} -
数据层(Repository)
kotlin
Copy Code
// 📁 repository/CounterRepository.kt
class CounterRepository(private val api: ApiService) {
// 封装网络请求与本地逻辑
suspend fun loadInitialCount(): Int {
return try {
val response = api.getCounter()
if (response.isSuccessful) response.body() ?: 0 else 0
} catch (e: Exception) {
0 // 网络异常返回默认值
}
}suspend fun saveCount(count: Int): Boolean {
return try {
api.saveCounter(count).isSuccessful
} catch (e: Exception) {
false
}
}
} -
状态层(扩展原有 CounterState)
kotlin
Copy Code
// 📁 state/CounterState.kt
data class CounterState(
val count: Int = 0,
val isLoading: Boolean = false, // 加载状态
val error: String? = null // 错误信息
) -
逻辑层(ViewModel 改造)
kotlin
Copy Code
// 📁 viewmodel/CounterViewModel.kt
class CounterViewModel(
private val repository: CounterRepository
) : ViewModel() {
private val _state = MutableStateFlow(CounterState())
val state: StateFlow = _state.asStateFlow()// 初始化时加载网络数据
init {
loadInitialCount()
}private fun loadInitialCount() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
val initialCount = repository.loadInitialCount()
_state.update { it.copy(count = initialCount, isLoading = false) }
}
}fun increment() {
_state.update { it.copy(count = it.count + 1) }
saveCount() // 每次修改后自动保存
}private fun saveCount() {
viewModelScope.launch {
_state.update { it.copy(isLoading = true) }
val success = repository.saveCount(state.value.count)
_state.update {
it.copy(
isLoading = false,
error = if (!success) “保存失败” else null
)
}
}
}
} -
UI 层(Composable 适配状态)
kotlin
Copy Code
// 📁 ui/CounterScreen.kt
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val state by viewModel.state.collectAsState()Box(modifier = Modifier.fillMaxSize()) {
Column(
modifier = Modifier.align(Alignment.Center),
horizontalAlignment = Alignment.CenterHorizontally
) {
if (state.isLoading) {
CircularProgressIndicator() // 加载指示器
} else {
Text(text = “当前计数: ${state.count}”, fontSize = 24.sp)
}Button(onClick = { viewModel.increment() },enabled = !state.isLoading // 加载时禁用按钮) {Text("增加计数")}state.error?.let { Text(text = it, color = Color.Red) // 错误提示}}
}
}
关键实现说明
分层架构
网络层:Retrofit 定义 API 接口
数据层:Repository 封装网络请求与本地逻辑
逻辑层:ViewModel 通过协程管理异步操作,更新 StateFlow 状态
UI 层:Composable 仅观察状态并发送事件
状态管理
使用 CounterState 集中管理 加载状态、错误信息 和 核心数据
通过 viewModelScope 自动取消协程,避免内存泄漏
错误处理
使用 try-catch 捕获网络异常
通过 error 字段传递错误信息到 UI 层
依赖注入示例(简化版)
kotlin
Copy Code
// 📁 di/AppModule.kt (使用 Koin 示例)
val appModule = module {
single { Retrofit.Builder().baseUrl(“https://your-api.com/”).build() }
single { get().create(ApiService::class.java) }
single { CounterRepository(get()) }
viewModel { CounterViewModel(get()) }
}
通过这种设计,网络请求逻辑被完全隔离在数据层,UI 层只需处理状态渲染,符合 MVVM 架构的最佳实践。
上面的示例中如果compose里面的ui修改了数据,如何写回数据层?
在 Compose 中修改数据并写回数据层,需要遵循 单向数据流 原则:UI 层只负责触发事件,由 ViewModel 协调业务逻辑并更新数据层。以下是具体实现流程和示例:
数据修改流程
text
Copy Code
UI 触发事件 → ViewModel 处理逻辑 → Repository 操作数据层 → 更新 State → UI 刷新
场景示例:用户通过输入框修改数据
我们扩展之前的计数器,允许用户手动输入数值并保存到服务器。
-
修改 State(增加输入相关状态)
kotlin
Copy Code
// State.kt
data class CounterState(
val count: Int = 0,
val inputText: String = “”, // 用户输入内容
val isLoading: Boolean = false,
val error: String? = null
) -
修改 ViewModel(新增输入处理方法)
kotlin
Copy Code
// CounterViewModel.kt
class CounterViewModel(private val repo: CounterRepository) : ViewModel() {
private val _state = MutableStateFlow(CounterState())
val state: StateFlow = _state.asStateFlow()// 处理输入框内容变化
fun onInputChange(newText: String) {
_state.update { it.copy(inputText = newText) }
}// 提交用户输入的值
fun submitCustomCount() {
val newCount = state.value.inputText.toIntOrNull() ?: run {
_state.update { it.copy(error = “请输入有效数字”) }
return
}viewModelScope.launch {_state.update { it.copy(isLoading = true) }val success = repo.saveCount(newCount)_state.update {if (success) {it.copy(count = newCount, isLoading = false, inputText = "")} else {it.copy(error = "保存失败", isLoading = false)}}}
}
} -
修改 UI 层(增加输入框)
kotlin
Copy Code
// CounterScreen.kt
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
val state by viewModel.state.collectAsState()Column(Modifier.padding(16.dp)) {
// 显示当前计数
Text(“当前计数: ${state.count}”, style = MaterialTheme.typography.h6)// 输入框OutlinedTextField(value = state.inputText,onValueChange = viewModel::onInputChange, // 输入变化触发事件label = { Text("输入新数值") },keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Number))// 提交按钮Button(onClick = { viewModel.submitCustomCount() },enabled = !state.isLoading) {if (state.isLoading) {CircularProgressIndicator(Modifier.size(20.dp))} else {Text("手动更新")}}// 错误提示state.error?.let {Text(it, color = Color.Red)}
}
}
关键机制解释
输入处理
用户输入时,onValueChange 触发 viewModel.onInputChange,更新 inputText 状态
不直接修改数据层,仅记录临时输入内容
数据提交
点击按钮时,调用 viewModel.submitCustomCount()
ViewModel 进行数据验证(检查是否为有效数字)
通过 Repository 保存到网络,成功后更新 count 状态
状态同步
数据保存成功后,count 更新会自动触发 UI 重组
输入框内容清空 (inputText = “”)
架构优势
层级 职责 修改数据的入口
UI 层 展示数据、收集用户输入事件 仅调用 viewModel.xxx()
ViewModel 业务逻辑处理、状态管理 修改 StateFlow 并操作 Repository
Repository 统一数据源访问(网络/本地) 提供 saveCount() 等原子操作
处理复杂场景
如果需要本地缓存 + 网络同步,可在 Repository 中添加逻辑:
kotlin
Copy Code
// CounterRepository.kt
class CounterRepository(
private val api: ApiService,
private val localDataSource: CounterLocalDataSource
) {
suspend fun saveCount(count: Int): Boolean {
return try {
// 先保存到本地
localDataSource.save(count)
// 再同步到网络
api.saveCounter(count).isSuccessful
} catch (e: Exception) {
false
}
}
}
通过这种设计,Compose UI 修改数据的完整链路被严格约束在架构规范内,数据流向清晰且易于维护扩展。
那前面我说的依赖注入是什么意思,如果你不太理解,我详细讲讲
依赖注入(Dependency Injection, DI)是一种设计模式,用于解耦代码并提高可维护性。通过外部提供组件所需的依赖,而不是让组件自己创建依赖,使得代码更灵活、更易测试。以下结合你的示例逐步解释:
- 什么是依赖注入?
想象一个咖啡机需要咖啡豆:
❌ 传统方式:咖啡机内部自己种植咖啡豆 → 高耦合
✅ 依赖注入:外部提供咖啡豆给咖啡机 → 解耦
代码中的类比:
ViewModel 需要 Repository
Repository 需要 ApiService
依赖注入负责将这些依赖“传递”给需要它们的组件。
2. 为什么需要依赖注入?
问题示例(无依赖注入)
kotlin
Copy Code
// ❌ 直接在 ViewModel 内部创建 Repository
class CounterViewModel : ViewModel() {
private val repo = CounterRepository() // 直接创建依赖
// …
}
// ❌ Repository 内部创建 ApiService
class CounterRepository {
private val api = Retrofit.Builder().build().create(ApiService::class.java)
// …
}
痛点:
组件高度耦合,难以替换实现(如测试时无法替换为 Mock 接口)。
重复创建对象(如多个组件需要同一个 ApiService 实例)。
3. 如何实现依赖注入?
(1) 手动依赖注入(不借助框架)
kotlin
Copy Code
// 步骤1:外部创建依赖
val apiService = Retrofit.Builder().build().create(ApiService::class.java)
val repository = CounterRepository(apiService) // 注入 ApiService
val viewModel = CounterViewModel(repository) // 注入 Repository
// 步骤2:通过构造函数传递依赖
class CounterViewModel(private val repo: CounterRepository) : ViewModel() {
// 业务逻辑使用 repo
}
class CounterRepository(private val api: ApiService) {
// 数据操作使用 api
}
(2) 使用依赖注入框架(如 Koin/Hilt)
以 Koin 为例:
kotlin
Copy Code
// 📁 di/AppModule.kt
val appModule = module {
// 定义如何创建 ApiService(单例)
single { Retrofit.Builder().build().create(ApiService::class.java) }
// 定义如何创建 Repository,注入 ApiService
single { CounterRepository(get()) } // get() 会自动查找已注册的 ApiService// 定义 ViewModel,注入 Repository
viewModel { CounterViewModel(get()) }
}
// 📁 MainActivity.kt
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
// 初始化 Koin
startKoin { modules(appModule) }
setContent { CounterScreen() }
}
}
- 在你的示例中如何工作?
定义依赖关系:
Koin 模块声明 ApiService → Repository → ViewModel 的创建顺序。
自动注入:
viewModel { CounterViewModel(get()) } 中的 get() 会自动查找并注入已注册的 CounterRepository。
在 Composable 中获取 ViewModel:
kotlin
Copy Code
@Composable
fun CounterScreen(viewModel: CounterViewModel = viewModel()) {
// Koin 会自动提供已注入依赖的 ViewModel 实例
}
- 依赖注入的优势
场景 无依赖注入 有依赖注入
替换实现 需修改多处代码 只需修改模块定义
单元测试 难以 Mock 依赖 轻松替换为测试依赖(如 Mock 对象)
代码复用 重复创建相同对象 单例管理,全局复用
生命周期管理 手动管理 框架自动管理(如 ViewModel 生命周期) - 常见问题
Q:一定要用 Koin/Hilt 吗?
不是!小型项目可以手动注入,但框架能简化大型项目的依赖管理。
Q:依赖注入会增加复杂度吗?
初期学习成本略高,但长期大幅提升代码可维护性。
Q:Compose 中如何配合使用?
通过 viewModel() 或 hiltViewModel() 自动获取已注入依赖的 ViewModel。
通过依赖注入,你的示例中的 ViewModel、Repository 和 ApiService 完全解耦,各组件职责清晰,为应用扩展(如添加数据库、Mock 测试)打下坚实基础。
上面如果换成用hilt依赖注入是怎么样的?
好的!我们使用 Hilt(基于 Dagger 的 Android 官方 DI 框架)替代 Koin,重构之前的依赖注入实现。以下是详细步骤和代码对比:
- 添加 Hilt 依赖
在 build.gradle 中添加必要依赖:
groovy
Copy Code
// 根目录 build.gradle
plugins {
id ‘com.google.dagger.hilt.android’ version ‘2.48’ apply false
}
// App 模块 build.gradle
plugins {
id ‘com.google.dagger.hilt.android’
}
dependencies {
implementation “com.google.dagger:hilt-android:2.48”
kapt “com.google.dagger:hilt-android-compiler:2.48”
implementation ‘androidx.hilt:hilt-navigation-compose:1.0.0’ // Compose 支持
}
- 定义 Hilt 模块
Retrofit 模块
kotlin
Copy Code
// 📁 di/NetworkModule.kt
@Module
@InstallIn(SingletonComponent::class) // 全局单例
object NetworkModule {
@Provides
fun provideApiService(): ApiService {
return Retrofit.Builder()
.baseUrl(“https://your-api.com/”)
.build()
.create(ApiService::class.java)
}
}
Repository 模块
kotlin
Copy Code
// 📁 di/RepositoryModule.kt
@Module
@InstallIn(SingletonComponent::class)
object RepositoryModule {
@Provides
fun provideCounterRepository(api: ApiService): CounterRepository {
return CounterRepository(api)
}
}
- 修改 Application 类
kotlin
Copy Code
// 📁 MyApplication.kt
@HiltAndroidApp // 必须标记
class MyApplication : Application()
在 AndroidManifest.xml 中声明:
xml
Copy Code
<application
android:name=“.MyApplication”
…>
-
改造 ViewModel (使用 Hilt 注入)
kotlin
Copy Code
// 📁 viewmodel/CounterViewModel.kt
@HiltViewModel // 替换 Koin 的 viewModel 定义
class CounterViewModel @Inject constructor(
private val repository: CounterRepository // 自动注入
) : ViewModel() {
// 原有逻辑不变
} -
在 Compose 中获取 ViewModel
kotlin
Copy Code
// 📁 ui/CounterScreen.kt
@Composable
fun CounterScreen(
viewModel: CounterViewModel = hiltViewModel() // 替换 Koin 的 viewModel()
) {
val state by viewModel.state.collectAsState()
// 原有 UI 逻辑不变
} -
对比 Hilt 与 Koin 的关键区别
特性 Hilt (Dagger) Koin
原理 编译时生成代码(APT + KSP) 运行时反射 + 函数式注册
性能 编译时处理,无运行时开销 运行时解析,轻微性能损耗
学习曲线 较陡峭(需理解 Dagger 注解) 简单(类似 Service Locator)
代码侵入性 需要 @HiltAndroidApp 和 @AndroidEntryPoint 无侵入,纯 Kotlin 实现
Google 官方支持 是 否 -
Hilt 核心注解说明
注解 作用
@HiltAndroidApp 标记 Application 类,触发 Hilt 代码生成
@AndroidEntryPoint 标记 Activity/Fragment 等 Android 组件,允许注入依赖(Compose 中用 hiltViewModel() 替代)
@HiltViewModel 标记 ViewModel,允许通过构造函数注入依赖
@Module + @InstallIn 定义依赖提供模块,并指定作用域(如 SingletonComponent 表示全局单例)
@Provides 在模块中标记提供依赖的方法
@Inject 标记构造函数或字段,表示需要注入依赖 -
处理复杂依赖场景
需要 Context 的依赖(示例)
kotlin
Copy Code
@Module
@InstallIn(SingletonComponent::class)
object ContextModule {
@Provides
fun provideSharedPreferences(@ApplicationContext context: Context): SharedPreferences {
return context.getSharedPreferences(“my_prefs”, Context.MODE_PRIVATE)
}
}
接口与实现类绑定
kotlin
Copy Code
// 定义接口
interface DataSource {
fun fetchData(): String
}
// 实现类
class NetworkDataSource @Inject constructor() : DataSource {
override fun fetchData() = “Data from network”
}
// 模块绑定
@Module
@InstallIn(SingletonComponent::class)
interface DataSourceModule {
@Binds
fun bindDataSource(networkDS: NetworkDataSource): DataSource
}
- 测试中的依赖替换
在单元测试中,可以用 Hilt 的测试支持替换模块:
kotlin
Copy Code
// 测试模块
@Module
@TestInstallIn(components = [SingletonComponent::class], replaces = [NetworkModule::class])
object FakeNetworkModule {
@Provides
fun provideFakeApiService(): ApiService {
return object : ApiService { // 模拟实现
override suspend fun getCounter() = Response.success(100)
}
}
}
// 测试类
@HiltAndroidTest
class CounterTest {
@get:Rule
val hiltRule = HiltAndroidRule(this)
@Before
fun init() {hiltRule.inject()
}@Test
fun testCounterInitialValue() {val vm = hiltViewModel<CounterViewModel>()assert(vm.state.value.count == 100) // 使用模拟数据
}
}
通过 Hilt 实现依赖注入,你的应用将获得:
编译时安全性:依赖关系在编译时检查,避免运行时错误
高效性能:无反射开销
标准化架构:符合 Google 推荐的 Android 开发最佳实践
在大型项目中,Hilt 的强类型和编译时检查优势尤为明显,建议优先选择 Hilt 作为 DI 框架。
hilt依赖注入没使用反射吗?那它是什么原理生成的
Hilt 的依赖注入机制不依赖反射,其核心原理是通过编译时代码生成实现的。以下是具体实现原理的分解:
一、Hilt 的核心机制
- 注解处理
编译时扫描:Hilt 在编译阶段通过注解处理器(APT/KSP)扫描代码中的注解(如 @HiltAndroidApp、@HiltViewModel 等)。
生成代码模板:根据注解生成对应的 Dagger 组件(Component)和模块(Module)实现类,例如 Hilt_MyApplication、Hilt_CounterViewModel 等。 - 组件与模块管理
组件层级:Hilt 预定义了 Android 组件的层级(如 SingletonComponent、ActivityComponent),每个层级对应不同的生命周期范围。
依赖绑定:通过 @Module 和 @Provides 定义依赖提供方式,Hilt 自动将依赖注入到目标类(如 ViewModel、Activity)的构造函数或字段中。 - 代码生成与编译优化
静态代码生成:Hilt 在编译时生成所有依赖注入相关的代码(如 Factory 类、Component 接口实现),运行时直接调用生成的代码完成依赖注入。
类型安全检查:所有依赖关系在编译时完成验证,避免运行时因类型不匹配导致的错误。
二、Hilt 与反射的对比
特性 Hilt(Dagger 2) 反射方案(如 Koin)
实现方式 编译时生成代码 运行时通过反射解析依赖
性能 无运行时开销,效率高 存在反射调用开销,效率略低
类型安全 编译时检查,避免类型错误 运行时可能因类型问题崩溃
可维护性 代码生成逻辑清晰,但需理解 Dagger 注解 配置简单,但依赖关系隐式管理
三、Hilt 的工作流程示例
以 @HiltViewModel 为例:
编译阶段:
Hilt 检测到 @HiltViewModel 注解,生成 Hilt_CounterViewModel 类及对应的 ViewModelFactory。
生成 DaggerAppComponent 类,包含依赖注入的完整逻辑。
运行时阶段:
应用启动时,@HiltAndroidApp 注解触发全局依赖容器的初始化。
在 Activity 中调用 hiltViewModel() 时,直接使用预生成的工厂类实例化 ViewModel,无需反射。
四、优势总结
高性能:完全避免反射,依赖解析在编译期完成。
强类型安全:所有依赖关系编译时验证,减少运行时崩溃风险。
标准化架构:通过预定义组件层级,强制规范代码结构。
Hilt 的设计充分体现了编译时优化和类型安全优先的理念,是大型 Android 项目依赖注入的优选方案。
相关文章:
Android 关于compose的一些坑和理解
** 1.如何在 WindowManager.addView 中使用 Jetpack Compose** 一、引出问题 Android 开发中,很常见的一个场景,通过 WindowManager.addView() 添加一个 View 到屏幕上。Android 最新的视图框架 Jetpack Compose,如何应用进来。这个被添加的…...
ref setState 合成事件
ref & setState & 合成事件 受控组件的概念:数据改变视图的叫受控组件;通过dom操作改变的叫非受控。 语法:给refxxx赋一个值,然后通过this.refs.xxx就可以获取到相应dom元素,通过你这个名字存储的值就是这个do…...
调用feapder作为子程序时setting.py文件不起作用
feaper 官方文档地址: 简介及安装 - feapder官方文档|feapder-document 问题: 在最近的开发中需要调用feapder作为主程序调用的子程序时发现自动入库时无法入库,通过查看日志信息发现连接数据库时被拒绝连接了,但是我的setting.p…...
gralloc1_perform具体在干什么
gralloc1_perform 会在特定场景下通过 ioctl 调用,执行 缓存 (cache) 管理 和 内存映射 操作,确保 CPU 和 GPU 之间的数据一致性。 📌 为什么需要对 cache 进行操作? 在 Android 系统中,CPU 和 GPU 通常共享 DDR 内存…...
【Pandas】pandas Series plot.barh
Pandas2.2 Series Plotting 方法描述Series.plot([kind, ax, figsize, …])用于绘制 Series 对象的数据可视化图表Series.plot.area([x, y, stacked])用于绘制堆叠面积图(Stacked Area Plot)Series.plot.bar([x, y])用于绘制垂直条形图(Ver…...
机器学习之浅层神经网络
文章目录 一、浅层神经网络概述(一)定义(二)常见类型 二、浅层神经网络的前向传播三、浅层神经网络的反向传播四、编写浅层神经网络案例(Python NumPy 实现)代码解释 五、浅层神经网络与深度学习的区别&am…...
透析Vue的nextTick原理
nextTick 是 Vue.js 中的一个核心机制,用于在 下一次 DOM 更新周期后 执行回调函数。它的核心原理是 利用 JavaScript 的事件循环机制(Event Loop),结合微任务(Microtask)或宏任务(Macrotask&am…...
Beans模块之工厂模块注解模块@Qualifier
博主介绍:✌全网粉丝5W,全栈开发工程师,从事多年软件开发,在大厂呆过。持有软件中级、六级等证书。可提供微服务项目搭建与毕业项目实战,博主也曾写过优秀论文,查重率极低,在这方面有丰富的经验…...
产品更新 | 数字助决胜:华望M-Arch平台实现从体系模型到仿真推演
华望产品更新速递 功能介绍 |M-Arch体系建模软件的核心功能 ◆体系架构建模 ◆逻辑仿真 ◆与多种工具集成 ◆多专业协同建模 产品亮点 |M-Arch软件在体系作战中的作用 ◆全面构建任务和体系架构建模的能力 ◆模型化的装备体系分析方法 ◆提升作战体系架构设计与优化 前…...
IP地址结构体与字符串转换函数详解
IP地址结构体与字符串转换函数详解 在Linux C网络编程中,IP地址的二进制结构体(如struct in_addr)与字符串形式(如"192.168.1.1")之间的转换经常涉及到,与IP地址格式相关的函数包括inet_aton、i…...
debug - 安装.msi时,为所有用户安装程序
文章目录 debug - 安装.msi时,为所有用户安装程序概述笔记试试在目标.msi后面直接加参数的测试 备注备注END debug - 安装.msi时,为所有用户安装程序 概述 为了测试,装了一个test.msi. 安装时,只有安装路径的选择,没…...
基于springboot的社区团购系统(012)
摘 要 本课题是根据用户的需要以及网络的优势建立的一个社区团购系统,来满足用户团购的需求。 本社区团购系统应用Java技术,MYSQL数据库存储数据,基于Spring Boot框架开发。在网站的整个开发过程中,首先对系统进行了需求分析&…...
应用权限组列表
文章目录 使用须知位置相机麦克风通讯录日历运动数据身体传感器图片和视频音乐和音频跨应用关联设备发现和连接剪切板文件夹文件(deprecated) 使用须知 在申请目标权限前,建议开发者先阅读应用权限管控概述-权限组和子权限,了解相关概念,再合…...
4.1、网络安全模型
目录 网络安全体系概述网络安全模型-BLP模型网络安全模型-Biba模型网络安全模型 - 信息流模型信息保障模型能力成熟度模型其它安全模型网络安全原则 网络安全体系概述 网络安全体系是网络安全保证系统的最高层概念抽象,是一个体系,体系一般是一个概念&a…...
前端对接生成式AI接口(类ChatGPT)问题汇总
文章目录 前端实现对话流问题总结流式数据传输问题后台Response Headers问题大量数据分段接收问题多个流时间戳(Time)相同导致被合并的问题 中止对话问题复制问题部署上线问题(Nginx缓冲导致) 前端实现对话流问题总结 流式数据传…...
布隆过滤器(Bloom Filter)详解
布隆过滤器详解 1. 什么是布隆过滤器? 布隆过滤器(Bloom Filter)是一种高效的概率型数据结构,主要用于判断某个元素是否存在于一个集合中。它的特点是: 允许误判:可能会误判元素存在(假阳性&…...
QoS(Quality of Service)服务质量概念
一、什么是QoS? QoS(Quality of Service)即服务质量,在网络业务中可以通过保证传输的带宽、降低传输时延、降低数据丢包率以及时延抖动等措施来提高服务质量。QoS是一套用于管理和提高网络性能的工具和技术,用于流量优…...
CSS实现当鼠标悬停在一个元素上时,另一个元素的样式发生变化的效果
CSS可以实现当鼠标悬停在一个元素上时,另一个元素的样式发生变化的效果。可以通过以下几种方法来实现: 1. 使用兄弟选择器(Adjacent Sibling Selector) 如果两个元素是兄弟关系(即它们有相同的父元素)&am…...
【C++11】左值引用、右值引用、移动语义和完美转发
🦄个人主页:修修修也 🎏所属专栏:C ⚙️操作环境:Visual Studio 2022 目录 📌左值引用和右值引用 🎏左值和左值引用 🎏右值和右值引用 📌左值引用和右值引用比较 🎏左值引用 🎏右值…...
Docker镜像迁移
目录 1.查看镜像当前配置 2.镜像迁移 1. 停止 Docker Desktop 2. 关闭 WSL 实例(若基于 WSL 2) 4. 导出原镜像数据 5.注销原实例 6. 导入数据到新路径 7. 设置 Docker Desktop 使用新路径(可选) 8. 启动 Docker Desktop …...
Compose 实践与探索十五 —— 自定义触摸
1、自定义触摸与一维滑动监测 之前我们在讲 Modifier 时讲过如下与手势检测相关的 Modifier: Modifier.clickable { } Modifier.combinedClickable { } Modifier.pointerInput {detectTapGestures { } }这里对以上内容就不再赘述了,直接去讲解更复杂的…...
第P8周:YOLOv5-C3模块实现
🍨 本文为🔗365天深度学习训练营中的学习记录博客 🍖 原作者:K同学啊 1. 模块组成 C3 模块由 卷积层(Convolutional layers)、激活函数(Activation functions) 和 残差连接&#…...
知识蒸馏:让大模型“瘦身“而不失智慧的魔术
引言:当AI模型需要"减肥" 在人工智能领域,一个有趣的悖论正在上演:大模型的参数规模每年以10倍速度增长,而移动设备的算力却始终受限。GPT-4的1750亿参数需要价值500万美元的GPU集群运行,但现实中的智能设备…...
`docker commit`和`docker tag`
1.docker commit docker commit是一个 Docker 命令,用于将一个正在运行的容器(Container)的状态提交为一个新的镜像(Image)。这类似于在版本控制系统中提交更改。 作用 • 当你对一个容器进行了修改(例如安…...
构建下一代AI Agent:自动化开发与行业落地全解析
1. 下一代AI Agent:概念与核心能力 核心能力描述技术支撑应用价值自主性独立规划与执行任务,无需持续人工干预决策树、强化学习、目标导向规划减少人工干预,提高任务执行效率决策能力评估多种方案并选择最优解决方案贝叶斯决策、多目标优化、…...
项目篇:模拟实现高并发内存池(2)
1.整体框架的设计 首先我们要来大概的梳理一下我们的高并发内存池的整体框架设计 在现代很多开发环境其实都是多核多线程,在申请内存的情况下,就必然会存在激烈的锁竞争问题。如果我们需要要实现内存池,必须要考虑以下几方面的问题。 1.性…...
PostgreSQL用SQL实现俄罗斯方块
📢📢📢📣📣📣 作者:IT邦德 中国DBA联盟(ACDU)成员,10余年DBA工作经验 Oracle、PostgreSQL ACE CSDN博客专家及B站知名UP主,全网粉丝10万 擅长主流Oracle、MySQL、PG、高斯…...
强大的AI网站推荐(第二集)—— V0.dev
网站:V0.dev 号称:前端开发神器,专为开发人员和设计师设计,能够使用 AI 生成 React 代码 博主评价:生成的UI效果太强大了,适合需要快速创建UI原型的设计师和开发者 推荐指数:🌟&…...
田间机器人幼苗视觉检测与护苗施肥装置研究(大纲)
田间机器人幼苗视觉检测与护苗施肥装置研究 基于多光谱视觉与精准施肥的农业机器人系统设计 第一章 绪论 1.1 研究背景与意义 农业智能化需求: 传统幼苗检测依赖人工,效率低且易遗漏弱苗/病苗施肥不精准导致资源浪费和环境污染 技术挑战:…...
vLLM 同时部署多个模型及调用
目录 一、单例加载多模型 (一) 原生多模型支持(vLLM ≥0.3.0) (二) 针对 vLLM 单实例部署多模型时 只有最后一个模型生效 的问题,结合实际测试和源码分析,以下是具体原因和解决方…...
LeetCode 2680.最大或值:位运算
【LetMeFly】2680.最大或值:位运算 力扣题目链接:https://leetcode.cn/problems/maximum-or/ 给你一个下标从 0 开始长度为 n 的整数数组 nums 和一个整数 k 。每一次操作中,你可以选择一个数并将它乘 2 。 你最多可以进行 k 次操作&#…...
Python——MySQL数据库编程
MySQL 是现在最流行的关系型数据库管理系统,在 WEB 开发中,MySQL 是最好的 RDBMS 应用软件之一。接下来,让我们快速掌握 python 使用 MySQL 的相关知识,并轻松使用 MySQL 数据库。 第1关:python数据库编程之创建数据库…...
AI 如何重塑数据湖的未来
在生成式 AI 与大模型技术飞速发展的今天,数据湖技术正迎来前所未有的挑战与机遇。海量非结构化数据的存储与处理、实时性与计算效率的平衡、高效存储的需求,已成为数据平台的核心难题。如何突破传统架构的局限,构建支持 AI 驱动的高效数据湖…...
C++ - 从零实现Json-Rpc框架-2(服务端模块 客户端模块 框架设计)
项⽬设计 本质上来讲,我们要实现的rpc(远端调⽤)思想上并不复杂,甚⾄可以说是简单,其实就是客⼾端想要完成某个任务的处理,但是这个处理的过程并不⾃⼰来完成,⽽是,将请求发送到服务…...
课程5. 迁移学习
课程5. 迁移学习 卷积神经网络架构ImageNet神经网络架构实践从 torchvision 加载模型在一个图像上测试预先训练的网络 迁移学习网络训练冻结层实践准备数据替换网络的最后一层冻结层网络训练获取测试样本的质量指标 课程计划: 流行的神经网络架构迁移学习 卷积神经…...
SATA(Serial Advanced Technology Attachment)详解
一、SATA的定义与核心特性 SATA(串行高级技术附件)是一种 用于连接存储设备(如硬盘、固态硬盘、光驱)的高速串行接口标准,取代了早期的PATA(并行ATA)。其核心特性包括: 高速传输&am…...
常用的 MyBatis 标签及其作用
MyBatis 是一个优秀的持久层框架,它通过 XML 或注解的方式将 Java 对象与数据库操作进行映射。在 MyBatis 的 XML 映射文件中,可以使用多种标签来定义 SQL 语句、参数映射、结果映射等。以下是一些常用的 MyBatis 标签及其作用: 1. 基本标签 …...
Blender配置渲染设置并输出动画
在Blender中,渲染设置和渲染动画的选项位于不同的面板中。以下是具体步骤: 渲染设置 渲染设置用于配置输出格式、分辨率、帧率等参数。 打开右侧的 属性面板(按 N 键可切换显示)。 点击 “输出属性” 选项卡(图标是…...
网络故障排查指南:分治法与排除法结合的分层诊断手册
目录 一、排查方法论:分治法与排除法的结合 1. 分治法(Divide and Conquer) 2. 排除法(Elimination) 二、分层诊断实战手册 1. 物理层排查(设备与线路) 硬件检测三板斧 运维经验 2. 网络…...
【万字总结】前端全方位性能优化指南(三)——GPU渲染加速、WebGPU、OffscreenCanvas多线程渲染
theme: condensed-night-purple 前言 当每秒60帧的流畅渲染遭遇百万级多边形场景,传统CPU绘图如同单车道上的赛车——即便引擎轰鸣,依然难逃卡顿困局。现代GPU加速技术将渲染任务从「单车道」扩展到「八车道」,本章以分层爆破、API革命、线程联邦为技术支柱,拆解如何通过G…...
报错 - redis - Unit redis.service could not be found.
报错: Unit redis.service could not be found.Could not connect to Redis at 127.0.0.1:6379: Connection refused解决方法: 检查状态、有必要的话 重新安装 Linux 上查看状态 systemctl status redis显示以下内容,代表正常服务 出现下面…...
Windows系统本地部署OpenManus对接Ollama调用本地AI大模型
文章目录 前言1. 环境准备1.1 安装Python1.2. 安装conda 2. 本地部署OpenManus2.1 创建一个新conda环境2.2 克隆存储库2.3 安装依赖环境 3. 安装Ollama4. 安装QwQ 32B模型5. 修改OpenManus配置文件6. 运行OpenManus7.通过网页使用OpenManus8. 安装内网穿透8.1 配置随机公网地址…...
【递归,搜索与回溯算法篇】- 名词解释
一. 递归 1. 什么是递归? 定义: 函数自己调用自己的情况关键点: ➀终止条件: 必须明确递归出口,避免无限递归 ➁子问题拆分: 问题需能分解成结构相同的更小的子问题缺点: ➀栈溢出风险&#x…...
【设计模式】装饰模式
六、装饰模式 装饰(Decorator) 模式也称为装饰器模式/包装模式,是一种结构型模式。这是一个非常有趣和值得学习的设计模式,该模式展现出了运行时的一种扩展能力,以及比继承更强大和灵活的设计视角和设计能力,甚至在有些场合下&am…...
c库、POSIX库、C++库、boost库之间的区别和联系
文章目录 一、区别1. 定义和来源2. 功能范围3. 可移植性4. 语言支持5. 维护和更新 二、联系1. 相互补充2. 部分功能重叠3. 共同促进编程发展4. 代码兼容性 三、总结 一、区别 1. 定义和来源 C 库函数:由 ANSI C 和 ISO C 标准定义,是 C 语言编程的基础…...
算法及数据结构系列 - 树
系列文章目录 算法及数据结构系列 - 二分查找 算法及数据结构系列 - BFS算法 算法及数据结构系列 - 动态规划 算法及数据结构系列 - 双指针 算法及数据结构系列 - 回溯算法 文章目录 树框架树遍历框架N叉树遍历框架 经典题型124.二叉树的最大路径和105.从前序与中序遍历序列构造…...
go安装lazydocker
安装 先安装go环境 https://blog.csdn.net/Yqha1/article/details/146430281?fromshareblogdetail&sharetypeblogdetail&sharerId146430281&sharereferPC&sharesourceYqha1&sharefromfrom_link 安装lazydocker go install github.com/jesseduffield/laz…...
《深度学习》——YOLOv3详解
文章目录 YOLOv3简介YOLOv3核心原理YOLOv3改进YOLOv3网络结构 YOLOv3简介 YOLOv3(You Only Look Once, version 3)是一种先进的实时目标检测算法,由 Joseph Redmon 和 Ali Farhadi 开发。它在目标检测领域表现出色,具有速度快、精…...
使用spring-ai-ollama访问本地化部署DeepSeek
创建SpringBoot工程,引入依赖 <?xml version"1.0" encoding"UTF-8"?> <project xmlns"http://maven.apache.org/POM/4.0.0"xmlns:xsi"http://www.w3.org/2001/XMLSchema-instance"xsi:schemaLocation"htt…...
Kafka消息自定义序列化
文章目录 1. 默认序列化2.自定义序列化3.示例4.自定义解序列化器 1. 默认序列化 在网络中发送数据都是以字节的方式,Kafka也不例外。Apache Kafka支持用户给broker发送各种类型的消息。它可以是一个字符串、一个整数、一个数组或是其他任意的对象类型。序列化器(se…...