Handler+ExecutorService(线程池)+MessageQueue模式+缓存模式

本文通过实例详细介绍了Android中使用线程池和缓存进行异步加载图片的方法,包括Handler+Runnable、Handler+Thread+Message及Handler+ExecutorService等模式,并最终封装成易于使用的类。

android线程池的理解,晚上在家无事 预习了一下android异步加载的例子,也学习到了一个很重要的东东 那就是线程池+缓存  下面看他们的理解

[size=1.8em]Handler+Runnable模式

我们先看一个并不是异步线程加载的例子,使用 Handler+Runnable模式。

这里为何不是新开线程的原因请参看这篇文章:Android Runnable 运行在那个线程 这里的代码其实是在UI 主线程中下载图片的,而不是新开线程。

我们运行下面代码时,会发现他其实是阻塞了整个界面的显示,需要所有图片都加载完成后,才能显示界面。

代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
package ghj1976.AndroidTest;
 
import java.io.IOException;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;
 
public class MainActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
                loadImage("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
                loadImage(<img class='\"zoom\"' id='\"aimg_zYanh\"' onmouseover='\"img_onmouseoverfunc(this)\"' onclick='\"zoom(this,' alt='\"\"' border='\"0\"' lazyloadthumb='\"1\"' file='\"http://www.chinatelecom.com.cn/images/logo_new.gif\"' 0)\"="" 0,="" this.src,="">",
                                R.id.imageView2);
                loadImage("http://cache.soso.com/30d/img/web/logo.gif, R.id.imageView3);
                loadImage("http://csdnimg.cn/www/images/csdnindex_logo.gif",
                                R.id.imageView4);
                loadImage("http://images.cnblogs.com/logo_small.gif",
                                R.id.imageView5);
        }
 
        private Handler handler = new Handler();
 
        private void loadImage(final String url, final int id) {
                handler.post(new Runnable() {
                        public void run() {
                                Drawable drawable = null;
                                try {
                                        drawable = Drawable.createFromStream(
                                                        new URL(url).openStream(), "image.gif");
                                } catch (IOException e) {
                                        Log.d("test", e.getMessage());
                                }
                                if (drawable == null) {
                                        Log.d("test", "null drawable");
                                } else {
                                        Log.d("test", "not null drawable");
                                }
                                // 为了测试缓存而模拟的网络延时
                                SystemClock.sleep(2000);
                                ((ImageView) MainActivity.this.findViewById(id))
                                                .setImageDrawable(drawable);
                        }
                });
        }
}



Handler+Thread+Message模式

这种模式使用了线程,所以可以看到异步加载的效果。

核心代码:

代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
package ghj1976.AndroidTest;
 
import java.io.IOException;
import java.net.URL;
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;
 
public class MainActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
                loadImage2("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
                loadImage2("http://www.chinatelecom.com.cn/images/logo_new.gif",
                                R.id.imageView2);
                loadImage2("http://cache.soso.com/30d/img/web/logo.gif", R.id.imageView3);
                loadImage2("http://csdnimg.cn/www/images/csdnindex_logo.gif",
                                R.id.imageView4);
                loadImage2("http://images.cnblogs.com/logo_small.gif",
                                R.id.imageView5);
        }
 
        final Handler handler2 = new Handler() {
                @Override
                public void handleMessage(Message msg) {
                        ((ImageView) MainActivity.this.findViewById(msg.arg1))
                                        .setImageDrawable((Drawable) msg.obj);
                }
        };
 
        // 采用handler+Thread模式实现多线程异步加载
        private void loadImage2(final String url, final int id) {
                Thread thread = new Thread() {
                        @Override
                        public void run() {
                                Drawable drawable = null;
                                try {
                                        drawable = Drawable.createFromStream(
                                                        new URL(url).openStream(), "image.png");
                                } catch (IOException e) {
                                        Log.d("test", e.getMessage());
                                }
 
                                // 模拟网络延时
                                SystemClock.sleep(2000);
 
                                Message message = handler2.obtainMessage();
                                message.arg1 = id;
                                message.obj = drawable;
                                handler2.sendMessage(message);
                        }
                };
                thread.start();
                thread = null;
        }
 
}


