You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.01 12:45:35