Android:子线程服务完成后如何触发主线程函数?
问题描述
我需要实现以下功能:
- 开启Switch后,在子线程启动LocationService(此功能目前正常运行);
- 当该服务执行成功后,触发主线程中的
InitiateMessage()函数。
我不介意是由服务直接调用该函数,还是服务向主线程返回"成功"值后再触发该函数。
关键代码片段
主线程代码(SendNotif Activity)
class SendNotif : AppCompatActivity() { val context = this private lateinit var Switch: Switch // Start LocationService when the switch is on Switch.setOnCheckedChangeListener { buttonView, isChecked -> if (isChecked) { Toast.makeText(context, "Starting LocationService", Toast.LENGTH_SHORT).show() Intent(applicationContext, LocationService::class.java).apply { action = LocationService.ACTION_START startService(this) } } else { Toast.makeText(context, "Stopping LocationService", Toast.LENGTH_SHORT).show() Intent(applicationContext, LocationService::class.java).apply { action = LocationService.ACTION_STOP startService(this) } } } fun InitiateMessage() { // 这个函数需要在LocationService执行成功后启动 } }
LocationService代码(服务成功后需启动InitiateMessage())
class LocationService: Service() { private val serviceScope = CoroutineScope(SupervisorJob() + Dispatchers.IO) private lateinit var locationClient: LocationClient var lat = 0.0F var long = 0.0F override fun onBind(p0: Intent?): IBinder? { return null } override fun onCreate() { super.onCreate() locationClient = DefaultLocationClient( applicationContext, LocationServices.getFusedLocationProviderClient(applicationContext) ) } // Start or stop the service 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() { // Starting notification val notification = NotificationCompat.Builder(this, "location") .setContentTitle("Tracking location...") .setContentText("Location: null") .setSmallIcon(R.drawable.ic_launcher_background) // Can't swipe this notification away .setOngoing(true) val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager // Starting the location updates locationClient // Every 10 seconds .getLocationUpdates(10000L) .catch { e -> e.printStackTrace() } .onEach { location -> lat = location.latitude.toString().toFloat() // .takeLast(3) // taking only the last 3 digits long = location.longitude.toString().toFloat() // .takeLast(3) val updatedNotification = notification.setContentText( "Location: ($lat, $long)" ) // notificationManager.notify(1, updatedNotification.build()) // Geofence MyGeofence(lat, long) } .launchIn(serviceScope) // startForeground(1, notification.build()) } private fun stop() { // Stopping the notification stopForeground(true) // Stopping the location service stopSelf() } override fun onDestroy() { super.onDestroy() serviceScope.cancel() } companion object { const val ACTION_START = "ACTION_START" const val ACTION_STOP = "ACTION_STOP" } fun MyGeofence(lat : Float, long : Float){ val context = this var db = DataBaseHandler(context) var data = db.readData() // Setting the accuracy of the geofence val acc = 2 val safelat : Double = data.get(0).LocLat.toFloat().round(acc) val safelong = data.get(0).LocLong.toFloat().round(acc) // .take(acc).take(acc) val h = Handler(context.mainLooper) if(safelat == lat.toFloat().round(acc) && safelong == long.toFloat().round(acc)){ h.post(Runnable { Toast.makeText(context, "You have reached your safe refuge! " + lat.toFloat().round(acc) + " " + long.toFloat().round(acc), Toast.LENGTH_LONG).show() }) // ToDo: Right hereafter the function InitiateMessage() should start } else{ h.post(Runnable { Toast.makeText(context, "You are still in great danger! " + lat.toFloat().round(acc) + " " + long.toFloat().round(acc), Toast.LENGTH_LONG).show() }) } } fun Float.round(decimals: Int): Double { var multiplier = 1.0 repeat(decimals) { multiplier *= 10 } return round(this * multiplier) / multiplier } }
遇到的错误
我曾尝试使用Looper实现,但出现以下错误:
java.lang.RuntimeException: Can't create handler inside thread Thread[DefaultDispatcher-worker-1,5,main] that has not called Looper.prepare()
我认为通过服务返回值的方式会更简单,想了解如何实现这一方式,并通过返回值触发主线程的InitiateMessage()函数。
解决方案
针对Started Service和Activity的通信需求,推荐两种实用方案:
方案一:广播接收器(直接简单)
通过广播实现服务向主线程发送成功信号,Activity接收后触发目标函数。
1. 在Activity中注册广播接收器
在SendNotif中添加广播接收器的注册和注销逻辑:
class SendNotif : AppCompatActivity() { private lateinit var locationSuccessReceiver: BroadcastReceiver override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... 原有初始化代码(如绑定Switch) // 初始化接收器:收到信号就调用InitiateMessage locationSuccessReceiver = object : BroadcastReceiver() { override fun onReceive(context: Context?, intent: Intent?) { InitiateMessage() } } // 注册接收器,指定接收的广播动作 val filter = IntentFilter("com.yourpackage.LOCATION_SUCCESS") registerReceiver(locationSuccessReceiver, filter) } override fun onDestroy() { super.onDestroy() // 销毁时注销,避免内存泄漏 unregisterReceiver(locationSuccessReceiver) } // ... 原有Switch监听和InitiateMessage函数 }
2. 在LocationService中发送广播
在MyGeofence的成功分支里添加广播发送逻辑:
fun MyGeofence(lat : Float, long : Float){ // ... 原有逻辑代码 if(safelat == lat.toFloat().round(acc) && safelong == long.toFloat().round(acc)){ h.post(Runnable { Toast.makeText(context, "You have reached your safe refuge! " + lat.toFloat().round(acc) + " " + long.toFloat().round(acc), Toast.LENGTH_LONG).show() }) // 发送成功广播 val successIntent = Intent("com.yourpackage.LOCATION_SUCCESS") sendBroadcast(successIntent) } else { // ... 原有else逻辑 } }
注意:将
com.yourpackage.LOCATION_SUCCESS替换为你自己的包名前缀,避免和其他应用广播冲突。
方案二:LiveData(现代化架构方案)
用Jetpack的LiveData实现组件间通信,无需手动注册注销,更符合现代Android开发规范。
1. 创建单例LiveData
object LocationSuccessLiveData : MutableLiveData<Boolean>()
2. 在Activity中观察LiveData
class SendNotif : AppCompatActivity() { override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) // ... 原有初始化代码 // 观察LiveData,值变化时触发函数 LocationSuccessLiveData.observe(this) { isSuccess -> if(isSuccess) { InitiateMessage() // 重置状态,避免重复触发 LocationSuccessLiveData.postValue(false) } } } // ... 原有Switch监听和InitiateMessage函数 }
3. 在LocationService中更新LiveData
fun MyGeofence(lat : Float, long : Float){ // ... 原有逻辑代码 if(safelat == lat.toFloat().round(acc) && safelong == long.toFloat().round(acc)){ // ... 原有Toast逻辑 // 更新LiveData通知Activity LocationSuccessLiveData.postValue(true) } else { // ... 原有else逻辑 } }
内容的提问来源于stack exchange,提问作者Qualla
相关产品推荐
相关产品推荐

