Android流式接口请求实践

最近做项目,接入GPT,接口是流式返回的,于是对流式接口请求做了下总结。项目中采用的OKHttp实现网络请求、
接口中,数据是一行一行返回的,每行数据的返回格式如下:

{
	 "code": 0,
	 "message": "",
	 "data": "{\"text\":\"\",\"auxiliary_tips\":[]}"
}

流式接口应该一行一行处理,因为返回的内容数据会被拆成一段一段的,放到text字段下,我们的业务比较复杂,在返回的数据中还会有一些特殊标识需要解析,是否需要展示按钮之类的,数据如下:

"此次GPT生成完成!<_action_data_> {\"action\": \"confirm\"}"

因为后面的标识可能会被GPT拆成两个,因此需要拼装后进行解析。

核心代码如下:

companion object {
     const val MSG_END_TAG = "<_action_data_>"
     const val DATA_DONE_TAG = "data: [[DONE]]"
     const val RETRY_TAG = "retry: "
 }
 
suspend fun requestByStream(
   		ur: String,
        taskType: String? = null,
        content: String? = null,
        onStart: () -> Unit,
        onEnd: (String?) -> Unit,
        onNetworkError: (Int?, String?) -> Unit,
        onError: ((Throwable) -> Unit)? = null,
        onCollect: (String?, Choices?, Int?, String?) -> Unit,
    ) {
        val body = JsonUtils.toJson(GenerateBody(task_type = taskType, content = content))
            .toRequestBody()
        val request =
            Request.Builder().url(url).post(body)
                .build()
        val call = RetrofitClient.client.newCall(request)
        val gson = Gson()
        withContext(Dispatchers.IO) {
            val response = call.execute()
            if (response.isSuccessful) {
                try {
                    response.body?.byteStream()?.bufferedReader()?.use { reader ->
                        val originData = StringBuilder()
                        var line: String?
                        while (reader.readLine().also { line = it } != null) {
                            try {
                                if (line?.startsWith("data: ") == true) {
                                    if (line?.startsWith(DATA_DONE_TAG) == true) {
                                        withContext(Dispatchers.Main) {
                                            onEnd(originData.toString())
                                        }
                                    } else {
                                        val resp = gson.fromJson<BaseResponse<Choices>>(
                                            line?.replaceFirst("data: ", ""),
                                            object : TypeToken<BaseResponse<Choices>>() {}.type
                                        )
                                        resp.data?.choices?.let {
                                            originData.append(it)
                                            val parts = originData.split(MSG_END_TAG)
                                            if (parts.size == 2) {
                                                resp.data?.choices = parts[0]
                                                runCatching {
                                                    val data = gson.fromJson(
                                                        parts[1], Choices::class.java
                                                    )
                                                    resp.data?.action = data.action
                                                    resp.data?.suggest_answers =
                                                        data.suggest_answers
                                                }
                                            } else {
                                                resp.data?.choices = parts[0]
                                            }
                                        }

                                        withContext(Dispatchers.Main) {
                                            onCollect(
                                                originData.toString(),
                                                resp.data,
                                                resp.code,
                                                resp.msg
                                            )
                                        }
                                    }
                                } else {
                                    withContext(Dispatchers.Main) {
                                        if (line?.startsWith(RETRY_TAG) == true) {
                                            onStart()
                                        }
                                    }
                                }
                            } catch (e: Exception) {
                                e.printStackTrace()
                            }
                        }
                    }
                } catch (e: IOException) {
                    e.printStackTrace()
                    withContext(Dispatchers.Main) {
                        onError?.invoke(e)
                    }
                }
            } else {
                withContext(Dispatchers.Main) {
                    onNetworkError(response.code, response.message)
                }
            }
        }
    }

采用Retrofit的@Streaming注解

Retrofit框架@Streaming注解返回ResponseBody,代码如下:

@Streaming
@GET("接口地址")
suspend fun requestByStream(): ResponseBody

接下来就是利用**ResponseBody.byteStream()**方法流式传输数据,不用等待整个响应返回。

感谢大家的支持,如有错误请指正,如需转载请标明原文出处!

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值