java.io.IOException: Permission denied (android中向SD卡创建文件的时候)

本文探讨了Android应用在尝试访问并写入SD卡时遇到Permission denied错误的原因,并提供了在manifest中添加必要的权限配置解决此问题的方法。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

当在往sdcard中写入文件的时候。报错误:Android 报错java.io.IOException: Permission denied  一般是由于没有给你的application添加权限,  在manifest中添加<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>  

package com.example.demoapplication; 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; import android.os.Bundle; import android.os.Handler; import android.os.Looper; import android.os.Message; import android.speech.tts.TextToSpeech; import android.util.Base64; import android.util.Log; import android.view.View; import android.view.animation.Animation; import android.view.animation.ScaleAnimation; import android.widget.Button; import android.widget.Toast; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.ActivityCompat; import androidx.core.content.ContextCompat; import org.json.JSONException; import org.json.JSONObject; import java.io.BufferedReader; import java.io.BufferedWriter; import java.io.ByteArrayInputStream; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.net.ServerSocket; import java.net.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; /** * 主活动类,实现录音、播放、网络通信和TTS功能 */ public class MainActivity extends AppCompatActivity implements TextToSpeech.OnInitListener { // 日志标签 private static final String TAG = "AudioRecorder"; // UI控件 private Button startRecordButton, stopRecordButton; private Button playSoundButton, pauseSoundButton, stopSoundButton, resumeSoundButton, clearSoundsButton; private AudioRecord audioRecord; // 音频配置常量 private static final int SAMPLE_RATE = 16000; private static final int BUFFER_SIZE; // 静态代码块计算缓冲区大小 static { 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; private volatile boolean isServerRunning = true; private volatile Socket clientSocket; private volatile BufferedWriter socketWriter; // TTS和音频播放控制 private TextToSpeech ttsEngine; private boolean isTtsInitialized = false; private AudioTrack audioTrack; // 队列用于存储录音数据 private final Queue<byte[]> recordingQueue = new LinkedList<>(); private final Queue<byte[]> pausedQueue = new LinkedList<>(); private final Queue<byte[]> playbackQueue = new LinkedList<>(); // 原子变量确保线程安全的状态管理 private final AtomicBoolean isPlaying = new AtomicBoolean(false); private final AtomicBoolean isPaused = new AtomicBoolean(false); private volatile boolean isPlaybackThreadActive = false; // 锁对象用于同步访问共享资源 private final Object audioTrackLock = new Object(); private final Object playbackQueueLock = new Object(); private final Object recordingQueueLock = new Object(); // 动画资源 private Animation buttonPressAnim; // 主线程Handler用于更新UI private final Handler handler = new Handler(Looper.getMainLooper()) { @Override public void handleMessage(@NonNull Message msg) { switch (msg.what) { case 0x11: showAnimatedToast("客户端已连接", Toast.LENGTH_SHORT); break; case 0x12: showAnimatedToast("开始录音", Toast.LENGTH_SHORT); sendJsonPacket("startRecorder", null); playTts("开始录音"); startButtonPulseAnimation(startRecordButton); break; case 0x14: showAnimatedToast("停止录音", Toast.LENGTH_SHORT); sendJsonPacket("stopRecorder", null); playTts("停止录音"); stopButtonPulseAnimation(startRecordButton); break; case 0x16: showAnimatedToast("错误: " + msg.obj, Toast.LENGTH_LONG); break; case 0x17: showAnimatedToast("播放完成", Toast.LENGTH_SHORT); isPlaying.set(false); isPlaybackThreadActive = false; updatePlayButtonsState(); stopPlaybackAnimations(); break; case 0x18: showAnimatedToast("已添加到播放队列", Toast.LENGTH_SHORT); break; case 0x19: updatePlayButtonsState(); break; case 0x20: sendJsonPacket("pauseSound", null); playTts("播放暂停"); pausePlaybackAnimations(); break; case 0x21: sendJsonPacket("stopSound", null); playTts("播放停止"); stopPlaybackAnimations(); break; case 0x22: sendJsonPacket("resumeSound", null); playTts("继续播放"); resumePlaybackAnimations(); break; case 0x23: sendJsonPacket("clearSounds", null); playTts("清空所有录音"); break; case 0x24: String base64Data = (String) msg.obj; try { byte[] decodedData = Base64.decode(base64Data, Base64.DEFAULT); addBase64ToPlaybackQueue(decodedData); } catch (IllegalArgumentException e) { Log.e(TAG, "Base64解码失败", e); sendErrorMessage("无效的Base64数据"); } break; } } }; /** * Activity创建方法 * @param savedInstanceState 保存的状态 */ @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 初始化按钮脉冲动画 initButtonAnimations(); // 初始化TTS引擎 ttsEngine = new TextToSpeech(this, this); initViews(); setupClickListeners(); checkPermissions(); startServer(30000); startSocketListener(); } /** * 初始化按钮动画 */ private void initButtonAnimations() { // 创建按钮点击动画 buttonPressAnim = new ScaleAnimation( 1.0f, 0.9f, 1.0f, 0.9f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); buttonPressAnim.setDuration(150); buttonPressAnim.setRepeatCount(0); buttonPressAnim.setFillAfter(false); } /** * 初始化视图组件 */ private void initViews() { // 绑定按钮 startRecordButton = findViewById(R.id.startRecordButton); stopRecordButton = findViewById(R.id.stopRecordButton); playSoundButton = findViewById(R.id.playSoundButton); pauseSoundButton = findViewById(R.id.pauseSoundButton); stopSoundButton = findViewById(R.id.stopSoundButton); resumeSoundButton = findViewById(R.id.resumeSoundButton); clearSoundsButton = findViewById(R.id.clearSoundsButton); // 初始按钮状态设置 stopRecordButton.setEnabled(false); pauseSoundButton.setEnabled(false); stopSoundButton.setEnabled(false); resumeSoundButton.setEnabled(false); } /** * 设置按钮点击监听器 */ private void setupClickListeners() { startRecordButton.setOnClickListener(v -> { animateButtonClick(v); startRecording(); }); stopRecordButton.setOnClickListener(v -> { animateButtonClick(v); stopRecording(); }); playSoundButton.setOnClickListener(v -> { animateButtonClick(v); addToPlaybackQueue(); }); pauseSoundButton.setOnClickListener(v -> { animateButtonClick(v); pausePlayback(); handler.sendEmptyMessage(0x20); }); stopSoundButton.setOnClickListener(v -> { animateButtonClick(v); stopPlayback(); handler.sendEmptyMessage(0x21); }); resumeSoundButton.setOnClickListener(v -> { animateButtonClick(v); if (isPaused.get() && !playbackQueue.isEmpty()) { resumePlayback(); handler.sendEmptyMessage(0x22); } }); clearSoundsButton.setOnClickListener(v -> { animateButtonClick(v); clearAllRecordings(); handler.sendEmptyMessage(0x23); }); } /** * 按钮点击动画 */ private void animateButtonClick(View view) { view.startAnimation(buttonPressAnim); } /** * 开始录音时启动脉冲动画 */ private void startButtonPulseAnimation(Button button) { stopButtonPulseAnimation(button); // 先停止之前的动画 Animation pulseAnim = new ScaleAnimation( 1.0f, 1.1f, 1.0f, 1.1f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); pulseAnim.setDuration(500); pulseAnim.setRepeatCount(Animation.INFINITE); pulseAnim.setRepeatMode(Animation.REVERSE); button.startAnimation(pulseAnim); } /** * 停止录音时停止脉冲动画 */ private void stopButtonPulseAnimation(Button button) { button.clearAnimation(); } /** * 播放暂停时的动画效果 */ private void pausePlaybackAnimations() { stopButtonPulseAnimation(playSoundButton); stopButtonPulseAnimation(resumeSoundButton); } /** * 播放恢复时的动画效果 */ private void resumePlaybackAnimations() { startButtonPulseAnimation(resumeSoundButton); } /** * 播放停止时的动画效果 */ private void stopPlaybackAnimations() { stopButtonPulseAnimation(playSoundButton); stopButtonPulseAnimation(resumeSoundButton); } /** * 显示带缩放动画的Toast */ private void showAnimatedToast(String message, int duration) { Toast toast = Toast.makeText(this, message, duration); View view = toast.getView(); Animation anim = new ScaleAnimation( 0.0f, 1.0f, 0.0f, 1.0f, Animation.RELATIVE_TO_SELF, 0.5f, Animation.RELATIVE_TO_SELF, 0.5f ); anim.setDuration(300); view.startAnimation(anim); toast.show(); } // ==================== 录音功能实现 ==================== /** * 开始录音 */ private void startRecording() { // 检查录音权限 if (ContextCompat.checkSelfPermission(this, Manifest.permission.RECORD_AUDIO) != PackageManager.PERMISSION_GRANTED) { sendErrorMessage("没有录音权限"); return; } // 如果已经在录音,先释放资源 if (isRecording.get()) { releaseAudioResources(); } 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); // 更新UI状态 startRecordButton.setEnabled(false); stopRecordButton.setEnabled(true); updatePlayButtonsState(); // 启动录音数据采集线程 if (scheduler != null && !scheduler.isShutdown()) { scheduler.shutdownNow(); } scheduler = Executors.newSingleThreadScheduledExecutor(); scheduler.scheduleAtFixedRate(this::captureAudioData, 0, 100, TimeUnit.MILLISECONDS); // 发送开始录音通知 handler.sendEmptyMessage(0x12); } catch (Exception e) { Log.e(TAG, "录音启动失败", e); sendErrorMessage("录音启动失败: " + e.getMessage()); releaseAudioResources(); } } /** * 停止录音 */ private void stopRecording() { if (!isRecording.get()) return; isRecording.set(false); releaseAudioResources(); // 更新UI状态 stopRecordButton.setEnabled(false); startRecordButton.setEnabled(true); updatePlayButtonsState(); // 发送停止录音通知 handler.sendEmptyMessage(0x14); } /** * 采集音频数据并保存到队列 */ private void captureAudioData() { if (!isRecording.get() || audioRecord == null) return; byte[] buffer = new byte[BUFFER_SIZE]; try { int bytesRead = audioRecord.read(buffer, 0, BUFFER_SIZE); if (bytesRead > 0) { // 将录制的音频数据保存到队列 synchronized (recordingQueueLock) { recordingQueue.offer(buffer.clone()); } // 发送录音数据包 String base64Data = Base64.encodeToString(buffer, Base64.DEFAULT); sendJsonPacket("recording", base64Data); } } catch (Exception e) { Log.e(TAG, "音频采集失败", e); } } // ==================== 录音功能结束 ==================== // ==================== 播放功能实现 ==================== /** * 添加当前录音到播放队列 */ private void addToPlaybackQueue() { if (recordingQueue.isEmpty()) { showAnimatedToast("没有可播放的录音", Toast.LENGTH_SHORT); return; } // 创建录音数据副本 Queue<byte[]> recordingCopy = new LinkedList<>(); synchronized (recordingQueueLock) { for (byte[] data : recordingQueue) { recordingCopy.offer(data.clone()); } } // 添加到播放队列 synchronized (playbackQueueLock) { playbackQueue.addAll(recordingCopy); } // 如果当前没有播放,立即开始播放 if (!isPlaybackThreadActive && !isPlaying.get()) { executorService.execute(this::playRecordingQueue); startButtonPulseAnimation(playSoundButton); } else { handler.sendEmptyMessage(0x18); } } /** * 将Base64编码的音频数据添加到播放队列 * @param decodedData 解码后的音频数据 */ private void addBase64ToPlaybackQueue(byte[] decodedData) { if (decodedData == null || decodedData.length == 0) { Log.w(TAG, "无效的音频数据"); return; } ByteArrayInputStream inputStream = new ByteArrayInputStream(decodedData); byte[] buffer; int bytesRead; // 使用固定大小的缓冲区读取数据 buffer = new byte[4096]; try { while ((bytesRead = inputStream.read(buffer)) != -1) { if (bytesRead > 0) { byte[] dataChunk = new byte[bytesRead]; System.arraycopy(buffer, 0, dataChunk, 0, bytesRead); synchronized (playbackQueueLock) { playbackQueue.offer(dataChunk); } } } } catch (IOException e) { Log.e(TAG, "读取音频数据失败", e); } finally { try { inputStream.close(); } catch (IOException e) { Log.e(TAG, "关闭输入流失败", e); } } // 如果当前没有播放,立即开始播放 if (!isPlaybackThreadActive && !isPlaying.get()) { executorService.execute(this::playRecordingQueue); startButtonPulseAnimation(playSoundButton); } else { handler.sendEmptyMessage(0x18); } } /** * 播放录音队列 */ private void playRecordingQueue() { isPlaybackThreadActive = true; isPlaying.set(true); isPaused.set(false); handler.sendEmptyMessage(0x19); // 配置音频播放器 int bufferSize = AudioTrack.getMinBufferSize( SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT ); // 创建新的AudioTrack synchronized (audioTrackLock) { if (audioTrack != null) { try { audioTrack.stop(); audioTrack.release(); } catch (Exception e) { Log.e(TAG, "释放AudioTrack失败", e); } } try { audioTrack = new AudioTrack( AudioManager.STREAM_MUSIC, SAMPLE_RATE, AudioFormat.CHANNEL_OUT_MONO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM ); audioTrack.play(); } catch (IllegalStateException e) { Log.e(TAG, "创建AudioTrack失败", e); stopPlayback(); return; } } // 播放队列中的所有录音数据 while (isPlaying.get() && !playbackQueue.isEmpty()) { if (isPaused.get()) { // 暂停状态,等待恢复 try { Thread.sleep(100); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } continue; } byte[] audioData; synchronized (playbackQueueLock) { audioData = playbackQueue.poll(); } if (audioData != null) { synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getState() == AudioTrack.STATE_INITIALIZED) { try { audioTrack.write(audioData, 0, audioData.length); } catch (IllegalStateException e) { Log.e(TAG, "音频写入失败: " + e.getMessage()); break; } } else { Log.w(TAG, "AudioTrack不可用,停止播放"); break; } } } } // 确保播放完成时正确释放资源 try { synchronized (audioTrackLock) { if (audioTrack != null) { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { audioTrack.stop(); } audioTrack.release(); audioTrack = null; } } } catch (Exception e) { Log.e(TAG, "播放完成后释放资源失败", e); } // 播放完成 stopPlayback(); handler.sendEmptyMessage(0x17); } /** * 暂停播放 */ private void pausePlayback() { if (!isPlaying.get() || isPaused.get()) return; isPaused.set(true); // 保存当前播放位置 synchronized (playbackQueueLock) { pausedQueue.clear(); pausedQueue.addAll(playbackQueue); playbackQueue.clear(); } // 暂停音频播放 synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING) { try { audioTrack.pause(); } catch (IllegalStateException e) { Log.e(TAG, "暂停播放失败: " + e.getMessage()); } } } handler.sendEmptyMessage(0x19); runOnUiThread(() -> showAnimatedToast("播放已暂停", Toast.LENGTH_SHORT) ); } /** * 继续播放 */ private void resumePlayback() { if (!isPaused.get() || pausedQueue.isEmpty()) { return; } isPaused.set(false); isPlaying.set(true); // 恢复播放位置 synchronized (playbackQueueLock) { playbackQueue.clear(); playbackQueue.addAll(pausedQueue); pausedQueue.clear(); } // 恢复音频播放 synchronized (audioTrackLock) { if (audioTrack != null && audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { try { audioTrack.play(); } catch (IllegalStateException e) { Log.e(TAG, "恢复播放失败: " + e.getMessage()); } } } handler.sendEmptyMessage(0x19); runOnUiThread(() -> showAnimatedToast("继续播放", Toast.LENGTH_SHORT) ); } /** * 停止播放 */ private void stopPlayback() { isPlaying.set(false); isPaused.set(false); synchronized (audioTrackLock) { if (audioTrack != null) { try { if (audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PLAYING || audioTrack.getPlayState() == AudioTrack.PLAYSTATE_PAUSED) { audioTrack.stop(); } audioTrack.release(); } catch (IllegalStateException e) { Log.e(TAG, "停止播放失败: " + e.getMessage()); } finally { audioTrack = null; } } } synchronized (playbackQueueLock) { playbackQueue.clear(); } pausedQueue.clear(); runOnUiThread(() -> { handler.sendEmptyMessage(0x19); showAnimatedToast("播放已停止", Toast.LENGTH_SHORT); }); } private void clearAllRecordings() { stopPlayback(); synchronized (recordingQueueLock) { recordingQueue.clear(); } pausedQueue.clear(); synchronized (playbackQueueLock) { playbackQueue.clear(); } handler.sendEmptyMessage(0x19); runOnUiThread(() -> showAnimatedToast("所有录音已清除", Toast.LENGTH_SHORT) ); } // ==================== 播放功能结束 ==================== // ==================== 辅助方法 ==================== /** * 更新播放按钮状态 */ private void updatePlayButtonsState() { runOnUiThread(() -> { boolean hasRecordings = !recordingQueue.isEmpty() || !pausedQueue.isEmpty(); boolean isPlayingState = isPlaying.get() && !isPaused.get(); playSoundButton.setEnabled(hasRecordings && !isPlayingState); pauseSoundButton.setEnabled(isPlayingState); stopSoundButton.setEnabled(isPlaying.get() || isPaused.get()); resumeSoundButton.setEnabled(!playbackQueue.isEmpty() && isPaused.get()); clearSoundsButton.setEnabled(hasRecordings); }); } /** * 播放TTS语音 * @param text 要播放的文本 */ private void playTts(String text) { if (isTtsInitialized) { ttsEngine.speak(text, TextToSpeech.QUEUE_FLUSH, null); } } /** * 释放音频资源 */ private void releaseAudioResources() { if (audioRecord != null) { try { if (audioRecord.getRecordingState() == AudioRecord.RECORDSTATE_RECORDING) { audioRecord.stop(); } audioRecord.release(); } catch (IllegalStateException e) { Log.e(TAG, "停止录音失败", e); } finally { audioRecord = null; } } if (scheduler != null) { try { scheduler.shutdownNow(); if (!scheduler.awaitTermination(500, TimeUnit.MILLISECONDS)) { Log.w(TAG, "录音线程池未正常关闭"); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } finally { scheduler = null; } } } /** * 发送JSON格式的数据包 * @param type 数据包类型 * @param data 数据内容(可以为null) */ private void sendJsonPacket(String type, Object data) { if (clientSocket == null || clientSocket.isClosed() || socketWriter == null) { return; } try { JSONObject packet = new JSONObject(); packet.put("type", type); if (data != null) { packet.put("data", data); } synchronized (this) { if (socketWriter != null) { socketWriter.write(packet.toString()); socketWriter.write("\n\n"); socketWriter.flush(); } } } catch (Exception e) { Log.e(TAG, "发送数据包失败: " + type, e); } } /** * 发送错误消息 * @param message 错误信息 */ private void sendErrorMessage(String message) { handler.obtainMessage(0x16, message).sendToTarget(); } /** * 检查权限 */ 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); } } /** * 启动服务器 * @param port 监听端口 */ 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); } 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(); } }); } /** * 启动Socket监听 */ private void startSocketListener() { executorService.execute(() -> { while (true) { if (clientSocket != null && !clientSocket.isClosed()) { try { BufferedReader reader = new BufferedReader( new InputStreamReader(clientSocket.getInputStream(), "UTF-8")); StringBuilder packetBuilder = new StringBuilder(); String line; while ((line = reader.readLine()) != null) { if (line.isEmpty()) { // 收到两个换行符,表示一个数据包结束 if (packetBuilder.length() > 0) { String packet = packetBuilder.toString(); Log.d(TAG, "收到数据包: " + packet); try { JSONObject command = new JSONObject(packet); String type = command.getString("type"); switch (type) { case "playSound": String base64Data = command.getString("data"); Message msg = handler.obtainMessage(0x24, base64Data); handler.sendMessage(msg); break; case "pauseSound": handler.sendEmptyMessage(0x20); break; case "stopSound": handler.sendEmptyMessage(0x21); break; case "resumeSound": handler.sendEmptyMessage(0x22); break; case "clearSounds": handler.sendEmptyMessage(0x23); break; default: Log.w(TAG, "未知指令类型: " + type); } } catch (JSONException e) { Log.e(TAG, "JSON解析失败", e); } // 重置包构建器 packetBuilder.setLength(0); } } else { // 添加数据到当前包 packetBuilder.append(line); } } } catch (IOException e) { Log.e(TAG, "Socket读取失败", e); } } else { try { Thread.sleep(500); } catch (InterruptedException e) { Thread.currentThread().interrupt(); break; } } } }); } /** * 关闭服务器Socket */ private void closeServerSocket() { try { if (serverSocket != null && !serverSocket.isClosed()) { serverSocket.close(); } } catch (IOException e) { Log.w(TAG, "关闭ServerSocket失败", e); } } /** * TTS初始化回调 */ @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; } } } /** * 活动销毁时调用 */ @Override protected void onDestroy() { super.onDestroy(); isServerRunning = false; if (ttsEngine != null) { ttsEngine.stop(); ttsEngine.shutdown(); } closeServerSocket(); closeSocket(clientSocket); // 停止所有录音和播放 stopRecording(); stopPlayback(); // 优雅关闭线程池 executorService.shutdown(); try { if (!executorService.awaitTermination(800, TimeUnit.MILLISECONDS)) { executorService.shutdownNow(); } } catch (InterruptedException e) { executorService.shutdownNow(); Thread.currentThread().interrupt(); } // 确保所有资源释放 releaseAudioResources(); } /** * 关闭Socket * @param socket 要关闭的Socket */ 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; } } } } 在这个页面添加点样式。录音时候带些波浪回馈,美化页面 然后把完整的安卓代码给我
最新发布
07-11
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值