如何在Service中获取SharedViewModel的LiveData存储的Clients列表
问题根本原因
ViewModel 的作用域是和 ViewModelStoreOwner 绑定的,你在 Activity/Fragment 中通过 activityViewModels() 获取的 SharedViewModel 依托的是 Activity 的 ViewModelStore,而你在 Service 中通过 ViewModelProvider(this) 获取的是和 Service 绑定的全新 ViewModel 实例,和 Activity 侧的不是同一个对象,自然拿不到已有的客户列表数据,观察也不会收到回调。
最优解决方案:引入单例数据层统一持有数据
通过全局单例的 Repository 层托管客户列表数据,不管是 ViewModel 还是 Service 都从该单例存取数据,保证全应用范围内数据统一。
步骤1:创建单例 Repository 类
class ClientRepository private constructor() { // 全局统一持有客户列表的LiveData val clientListLiveData = MutableLiveData<List<Client>>() companion object { @Volatile private var INSTANCE: ClientRepository? = null fun getInstance(): ClientRepository { return INSTANCE ?: synchronized(this) { INSTANCE ?: ClientRepository().also { INSTANCE = it } } } } // 对外提供更新列表的统一入口 fun updateClientList(newList: List<Client>) { clientListLiveData.postValue(newList) } }
步骤2:修改原有 SharedViewModel 逻辑
所有数据读写都走 Repository 层,保证数据变更全局同步:
class SharedViewModel : ViewModel() { private val repository = ClientRepository.getInstance() val clientList: LiveData<List<Client>> = repository.clientListLiveData // 原有新增客户逻辑修改为通过Repository更新 fun addClient(client: Client) { val currentList = clientList.value?.toMutableList() ?: mutableListOf() currentList.add(client) repository.updateClientList(currentList) } // 原有编辑、删除客户的逻辑同理,全部通过Repository更新数据 }
步骤3:修改 Service 逻辑,直接从 Repository 取数据
Service 没有默认的 LifecycleOwner,使用 observeForever 观察数据,记得销毁时取消观察避免内存泄漏:
class ClientSaveService : Service() { private val repository = ClientRepository.getInstance() private var latestClientList: List<Client>? = null // 全局声明Observer,方便后续取消注册 private val listObserver = Observer<List<Client>> { list -> latestClientList = list } override fun onCreate() { super.onCreate() // 注册数据观察者 repository.clientListLiveData.observeForever(listObserver) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { // 原有runBlocking没有实际作用可以直接删除,CountDownTimer回调默认运行在主线程 object : CountDownTimer(1000000000, 10000) { override fun onTick(millisUntilFinished: Long) { latestClientList?.let { list -> val jsonString = Gson().toJson(list) File("/storage/emulated/0/Documents/myFile.txt").printWriter().use { out -> out.write(jsonString) } Toast.makeText(applicationContext, "Saved", Toast.LENGTH_SHORT).show() Log.d("Tick", jsonString) } } override fun onFinish() { latestClientList?.let { list -> val jsonString = Gson().toJson(list) File("/storage/emulated/0/Documents/myFile.txt").printWriter().use { out -> out.write(jsonString) } Toast.makeText(applicationContext, "Saved", Toast.LENGTH_SHORT).show() } } }.start() return START_STICKY } override fun onDestroy() { super.onDestroy() // 移除观察者避免内存泄漏 repository.clientListLiveData.removeObserver(listObserver) } override fun onBind(intent: Intent?): IBinder? = null }
注意事项
- Android 10及以上版本直接写公共目录路径会有权限问题,建议改用 MediaStore 或者存储访问框架(SAF)实现文件写入
- 后台服务在高版本Android系统弹出Toast会被系统限制,如有必要请将服务改为前台服务
- 长时间运行的定时任务建议改用 WorkManager 实现,兼容性和稳定性更好
内容的提问来源于stack exchange,提问作者Андрей Ерошенко
相关产品推荐
相关产品推荐

