如何将LocationService中的位置数据传递至HomeViewModel?
问题描述
尝试将LocationService中的位置数据传递至HomeViewModel,使用Dagger注入后值始终为null,附上LocationService代码:
class LocationService: Service() { private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var locationClient: LocationClient private val _locationLiveData = MutableLiveData<Pair<String, String>>() val locationLiveData: LiveData<Pair<String, String>> = _locationLiveData override fun onBind(p0: Intent?): IBinder? { return null } override fun onCreate() { super.onCreate() locationClient = DefaultLocationClient( applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext) ) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when(intent?.action) { ACTION_START -> start() ACTION_STOP -> stop() } return super.onStartCommand(intent, flags, startId) } private fun start() { val notification = NotificationCompat.Builder(this, CHANNEL_ID) .setContentTitle("Tracking location...") .setContentText("Location: null") .setSmallIcon(R.drawable.ic_launcher_background) .setOngoing(true) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager locationClient.getLocationUpdates(5000L) .catch { e-> e.printStackTrace() } .onEach { location -> val lat = location.latitude.toString() val long = location.longitude.toString() _locationLiveData.postValue(Pair(lat, long)) Log.d("LocationT", "LocationService: " + (locationLiveData.value?.first ?: "Dupa0")) val updatedNotification = notification.setContentText( "Location: $lat, $long" ) notificationManager.notify(1, updatedNotification.build()) }.launchIn(serviceScope) startForeground(1, notification.build()) } private fun stop() { stopForeground(true) stopSelf() } override fun onDestroy() { super.onDestroy() serviceScope.cancel() } companion object { const val ACTION_START = "ACTION_START" const val ACTION_STOP = "ACTION_STOP" } }
解决方案
问题核心
当前LocationService是非绑定服务(onBind返回null),且内部的locationLiveData是局部实例,Dagger无法将同一个实例注入到HomeViewModel——要么注入的是新的空实例,要么ViewModel根本拿不到Service中正在更新的那个LiveData。
下面是三种可行的解决方式:
方式一:将Service改为绑定服务,直接暴露LiveData
修改LocationService为可绑定服务,通过Binder让ViewModel所在的组件获取到Service实例,进而观察其LiveData:
- 修改
LocationService的Binder实现:
class LocationService: Service() { // ... 原有代码不变 ... // 自定义Binder,暴露获取LiveData的方法 private val binder = LocalBinder() inner class LocalBinder: Binder() { fun getService(): LocationService = this@LocationService } override fun onBind(p0: Intent?): IBinder? { return binder } // ... 原有代码不变 ... }
- 在HomeViewModel关联的Activity/Fragment中绑定Service,传递LiveData给ViewModel:
class HomeActivity : AppCompatActivity() { private lateinit var serviceConnection: ServiceConnection private var locationService: LocationService? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) val homeViewModel = ViewModelProvider(this)[HomeViewModel::class.java] serviceConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { val binder = service as LocationService.LocalBinder locationService = binder.getService() // 观察Service的LiveData,传递给ViewModel locationService?.locationLiveData?.observe(this@HomeActivity) { locationPair -> homeViewModel.updateLocation(locationPair) } } override fun onServiceDisconnected(arg0: ComponentName) { locationService = null } } // 绑定服务 Intent(this, LocationService::class.java).also { intent -> bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) } } override fun onDestroy() { super.onDestroy() unbindService(serviceConnection) } }
- 在
HomeViewModel中添加接收数据的方法:
class HomeViewModel: ViewModel() { private val _location = MutableLiveData<Pair<String, String>>() val location: LiveData<Pair<String, String>> = _location fun updateLocation(locationPair: Pair<String, String>) { _location.postValue(locationPair) } }
方式二:使用全局数据仓库(推荐)
创建单例的LocationRepository,统一管理位置数据,LocationService更新仓库数据,HomeViewModel注入仓库并观察:
- 定义
LocationRepository:
class LocationRepository @Inject constructor() { private val _locationData = MutableLiveData<Pair<String, String>>() val locationData: LiveData<Pair<String, String>> = _locationData fun updateLocation(lat: String, long: String) { _locationData.postValue(Pair(lat, long)) } }
- 在Dagger Module中提供单例的
LocationRepository:
@Module @InstallIn(SingletonComponent::class) object AppModule { @Provides @Singleton fun provideLocationRepository(): LocationRepository { return LocationRepository() } }
- 修改
LocationService,注入LocationRepository并更新数据:
class LocationService: Service() { @Inject lateinit var locationRepository: LocationRepository private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var locationClient: LocationClient override fun onCreate() { super.onCreate() // 注入Repository DaggerAppComponent.create().inject(this) locationClient = DefaultLocationClient( applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext) ) } private fun start() { // ... 原有代码不变 ... locationClient.getLocationUpdates(5000L) .catch { e-> e.printStackTrace() } .onEach { location -> val lat = location.latitude.toString() val long = location.longitude.toString() // 更新仓库数据 locationRepository.updateLocation(lat, long) Log.d("LocationT", "LocationService: $lat, $long") // ... 原有通知代码不变 ... }.launchIn(serviceScope) // ... 原有代码不变 ... } // ... 原有代码不变 ... }
- 在
HomeViewModel中注入LocationRepository并观察:
class HomeViewModel @Inject constructor( private val locationRepository: LocationRepository ): ViewModel() { val location: LiveData<Pair<String, String>> = locationRepository.locationData }
方式三:直接注入LiveData实例(需注意生命周期)
让Dagger提供单例的MutableLiveData,同时注入到LocationService和HomeViewModel:
- 在Dagger Module中提供单例LiveData:
@Module @InstallIn(SingletonComponent::class) object AppModule { @Provides @Singleton fun provideLocationLiveData(): MutableLiveData<Pair<String, String>> { return MutableLiveData() } }
- 修改
LocationService注入LiveData:
class LocationService: Service() { @Inject lateinit var _locationLiveData: MutableLiveData<Pair<String, String>> val locationLiveData: LiveData<Pair<String, String>> = _locationLiveData // ... 原有代码不变 ... private fun start() { // ... 原有代码不变 ... locationClient.getLocationUpdates(5000L) .catch { e-> e.printStackTrace() } .onEach { location -> val lat = location.latitude.toString() val long = location.longitude.toString() _locationLiveData.postValue(Pair(lat, long)) // ... 原有通知代码不变 ... }.launchIn(serviceScope) // ... 原有代码不变 ... } // ... 原有代码不变 ... }
- 在
HomeViewModel中注入并观察:
class HomeViewModel @Inject constructor( val locationLiveData: LiveData<Pair<String, String>> ): ViewModel() { // 直接使用注入的LiveData即可 }
注意事项
- 绑定服务方式需注意组件生命周期,避免内存泄漏
- 全局仓库方式是Jetpack推荐的架构模式,解耦性更好
- 直接注入LiveData需确保LiveData的生命周期由ViewModel或单例管理,避免意外销毁
内容的提问来源于stack exchange,提问作者xMaster
相关产品推荐
相关产品推荐

