从使用协程进行网络调用的服务内部发送广播

Posted

技术标签:

【中文标题】从使用协程进行网络调用的服务内部发送广播【英文标题】:sendBroadcast from inside Service which uses Coroutine for network call 【发布时间】:2020-09-09 15:01:48 【问题描述】:

我有一个 JobIntentService,它应该进行 API 调用并在结果可用后进行广播。

我正在使用协程通过 Retrofit 进行网络调用。 但是,如果我在 CoroutineScope 中执行 sendBroadcast,它不会触发 BroadcastReceiver

这是我的服务代码 -

MyService.kt

class MyService : JobIntentService() 

    private val TAG = MyService::class.java.simpleName
    private var databaseHelper: DatabaseHelper = DatabaseHelper(this)
    private var imageFetcher: ImageFetcher = ImageFetcher(this)
    private var imageSaver: ImageSaver = ImageSaver(this)
    private val receiver = ServiceBroadcastReceiver()


    override fun onHandleWork(intent: Intent) 
        val filter = IntentFilter()
        filter.addAction("ACTION_FINISHED_SERVICE")
        registerReceiver(receiver, filter)

        when (intent.action) 
            "ACTION_FETCH_FROM_API" -> 
                handleFetchFromAPI()
            
        
    

    override fun onDestroy() 
        super.onDestroy()
        unregisterReceiver(receiver)
    

    private fun handleFetchFromAPI() 
        val API = ServiceBuilder.buildWebService(WebService::class.java)
        CoroutineScope(IO).launch 
            try 
                var apiSuccess : Boolean = false
                val apiResponse = API.getImageOfTheDay()
                if (apiResponse.isSuccessful) 
                    apiSuccess = true
                    val imageAPIResponse = apiResponse.body()
                    val bitmap = imageFetcher.getImageBitmapFromURL(imageAPIResponse.url)
                    val filePath = imageSaver.saveBitmapToFile(bitmap, "image.jpg")
                    withContext(Main) 
                        databaseHelper.saveImageInRoom(imageAPIResponse, filePath)
                    
                
                if(apiSuccess)
                    val broadCastIntent = Intent()
                    broadCastIntent.action = "ACTION_FINISHED_SERVICE"
                    sendBroadcast(broadCastIntent)
                 
             catch (exception: Exception) 
                Log.d(TAG, "Exception occurred $exception.message")
            
        
    

    companion object 
        private const val JOB_ID = 2
        @JvmStatic
        fun enqueueWork(context: Context, intent: Intent) 
            enqueueWork(context, MyService::class.java, JOB_ID, intent)
        
    

ServiceBroadcastReceiver.kt 类 ServiceBroadcastReceiver : BroadcastReceiver()

private val TAG = ServiceBroadcastReceiver::class.java.simpleName
private lateinit var _mNotificationManager: NotificationManager
private val _notificationId = 0
private val _primaryChannelId = "primary_notification_channel"

override fun onReceive(context: Context, intent: Intent) 
    _mNotificationManager = context.getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager
    when (intent.action) 
        "ACTION_FINISHED_SERVICE" -> 
            deliverNotification(context)
        
    


private fun deliverNotification(context: Context) 
    val contentIntent = Intent(context, MainActivity::class.java)
    val pendingIntent = PendingIntent.getActivity(context,_notificationId,contentIntent,
            PendingIntent.FLAG_UPDATE_CURRENT)
    val builder = NotificationCompat.Builder(context,_primaryChannelId)
    builder.setSmallIcon(R.mipmap.ic_launcher)
    builder.setContentTitle("Hi There")
    builder.setContentText("Service finished its job")
    builder.setContentIntent(pendingIntent)
    builder.priority = NotificationCompat.PRIORITY_HIGH
    builder.setAutoCancel(true)
    builder.setDefaults(NotificationCompat.DEFAULT_ALL)
    _mNotificationManager.notify(_notificationId,builder.build())

getImageOfTheDay() 是 WebService.kt 中的挂起函数

@Headers("Content-Type: application/json")
@GET("/v1/getImageOfTheDay")
suspend fun getImageOfTheDay(): Response<ImageAPIResponse>

如果我将代码移到协程范围之外,则广播会正确发送。 我该如何解决这个问题?

【问题讨论】:

【参考方案1】:

你不应该在这里使用协程。 onHandleWork 方法在后台线程上调用,从该方法返回表示工作已完成并且可以终止服务。

当您使用 launch 启动协程时,onHandleWork 立即返回并且您的服务终止。

您应该直接调用您的网络 API,而不是在协程中,因为 JobIntentService 已经设计为以这种方式工作。

【讨论】:

如何直接调用 API.getImageOfTheDay() 方法?它是一个带有挂起修饰符的函数,因此必须在协程中调用。 在这种情况下将其包裹在runBlocking中。

以上是关于从使用协程进行网络调用的服务内部发送广播的主要内容,如果未能解决你的问题,请参考以下文章

为啥从 ajax 调用 web 服务会引发内部服务器 500 错误?

从Intent服务发送短信 - 无法接收广播以检查是否发送了短信

网络编程-协程-1迭代器

Android面试每日一题:BroadcastReceiver 与 LocalBroadcastReceiver 有什么区别?

Android面试每日一题:BroadcastReceiver 与 LocalBroadcastReceiver 有什么区别?

单线程多任务异步协程