如何在LocationServiceUser中初始化lateinit变量mMap(GoogleMap)?
问题:lateinit property mMap has not been initialized
报错场景
在Service中尝试初始化lateinit var mMap: GoogleMap时触发该错误,Service负责获取定位数据并更新地图,但目前无法完成mMap的初始化。
相关代码
LocationServiceUser(Service类)
class LocationServiceUser : Service(), OnMapReadyCallback { private val serviceScopeUser = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var locationClient: LocationClientUser private val db = FirebaseFirestore.getInstance() private lateinit var mMap: GoogleMap private val markerOptions = MarkerOptions() override fun onBind(p0: Intent?): IBinder? { return null } override fun onCreate() { super.onCreate() locationClient = DefaultLocationClientUser( applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext) ) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_READING_DATA -> readingData() } return super.onStartCommand(intent, flags, startId) } override fun onMapReady(googleMap: GoogleMap) { mMap = googleMap } fun readingData() { locationClient.getLocation(5000L) .catch { e -> e.printStackTrace() } .onEach { location -> var lat = location.latitude var lng = location.longitude var currentLatLng = LatLng(lat, lng) db.collection("").document("") .addSnapshotListener { documentUser, error -> if (documentUser != null) { lat = documentUser.getGeoPoint("localizacao")?.latitude!! lng = documentUser.getGeoPoint("localizacao")?.longitude!! Log.d( "Response", "Dado lido a cada 5s: lat: $lat, lng: $lng}" ) currentLatLng = LatLng(lat, lng) } if (::mMap.isInitialized) { mMap.addMarker( MarkerOptions().position(currentLatLng) .title("Minha Localizacao atual") ) Log.d("Response", "Minha localizacao de agora: $currentLatLng") }else{ Log.d("Response", "readingData: Erro ao inicializar o mMap") } markerOptions.position(currentLatLng) mMap.addMarker(markerOptions) mMap.moveCamera(CameraUpdateFactory.newLatLng(currentLatLng)) } }.launchIn(serviceScopeUser) } override fun onDestroy() { super.onDestroy() serviceScopeUser.cancel() } companion object { const val ACTION_READING_DATA = "ACTION_READING_DATA" } }
UserFragment(使用Service的Fragment)
class UserFragment: Fragment() { private var _binding: FragmentUserBinding? = null private val binding: FragmentUserBinding get() = _binding!! private lateinit var locationClientUser: LocationClientUser override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentUserBinding.inflate(inflater,container,false) return binding.root } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) activity?.startService(Intent(context,LocationServices::class.java)) locationClientUser = DefaultLocationClientUser( requireContext().applicationContext, LocationServices.getFusedLocationProviderClient(requireContext().applicationContext) ) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.btnReadingData.setOnClickListener { Intent( requireContext().applicationContext, LocationServiceUser::class.java ).apply { action = LocationServiceUser.ACTION_READING_DATA activity?.startService(this) } } } }
疑问
已知GoogleMap通常在Fragment中初始化,但该Service持有需用于更新地图的定位数据,请问是否有方法在Service中完成mMap的初始化?
解决方案
首先明确:Service中无法直接初始化GoogleMap。原因是GoogleMap的初始化依赖于MapView或MapFragment这类UI组件,而Service是运行在后台的无UI组件,没有关联的View层级和UI生命周期,OnMapReadyCallback永远不会被触发,自然无法完成mMap的初始化。
正确的做法是拆分职责:
- Service负责数据获取:只处理定位请求、Firebase数据监听,把获取到的经纬度数据传递给UI层(Fragment)。
- Fragment负责地图操作:在Fragment中初始化GoogleMap,接收Service传递的数据,更新地图标记和相机位置。
1. 修改Service,通过Flow传递数据
将Service改为可绑定类型,用MutableSharedFlow发送定位数据:
class LocationServiceUser : Service() { private val serviceScopeUser = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var locationClient: LocationClientUser private val db = FirebaseFirestore.getInstance() // 用SharedFlow发送定位数据,支持多订阅者 private val locationFlow = MutableSharedFlow<LatLng>(replay = 1) override fun onBind(intent: Intent): IBinder { return LocalBinder() } inner class LocalBinder : Binder() { fun getService(): LocationServiceUser = this@LocationServiceUser } override fun onCreate() { super.onCreate() locationClient = DefaultLocationClientUser( applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext) ) } override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { when (intent?.action) { ACTION_READING_DATA -> readingData() } return START_STICKY } fun readingData() { locationClient.getLocation(5000L) .catch { e -> e.printStackTrace() } .onEach { location -> var lat = location.latitude var lng = location.longitude var currentLatLng = LatLng(lat, lng) db.collection("").document("") .addSnapshotListener { documentUser, error -> if (documentUser != null) { lat = documentUser.getGeoPoint("localizacao")?.latitude!! lng = documentUser.getGeoPoint("localizacao")?.longitude!! currentLatLng = LatLng(lat, lng) Log.d("Response", "Dado lido a cada 5s: lat: $lat, lng: $lng}") } // 发送数据到Flow serviceScopeUser.launch { locationFlow.emit(currentLatLng) } } }.launchIn(serviceScopeUser) } // 对外暴露Flow,供Fragment订阅 fun getLocationFlow(): SharedFlow<LatLng> = locationFlow override fun onDestroy() { super.onDestroy() serviceScopeUser.cancel() } companion object { const val ACTION_READING_DATA = "ACTION_READING_DATA" } }
2. 修改Fragment,初始化地图并监听数据
在Fragment中初始化MapView,绑定Service并订阅数据更新地图:
class UserFragment: Fragment(), OnMapReadyCallback { private var _binding: FragmentUserBinding? = null private val binding: FragmentUserBinding get() = _binding!! private lateinit var mMap: GoogleMap private var locationService: LocationServiceUser? = null private var serviceBound = false private val serviceConnection = object : ServiceConnection { override fun onServiceConnected(className: ComponentName, service: IBinder) { val binder = service as LocationServiceUser.LocalBinder locationService = binder.getService() serviceBound = true // 订阅定位数据,在UI线程更新地图 locationService?.getLocationFlow()?.onEach { latLng -> requireActivity().runOnUiThread { updateMap(latLng) } }?.launchIn(viewLifecycleOwner.lifecycleScope) } override fun onServiceDisconnected(arg0: ComponentName) { serviceBound = false locationService = null } } override fun onCreateView( inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle? ): View { _binding = FragmentUserBinding.inflate(inflater,container,false) // 初始化布局中的MapView binding.mapView.onCreate(savedInstanceState) binding.mapView.getMapAsync(this) return binding.root } override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // 绑定Service Intent(requireContext(), LocationServiceUser::class.java).also { intent -> requireActivity().bindService(intent, serviceConnection, Context.BIND_AUTO_CREATE) } } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) binding.btnReadingData.setOnClickListener { Intent(requireContext(), LocationServiceUser::class.java).apply { action = LocationServiceUser.ACTION_READING_DATA requireActivity().startService(this) } } } override fun onMapReady(googleMap: GoogleMap) { mMap = googleMap // 基础地图设置 mMap.uiSettings.isZoomControlsEnabled = true } private fun updateMap(latLng: LatLng) { if (::mMap.isInitialized) { // 清除旧标记(可选) mMap.clear() // 添加新标记 mMap.addMarker(MarkerOptions().position(latLng).title("Minha Localizacao atual")) // 移动相机到目标位置 mMap.moveCamera(CameraUpdateFactory.newLatLngZoom(latLng, 15f)) Log.d("Response", "Minha localizacao de agora: $latLng") } } // 管理MapView的生命周期 override fun onStart() { super.onStart() binding.mapView.onStart() } override fun onResume() { super.onResume() binding.mapView.onResume() } override fun onPause() { super.onPause() binding.mapView.onPause() } override fun onStop() { super.onStop() binding.mapView.onStop() } override fun onDestroyView() { super.onDestroyView() binding.mapView.onDestroy() _binding = null } override fun onDestroy() { super.onDestroy() if (serviceBound) { requireActivity().unbindService(serviceConnection) serviceBound = false } } }
关键说明
- 拆分职责后,Service专注于后台数据逻辑,Fragment负责UI渲染,符合Android组件设计规范。
- 使用
ServiceConnection绑定Service,Fragment可以安全获取Service实例并订阅数据;也可以用LiveData替代SharedFlow,更贴合Android生命周期管理。 - 必须在Fragment中管理
MapView的完整生命周期,确保onMapReady正常触发,完成mMap的初始化。
内容的提问来源于stack exchange,提问作者Matheus Finamor
相关产品推荐
相关产品推荐

