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

Android使用OkHttp对接AVS无DownchannelStream响应问题

解决Android OkHttp与AVS HTTP/2 Downchannel流读取问题

首先先帮你理清几个正常现象,避免你误解:

  • SynchronizeState和Recognize请求返回204是正常的:AVS的/events端点处理这类事件时,不会在请求的响应体里返回内容,而是通过你之前建立的Downchannel长连接异步推送指令。所以syncResp和speechResp的空响应完全符合预期,不用纠结这部分。

接下来针对你遇到的核心问题,拆解分析并给出解决方案:

1. 为什么response.body().string()会阻塞Downchannel处理?

Downchannel是HTTP/2长连接,AVS会持续保持连接并在有指令时推送数据,流不会主动关闭。response.body().string()会尝试读取整个流直到结束,这会导致线程无限阻塞,所以你看到downChannelResp后续代码无法执行是必然结果——永远不要用string()读取长连接的响应体。

2. 音频请求的Content-Disposition错误

在testRecognizeEventAVS方法中,你给音频部分的Content-Disposition设置成了name="metadata",这是关键错误!AVS要求multipart请求里的音频部分必须是name="audio",否则AVS无法正确解析你的语音请求,自然不会推送任何指令到Downchannel。

修正这部分代码:

// Audio Part
Map<String, String> audioHeaders = new HashMap<String, String>();
audioHeaders.put("Content-Disposition", "form-data; name=\"audio\""); // 把metadata改成audio
MultipartBody.Part audioPart = MultipartBody.Part.create(Headers.of(audioHeaders), RequestBody.create(createTestFile(), AUDIO_TYPE));
partList.add(audioPart);

3. Downchannel流的正确读取方式

你当前用while (!bufferedSource.exhausted())的方式会阻塞线程,因为exhausted()会等待流结束。正确的做法是用异步线程持续监听流,读取AVS推送的换行分隔的JSON指令:

替换你的processResponse中针对Downchannel的处理逻辑,改成如下:

private void processDownChannelResponse(Response response) {
    // 开启单独线程读取长连接,避免阻塞OkHttp的回调线程
    new Thread(() -> {
        BufferedSource source = response.body().source();
        Buffer buffer = new Buffer();
        try {
            while (!Thread.currentThread().isInterrupted()) {
                // 尝试读取数据,无数据时会短暂等待,不会无限阻塞
                long bytesRead = source.read(buffer, 8192);
                if (bytesRead == -1) {
                    Log.d("downChannelResp", "Downchannel连接已关闭");
                    break;
                }
                // AVS的Downchannel指令是每行一个JSON,所以按行读取
                String rawDirective = buffer.readUtf8Line();
                if (rawDirective != null && !rawDirective.trim().isEmpty()) {
                    Log.d("downChannelResp", "收到指令: " + rawDirective);
                    // 这里可以将rawDirective解析成JSON对象,处理具体的指令(比如Speak、Play等)
                }
            }
        } catch (IOException e) {
            Log.e("downChannelResp", "读取Downchannel失败", e);
        } finally {
            // 关闭响应体,释放资源
            try {
                response.body().close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }).start();
}

然后在Downchannel的回调中调用这个方法:

@Override
public void onResponse(@NotNull Call call, @NotNull Response response) throws IOException {
    Log.d("downChannelResp", "Down channel recieved! Test 1");
    processDownChannelResponse(response);
    Log.d("downChannelResp", "Down channel recieved! Test 2");
    responseDirective = response;
}

4. 优化OkHttpClient配置,确保长连接稳定

为了让Downchannel连接保持活跃,需要调整OkHttpClient的超时设置:

OkHttpClient downChannelClient = new OkHttpClient.Builder()
        .connectionPool(new ConnectionPool(5, 5, TimeUnit.MINUTES)) // 维持连接池,复用连接
        .readTimeout(0, TimeUnit.MILLISECONDS) // 读取超时设为0,禁用超时(因为长连接会一直保持)
        .writeTimeout(30, TimeUnit.SECONDS)
        .connectTimeout(30, TimeUnit.SECONDS)
        .build();

最后验证步骤:

  1. 修正音频部分的Content-Disposition
  2. 用新的Downchannel读取逻辑
  3. 确保access token有效且权限正确
  4. 发送Recognize请求后,应该能在Downchannel的日志里看到AVS推送的指令(比如SpeechSynthesizer的Speak指令)

内容的提问来源于stack exchange,提问作者W --

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.05.09 11:37:49