这时候我们可以看到实现了异步加载, 界面打开时,五个ImageView都是没有图的,然后在各自线程下载完后才把图自动更新上去

Handler+ExecutorService(线程池)+MessageQueue模式

能开线程的个数毕竟是有限的,我们总不能开很多线程,对于手机更是如此。

这个例子是使用线程池。Android拥有与Java相同的ExecutorService实现,我们就来用它。

线程池的基本思想还是一种对象池的思想,开辟一块内存空间,里面存放了众多(未死亡)的线程,池中线程执行调度由池管理器来处理。当有线程任务时,从池中取一个,执行完成后线程对象归池,这样可以避免反复创建线程对象所带来的性能开销,节省了系统的资源。

线程池的信息可以参看这篇文章:Java&Android的线程池-ExecutorService 下面的演示例子是创建一个可重用固定线程数的线程池

核心代码

代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
package ghj1976.AndroidTest;
 
import java.io.IOException;
import java.net.URL;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
import android.os.Handler;
import android.os.Message;
import android.os.SystemClock;
import android.util.Log;
import android.widget.ImageView;
 
public class MainActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
                loadImage3("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
                loadImage3("http://www.chinatelecom.com.cn/images/logo_new.gif",
                                R.id.imageView2);
                loadImage3("http://cache.soso.com/30d/img/web/logo.gif",
                                R.id.imageView3);
                loadImage3("http://csdnimg.cn/www/images/csdnindex_logo.gif",
                                R.id.imageView4);
                loadImage3("http://images.cnblogs.com/logo_small.gif",
                                R.id.imageView5);
        }
 
        private Handler handler = new Handler();
 
        private ExecutorService executorService = Executors.newFixedThreadPool(5);
 
        // 引入线程池来管理多线程
        private void loadImage3(final String url, final int id) {
                executorService.submit(new Runnable() {
                        public void run() {
                                try {
                                        final Drawable drawable = Drawable.createFromStream(
                                                        new URL(url).openStream(), "image.png");
                                        // 模拟网络延时
                                        SystemClock.sleep(2000);
                                        handler.post(new Runnable() {
                                                public void run() {
                                                        ((ImageView) MainActivity.this.findViewById(id))
                                                                        .setImageDrawable(drawable);
                                                }
                                        });
                                } catch (Exception e) {
                                        throw new RuntimeException(e);
                                }
                        }
                });
        }
}

这里我们象第一步一样使用了 handler.post(new Runnable() {  更新前段显示当然是在UI主线程,我们还有 executorService.submit(new Runnable() { 来确保下载是在线程池的线程中。

Handler+ExecutorService(线程池)+MessageQueue+缓存模式

下面比起前一个做了几个改造:

  • 把整个代码封装在一个类中
  • 为了避免出现同时多次下载同一幅图的问题,使用了本地缓存

封装的类:

代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
package ghj1976.AndroidTest;
 
import java.lang.ref.SoftReference;
import java.net.URL;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
 
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.os.SystemClock;
 
public class AsyncImageLoader3 {
        // 为了加快速度,在内存中开启缓存(主要应用于重复图片较多时,或者同一个图片要多次被访问,比如在ListView时来回滚动)
        public Map<String, SoftReference<Drawable>> imageCache = new HashMap<String, SoftReference<Drawable>>();
         
        private ExecutorService executorService = Executors.newFixedThreadPool(5); // 固定五个线程来执行任务
        private final Handler handler = new Handler();
 
        /**
         *
         * @param imageUrl
         *            图像url地址
         * @param callback
         *            回调接口
         * <a href='\"http://www.eoeandroid.com/home.php?mod=space&uid=7300\"' target='\"_blank\"'>@return</a> 返回内存中缓存的图像,第一次加载返回null
         */
        public Drawable loadDrawable(final String imageUrl,
                        final ImageCallback callback) {
                // 如果缓存过就从缓存中取出数据
                if (imageCache.containsKey(imageUrl)) {
                        SoftReference<Drawable> softReference = imageCache.get(imageUrl);
                        if (softReference.get() != null) {
                                return softReference.get();
                        }
                }
                // 缓存中没有图像,则从网络上取出数据,并将取出的数据缓存到内存中
                executorService.submit(new Runnable() {
                        public void run() {
                                try {
                                        final Drawable drawable = loadImageFromUrl(imageUrl);
                                                 
                                        imageCache.put(imageUrl, new SoftReference<Drawable>(
                                                        drawable));
 
                                        handler.post(new Runnable() {
                                                public void run() {
                                                        callback.imageLoaded(drawable);
                                                }
                                        });
                                } catch (Exception e) {
                                        throw new RuntimeException(e);
                                }
                        }
                });
                return null;
        }
 
        // 从网络上取数据方法
        protected Drawable loadImageFromUrl(String imageUrl) {
                try {
                        // 测试时,模拟网络延时,实际时这行代码不能有
                        SystemClock.sleep(2000);
 
                        return Drawable.createFromStream(new URL(imageUrl).openStream(),
                                        "image.png");
 
                } catch (Exception e) {
                        throw new RuntimeException(e);
                }
        }
 
        // 对外界开放的回调接口
        public interface ImageCallback {
                // 注意 此方法是用来设置目标对象的图像资源
                public void imageLoaded(Drawable imageDrawable);
        }
}


说明:

final参数是指当函数参数为final类型时,你可以读取使用该参数,但是无法改变该参数的值。参看:Java关键字final、static使用总结
这里使用SoftReference 是为了解决内存不足的错误(OutOfMemoryError)的,更详细的可以参看:内存优化的两个类:SoftReference 和 WeakReference

前段调用:

代码片段,双击复制
01
02
03
04
05
06
07
08
09
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
package ghj1976.AndroidTest;
 
import android.app.Activity;
import android.graphics.drawable.Drawable;
import android.os.Bundle;
 
import android.widget.ImageView;
 
public class MainActivity extends Activity {
        @Override
        public void onCreate(Bundle savedInstanceState) {
                super.onCreate(savedInstanceState);
                setContentView(R.layout.main);
                loadImage4("http://www.baidu.com/img/baidu_logo.gif", R.id.imageView1);
                loadImage4("http://www.chinatelecom.com.cn/images/logo_new.gif",
                                R.id.imageView2);
                loadImage4("http://cache.soso.com/30d/img/web/logo.gif",
                                R.id.imageView3);
                loadImage4("http://csdnimg.cn/www/images/csdnindex_logo.gif",
                                R.id.imageView4);
                loadImage4("http://images.cnblogs.com/logo_small.gif",
                                R.id.imageView5);
        }
 
        private AsyncImageLoader3 asyncImageLoader3 = new AsyncImageLoader3();
 
        // 引入线程池,并引入内存缓存功能,并对外部调用封装了接口,简化调用过程
        private void loadImage4(final String url, final int id) {
                // 如果缓存过就会从缓存中取出图像,ImageCallback接口中方法也不会被执行
                Drawable cacheImage = asyncImageLoader3.loadDrawable(url,
                                new AsyncImageLoader3.ImageCallback() {
                                        // 请参见实现:如果第一次加载url时下面方法会执行
                                        public void imageLoaded(Drawable imageDrawable) {
                                                ((ImageView) findViewById(id))
                                                                .setImageDrawable(imageDrawable);
                                        }
                                });
                if (cacheImage != null) {
                        ((ImageView) findViewById(id)).setImageDrawable(cacheImage);
                }
        }
 
}
// 定义包名,对应项目结构中的包路径 package com.example.demoapplication; // 导入Android系统权限相关类 import android.Manifest; // 提供系统权限声明 import android.content.pm.PackageManager; // 用于检查和请求权限 // 导入音频处理相关类 import android.media.AudioFormat; // 音频格式定义 import android.media.AudioManager; // 音频管理器 import android.media.AudioRecord; // 录音功能 import android.media.AudioTrack; // 音频播放功能 import android.media.MediaRecorder; // 媒体录制配置 // 导入Android基础组件类 import android.os.Bundle; // 用于保存和恢复Activity状态 import android.os.Handler; // 用于线程间通信 import android.os.Looper; // 线程消息循环 import android.os.Message; // 消息对象 // 导入语音识别与文本转语音相关类 import android.speech.tts.TextToSpeech; // 文本转语音功能 // 导入数据转换与编码工具类 import android.util.Base64; // Base64编码解码 import android.util.Log; // 日志记录 import android.widget.Button; // 按钮控件 import android.widget.Toast; // 短时提示信息 // 导入AndroidX注解支持 import androidx.annotation.NonNull; // 表示非空注解 // 导入权限兼容处理工具类 import androidx.appcompat.app.AppCompatActivity; // 兼容Activity基类 import androidx.core.app.ActivityCompat; // 兼容权限请求 import androidx.core.content.ContextCompat; // 权限状态检查 // 导入JSON解析库 import org.json.JSONException; // JSON异常 import org.json.JSONObject; // JSON对象操作 // 导入IO流相关类 import java.io.BufferedReader; // 缓冲字符输入流 import java.io.BufferedWriter; // 缓冲字符输出流 import java.io.IOException; // IO异常 import java.io.InputStreamReader; // 字节流到字符流的转换 import java.io.OutputStreamWriter; // 字符流到字节流的转换 // 导入网络通信相关类 import java.net.ServerSocket; // 服务器端Socket import java.net.Socket; // 客户端Socket // 导入集合框架 import java.util.LinkedList; // 双向链表实现 import java.util.Locale; // 地区设置 import java.util.Queue; // 队列接口 // 导入并发编程相关类 import java.util.concurrent.ExecutorService; // 执行服务 import java.util.concurrent.Executors; // 线程池工厂 import java.util.concurrent.ScheduledExecutorService; // 定时任务执行服务 import java.util.concurrent.TimeUnit; // 时间单位 // 导入原子变量类 import java.util.concurrent.atomic.AtomicBoolean; // 原子布尔值 // 主Activity类,继承自AppCompatActivity并实现TTS初始化监听器 public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { // 日志标签和UI组件定义 private static final String TAG = "AudioRecorder"; // 日志标签 private Button startRecordButton, stopRecordButton; // 录音控制按钮 private AudioRecord audioRecord; // 音频录制实例 // 音频采样率和缓冲区大小配置 private static final int SAMPLE_RATE = 16000; // 采样率(Hz) private static final int BUFFER_SIZE; // 缓冲区大小 // 静态代码块用于初始化缓冲区大小 static { // 计算最小缓冲区大小并确保不小于4096字节 int minBufferSize = AudioRecord.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT ); BUFFER_SIZE = Math.max(minBufferSize, 4096); } // 线程池和服务状态变量 private ScheduledExecutorService scheduler; // 定时任务调度器 private AtomicBoolean isRecording = new AtomicBoolean(false); // 录音状态标志 private static final int PERMISSION_REQUEST_CODE = 1; // 权限请求码 private final ExecutorService executorService = Executors.newCachedThreadPool(); // 缓存线程池 // 网络通信相关变量 private ServerSocket serverSocket; // 服务器Socket private volatile boolean isServerRunning = true; // 服务器运行状态 private volatile Socket clientSocket; // 客户端Socket private volatile BufferedWriter socketWriter; // Socket写入器 // 文字转语音(TTS)引擎和播放器 private TextToSpeech ttsEngine; // TTS引擎实例 private boolean isTtsInitialized = false; // TTS初始化状态 private AudioTrack audioTrack; // 音频播放轨道 // 控制是否已发送开始/结束提示 private boolean isStartMessageSent = false; // 开始消息发送状态 private boolean isStopMessageSent = false; // 停止消息发送状态 private boolean isTransmitStarted = false; // 传输开始状态 private boolean isStopTransmitSent = false; // 停止传输发送状态 // 新增音频播放状态和控制字段 private boolean isPaused = false; // 是否处于暂停状态 private byte[] pausedAudioData; // 暂停时的音频数据 private int pausedPosition; // 暂停位置 // TTS音频队列和播放状态 private final Queue<byte[]> ttsQueue = new LinkedList<>(); // TTS音频队列 private boolean isTtsPlaying = false; // TTS播放状态 // 主线程消息处理器 private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: // 客户端连接 Toast.makeText(MainActivity.this, "客户端已连接", Toast.LENGTH_SHORT).show(); break; case 0x12: // 开始录音 Toast.makeText(MainActivity.this, "开始录音", Toast.LENGTH_SHORT).show(); isStartMessageSent = true; isStopMessageSent = false; sendControlPacket("startRecorder"); playTts("开始录音"); break; case 0x13: // 数据发送 break; case 0x14: // 停止录音 Toast.makeText(MainActivity.this, "停止录音", Toast.LENGTH_SHORT).show(); isStopMessageSent = true; isStartMessageSent = false; sendControlPacket("stopRecorder"); playTts("停止录音"); break; case 0x15: // 控制指令 Toast.makeText(MainActivity.this, "收到指令: " + msg.obj, Toast.LENGTH_SHORT).show(); break; case 0x16: // 错误 Toast.makeText(MainActivity.this, "错误: " + msg.obj, Toast.LENGTH_LONG).show(); break; case 0x18: // TTS音频 handleTtsAudio((String) msg.obj); break; case 0x19: // 聊天开始 Toast.makeText(MainActivity.this, "聊天开始: " + msg.obj, Toast.LENGTH_SHORT).show(); break; case 0x20: // 聊天回复 Toast.makeText(MainActivity.this, "回复: " + msg.obj, Toast.LENGTH_LONG).show(); break; case 0x21: // 播放完成 Toast.makeText(MainActivity.this, "播放完成", Toast.LENGTH_SHORT).show(); // 播放下一段音频 playNextTtsAudio(); break; case 0x22: // 暂停播放 Toast.makeText(MainActivity.this, "播放已暂停", Toast.LENGTH_SHORT).show(); pauseCurrentPlayback(); break; case 0x23: // 继续播放 Toast.makeText(MainActivity.this, "继续播放", Toast.LENGTH_SHORT).show(); resumeCurrentPlayback(); break; case 0x24: // 清除队列 Toast.makeText(MainActivity.this, "队列已清除", Toast.LENGTH_SHORT).show(); clearTtsQueue(); break; } } }; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化TTS引擎 ttsEngine = new TextToSpeech(this, this); initViews(); setupClickListeners(); checkPermissions(); startServer(30000); } private void initViews() { startRecordButton = findViewById(R.id.startRecordButton); // 绑定开始录音按钮 stopRecordButton = findViewById(R.id.stopRecordButton); // 绑定停止录音按钮 stopRecordButton.setEnabled(false); // 默认禁用停止按钮 } private void setupClickListeners() { startRecordButton.setOnClickListener(v -> startRecording()); // 设置开始录音点击事件 stopRecordButton.setOnClickListener(v -> stopRecording()); // 设置停止录音点击事件 } private void checkPermissions() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.RECORD_AUDIO}, PERMISSION_REQUEST_CODE); } } private void startRecording() { if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { sendErrorMessage("没有录音权限"); return; } if (isRecording.get()) { releaseAudioResources(); } if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { sendErrorMessage("客户端未连接"); return; } try { audioRecord = new AudioRecord( MediaRecorder.AudioSource.MIC, SAMPLE_RATE, AudioFormat.CHANNEL_IN_MONO, AudioFormat.ENCODING_PCM_16BIT, BUFFER_SIZE ); if (audioRecord.getState() != AudioRecord.STATE_INITIALIZED) { throw new IllegalStateException("AudioRecord 初始化失败"); } audioRecord.startRecording(); isRecording.set(true); startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); if (scheduler != null) { scheduler.shutdownNow(); } scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::uploadAudioData, 0, 100, TimeUnit.MILLISECONDS); handler.sendEmptyMessage(0x12); sendControlPacket("startRecorder"); playTts("开始录音"); } catch (Exception e) { Log.e(TAG, "录音启动失败", e); sendErrorMessage("录音启动失败: " + e.getMessage()); releaseAudioResources(); } } private void stopRecording() { if (!isRecording.get()) return; isRecording.set(false); releaseAudioResources(); stopRecordButton.setEnabled(false); startRecordButton.setEnabled(true); isStopMessageSent = false; isStartMessageSent = false; handler.sendEmptyMessage(0x14); sendControlPacket("stopRecorder"); playTts("停止录音"); } private void playTts(String text) { if (isTtsInitialized) { ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); Log.i(TAG, "播放TTS: " + text); } } private void releaseAudioResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } } catch (IllegalStateException e) { Log.e(TAG, "停止录音失败", e); } audioRecord.release(); audioRecord = null; } if (scheduler != null) { scheduler.shutdownNow(); scheduler = null; } } private void uploadAudioData() { if (!isRecording.get() || clientSocket == null || clientSocket.isClosed() || socketWriter == null) { return; } byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING && !isTransmitStarted) { sendControlPacket("startTransmit"); playTts("开始传输"); audioRecord.startRecording(); isTransmitStarted = true; } if (bytesRead < BUFFER_SIZE / 2 && isTransmitStarted) { sendControlPacket("stopTransmit"); playTts("停止传输"); isTransmitStarted = false; } JSONObject json = new JSONObject(); json.put("type", "recording"); json.put("data", Base64.encodeToString(buffer, 0, bytesRead, Base64.NO_WRAP)); synchronized (this) { if (socketWriter != null) { socketWriter.write(json.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } } } catch (Exception e) { Log.e(TAG, "发送音频数据失败", e); sendErrorMessage("发送音频数据失败: " + e.getMessage()); } } @Override public void onInit(int status) { if (status == TextToSpeech.SUCCESS) { int result = ttsEngine.setLanguage(Locale.CHINESE); if (result == TextToSpeech.LANG_MISSING_DATA || result == TextToSpeech.LANG_NOT_SUPPORTED) { Log.e(TAG, "TTS语言不支持中文"); } else { isTtsInitialized = true; } } } private void sendControlPacket(String type) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } } catch (Exception e) { Log.e(TAG, "发送控制指令失败", e); } } private void sendErrorMessage(String message) { handler.obtainMessage(0x16, message).sendToTarget(); } private void startServer(int port) { executorService.execute(() -> { try { serverSocket = new ServerSocket(port); Log.i(TAG, "服务器启动: " + port); while (isServerRunning) { try { Socket socket = serverSocket.accept(); clientSocket = socket; synchronized (this) { socketWriter = new BufferedWriter( new OutputStreamWriter(socket.getOutputStream(), "UTF-8")); } handler.sendEmptyMessage(0x11); executorService.execute(() -> startCommunication(socket)); } catch (IOException e) { if (isServerRunning) Log.e(TAG, "接受连接失败", e); } } } catch (IOException e) { Log.e(TAG, "服务器启动失败", e); runOnUiThread(() -> Toast.makeText(this, "服务器启动失败: " + e.getMessage(), Toast.LENGTH_LONG).show()); } finally { closeServerSocket(); } }); } private void startCommunication(Socket socket) { try (BufferedReader reader = new BufferedReader( new InputStreamReader(socket.getInputStream(), "UTF-8"))) { StringBuilder packetBuilder = new StringBuilder(); int c; while ((c = reader.read()) != -1 && isServerRunning) { char ch = (char) c; packetBuilder.append(ch); if (packetBuilder.length() >= 2 && packetBuilder.charAt(packetBuilder.length() - 2) == '\n' && packetBuilder.charAt(packetBuilder.length() - 1) == '\n') { String packet = packetBuilder.toString().trim(); packetBuilder.setLength(0); if (!packet.isEmpty()) { try { JSONObject jsonObject = new JSONObject(packet); handleReceivedPacket(jsonObject); } catch (JSONException e) { Log.w(TAG, "JSON解析失败: " + packet, e); } } } } } catch (IOException e) { if (isServerRunning) { Log.e(TAG, "通信中断", e); } } finally { closeSocket(socket); } } /** * 处理接收到的JSON数据包 * @param jsonObject 接收到的JSON对象 */ private void handleReceivedPacket(JSONObject jsonObject) { try { // 获取数据包类型字段 String type = jsonObject.getString("type"); // 获取可选的数据字段(可能不存在) Object data = jsonObject.opt("data"); Message msg; // 根据数据包类型进行处理 switch (type) { case "tts_audio": // TTS音频数据 // 创建带数据的消息并发送到主线程 msg = handler.obtainMessage(0x18, data.toString()); handler.sendMessage(msg); break; case "chat_start": // 聊天开始事件 // 解析查询内容并发送消息 msg = handler.obtainMessage(0x19, jsonObject.getJSONObject("data").getString("query")); handler.sendMessage(msg); break; case "chat_reply": // 聊天回复事件 // 解析回复内容并发送消息 msg = handler.obtainMessage(0x20, jsonObject.getJSONObject("data").getString("reply")); handler.sendMessage(msg); break; case "play_complete": // 播放完成通知 // 发送空消息触发下一段播放 handler.sendEmptyMessage(0x21); break; case "playSound": // 播放指定音频 // 添加到播放队列 addTtsAudioToQueue(Base64.decode(data.toString(), Base64.DEFAULT)); break; case "pauseSound": // 暂停播放 handler.sendEmptyMessage(0x22); break; case "resumeSound": // 继续播放 handler.sendEmptyMessage(0x23); break; case "stopSound": // 停止播放 handler.sendEmptyMessage(0x24); break; case "clearSound": // 清除队列 // 清空播放队列 handler.sendEmptyMessage(0x24); break; default: // 其他未知类型 // 发送通用处理消息 msg = handler.obtainMessage(0x15, type + ": " + data); handler.sendMessage(msg); break; } } catch (JSONException e) { // 记录JSON处理异常日志 Log.e(TAG, "处理数据包失败", e); } } /** * 处理TTS音频数据 - 将Base64数据解码后加入播放队列 * @param base64Data Base64编码的PCM音频数据 */ private void handleTtsAudio(String base64Data) { // 解码Base64数据为字节数组 byte[] pcmData = Base64.decode(base64Data, Base64.DEFAULT); // 添加到音频播放队列 addTtsAudioToQueue(pcmData); } /** * 将PCM音频数据添加到播放队列 * @param pcmData 要添加的PCM音频数据 */ private void addTtsAudioToQueue(byte[] pcmData) { synchronized (ttsQueue) { // 如果不是暂停状态,则直接加入队列 if (!isPaused) { // 将音频数据加入队列尾部 ttsQueue.offer(pcmData); // 如果当前没有正在播放,则立即开始播放 if (!isTtsPlaying) { playNextTtsAudio(); } } else { // 替换当前暂停的音频数据 pausedAudioData = pcmData; pausedPosition = 0; } } } /** * 播放队列中的下一段音频 */ private void playNextTtsAudio() { synchronized (ttsQueue) { // 如果队列为空,标记播放结束 if (ttsQueue.isEmpty()) { isTtsPlaying = false; return; } // 取出队列头部的音频数据 byte[] pcmData = ttsQueue.poll(); // 开始播放该段音频 playPcm(pcmData); // 标记为正在播放状态 isTtsPlaying = true; } } /** * 播放PCM格式的音频数据,并设置播放完成监听 * @param pcmData PCM音频数据字节数组 */ private void playPcm(byte[] pcmData) { // 停止任何正在进行的音频播放 stopAudioPlayback(); // 设置音频参数 int sampleRate = 16000; // 采样率 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 单声道输出 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 16位PCM编码 // 计算最小缓冲区大小 int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); // 创建音频播放器实例 audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, // 音频流类型 sampleRate, // 采样率 channelConfig, // 声道配置 audioFormat, // 音频格式 bufferSize, // 缓冲区大小 AudioTrack.MODE_STREAM); // 播放模式 // 设置播放位置监听器 audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { @Override public void onMarkerReached(AudioTrack track) { // 播放完成后发送通知 handler.sendEmptyMessage(0x21); } @Override public void onPeriodicNotification(AudioTrack track) { // 周期性通知,不需要实现 } }); // 计算帧数(单声道,16位,所以每帧2字节) int frameCount = pcmData.length / 2; // 设置标记位置(在最后一帧) audioTrack.setNotificationMarkerPosition(frameCount); audioTrack.play(); audioTrack.write(pcmData, 0, pcmData.length); } // 停止音频播放(添加状态重置) private void stopAudioPlayback() { if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } // 移除监听器,防止回调时使用已释放的对象 audioTrack.setPlaybackPositionUpdateListener(null); audioTrack.release(); audioTrack = null; } // 重置播放状态 synchronized (ttsQueue) { ttsQueue.clear(); isTtsPlaying = false; } } // 暂停当前播放 private void pauseCurrentPlayback() { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.pause(); isPaused = true; // 保存当前音频数据和播放位置 if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { int position = audioTrack.getPlaybackHeadPosition(); if (position > 0) { pausedPosition = position * 2; // 转换为字节位置 if (pausedPosition < audioTrack.getBufferSizeInFrames() * 2) { int remaining = audioTrack.getBufferSizeInFrames() * 2 - pausedPosition; byte[] remainingData = new byte[remaining]; System.arraycopy(pausedAudioData, pausedPosition, remainingData, 0, remaining); pausedAudioData = remainingData; } } } } } // 继续当前播放 private void resumeCurrentPlayback() { if (isPaused && pausedAudioData != null) { // 从暂停位置继续播放 playPcmFromPosition(pausedAudioData, pausedPosition); isPaused = false; } else if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { audioTrack.play(); } else { // 没有暂停的音频,播放队列中的下一段 playNextTtsAudio(); } } // 从指定位置播放PCM音频数据 private void playPcmFromPosition(byte[] pcmData, int startPosition) { // 停止任何正在进行的音频播放 stopAudioPlayback(); // 设置音频参数 int sampleRate = 16000; // 采样率 int channelConfig = AudioFormat.CHANNEL_OUT_MONO; // 单声道输出 int audioFormat = AudioFormat.ENCODING_PCM_16BIT; // 16位PCM编码 // 计算最小缓冲区大小 int bufferSize = AudioTrack.getMinBufferSize(sampleRate, channelConfig, audioFormat); // 创建音频播放器实例 audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, // 音频流类型 sampleRate, // 采样率 channelConfig, // 声道配置 audioFormat, // 音频格式 bufferSize, // 缓冲区大小 AudioTrack.MODE_STREAM); // 播放模式 // 设置播放位置监听器 audioTrack.setPlaybackPositionUpdateListener(new AudioTrack.OnPlaybackPositionUpdateListener() { @Override public void onMarkerReached(AudioTrack track) { // 播放完成后发送通知 handler.sendEmptyMessage(0x21); } @Override public void onPeriodicNotification(AudioTrack track) { // 周期性通知,不需要实现 } }); // 计算剩余数据长度 int remainingLength = pcmData.length - startPosition; // 创建新数组存储从指定位置开始的数据 byte[] remainingData = new byte[remainingLength]; System.arraycopy(pcmData, startPosition, remainingData, 0, remainingLength); // 计算帧数(单声道,16位,所以每帧2字节) int frameCount = remainingLength / 2; // 设置标记位置(在最后一帧) audioTrack.setNotificationMarkerPosition(frameCount); audioTrack.play(); audioTrack.write(remainingData, 0, remainingLength); } // 清空TTS播放队列 private void clearTtsQueue() { synchronized (ttsQueue) { ttsQueue.clear(); // 停止当前播放并重置状态 if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } audioTrack.release(); audioTrack = null; } isTtsPlaying = false; isPaused = false; pausedAudioData = null; pausedPosition = 0; } } private void closeSocket(Socket socket) { try { if (socket != null && !socket.isClosed()) { socket.close(); } } catch (IOException e) { Log.w(TAG, "关闭Socket失败", e); } if (socket == clientSocket) { clientSocket = null; synchronized (this) { socketWriter = null; } } } private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } closeServerSocket(); closeSocket(clientSocket); executorService.shutdownNow(); releaseAudioResources(); stopAudioPlayback(); } @Override public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); if (requestCode == PERMISSION_REQUEST_CODE) { if (grantResults.length > 0 && grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "录音权限已授予", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "录音权限被拒绝", Toast.LENGTH_SHORT).show(); } } } public boolean isStopTransmitSent() { return isStopTransmitSent; } public void setStopTransmitSent(boolean stopTransmitSent) { isStopTransmitSent = stopTransmitSent; } public boolean isStartMessageSent() { return isStartMessageSent; } public void setStartMessageSent(boolean startMessageSent) { isStartMessageSent = startMessageSent; } public boolean isStopMessageSent() { return isStopMessageSent; } public void setStopMessageSent(boolean stopMessageSent) { isStopMessageSent = stopMessageSent; } } 生成JAVa在ider用的后台接收代码
最新发布
07-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值