AndroidApp/RecordingService.java

431 lines
15 KiB
Java
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

package com.example.myapplication.Service;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.content.pm.ServiceInfo;
import android.location.Location;
import android.media.MediaRecorder;
import android.os.Build;
import android.os.Handler;
import android.os.IBinder;
import android.os.Looper;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.RequiresApi;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.app.NotificationManagerCompat;
import androidx.lifecycle.Observer;
import androidx.lifecycle.ViewModelProvider;
import androidx.room.Room;
import com.example.myapplication.DataBase.AppDatabase;
import com.example.myapplication.R;
import com.example.myapplication.Tool.BackgroundToast;
import com.example.myapplication.model.AudioEntity;
import com.example.myapplication.model.SharedDataManager;
import java.io.File;
import java.io.IOException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import io.reactivex.disposables.Disposable;
import okhttp3.MediaType;
import okhttp3.MultipartBody;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.Response;
public class RecordingService extends Service implements SharedDataManager.DataChangeListener {
private static final String CHANNEL_ID = "recording_channel";
private static final int NOTIFICATION_ID = 2;
private MediaRecorder mediaRecorder;
private String currentAudioPath;
private Handler handler;
private Runnable updateTimerRunnable;
private AppDatabase db;
private AppDatabase db2;
private final ExecutorService executor = Executors.newFixedThreadPool(4);
private String ImageId="-1";
private String currentToken="-1";
private SharedDataManager dataManager;
@Override
public IBinder onBind(Intent intent) {
return null;
}
private final OkHttpClient httpClient = new OkHttpClient.Builder()
.connectTimeout(30, TimeUnit.SECONDS) // 连接超时
.readTimeout(60, TimeUnit.SECONDS) // 读取超时
.writeTimeout(60, TimeUnit.SECONDS) // 写入超时
.build();
private int recordingSeconds = 0;
@RequiresApi(api = Build.VERSION_CODES.R)
@Override
public void onCreate() {
super.onCreate();
try {
createNotificationChannel();
// 启动前台服务必须5秒内调用startForeground
Notification notification = buildNotification("录音服务运行中");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
// Android 10+ 需要指定类型
startForeground(NOTIFICATION_ID, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else {
// Android 8.0-9.0
startForeground(NOTIFICATION_ID, notification);
}
} catch (Exception e) {
Toast.makeText(this,"错误:"+e,Toast.LENGTH_LONG).show();
stopSelf();
}
handler = new Handler(Looper.getMainLooper());
db = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "image-database").build();
db2 = Room.databaseBuilder(getApplicationContext(),
AppDatabase.class, "image-database2").build();
createNotificationChannel();
// 在 onCreate() 中改进 ViewModel 初始化
// 观察LiveData变化
// 在类成员变量中添加
dataManager=SharedDataManager.getInstance();
dataManager.addListener(this);
currentToken = dataManager.getToken();
ImageId=dataManager.getImageId();
// 在onCreate()中改为使用成员变量观察
}
@Override
public void onImageIdChanged(String newId) {
ImageId=newId;
}
@Override
public void onTokenChanged(String newToken) {
currentToken = newToken;
Log.d("Service", "Token updated: " + newToken);
}
@Override
public void onUserChanged(String newUser) {
}
@Override
public void onAudioPathChanged(String newAudioPath) {
}
@Override
public void onProjectIdChanged(String newProjectId) {
}
@Override
public void onUnitChanged(String newUnit) {
}
@Override
public void onBladeChanged(int newBlade) {
}
@Override
public void onPartidChanged(String newPartid) {
}
@Override
public void onChooseImageSourceChanged(String newChooseImageSource) {
}
@Override
public void onUnitNameChanged(String newUnitName) {
}
@Override
public void onIsRecordingChanged(boolean newisRecording) {
}
@Override
public void onUploadingStatusChanged(boolean isUploading) {
}
@Override
public void onLocationChanged(Location newLocation) {
}
@Override
public void onOtherMsgChanged(String msg) {
}
// 获取当前数据(可选,如果 Activity 需要读取)
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (intent != null && "start_recording".equals(intent.getAction())) {
startRecording();
} else if (intent != null && "stop_recording".equals(intent.getAction())) {
stopRecording();
}
return START_REDELIVER_INTENT;
}
private void startRecording() {
try {
dataManager.setIsRecording(true);
// 1. 设置录音文件路径
currentAudioPath = getExternalCacheDir().getAbsolutePath()
+ "/" + System.currentTimeMillis() + ".3gp";
dataManager.setAudioPath(currentAudioPath);
// 2. 初始化MediaRecorder
mediaRecorder = new MediaRecorder();
mediaRecorder.setAudioSource(MediaRecorder.AudioSource.MIC);
mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
mediaRecorder.setAudioEncoder(MediaRecorder.AudioEncoder.AMR_NB);
mediaRecorder.setOutputFile(currentAudioPath);
mediaRecorder.prepare();
mediaRecorder.start();
// 3. 启动前台服务
startForegroundService();
// 4. 开始计时更新通知
startUpdatingTimer();
} catch (Exception e) {
Toast.makeText(this, "录音失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
stopSelf();
}
}
private void startForegroundService() {
Notification notification = buildNotification("录音中... 00:00");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(NOTIFICATION_ID, notification,
ServiceInfo.FOREGROUND_SERVICE_TYPE_MICROPHONE);
} else {
startForeground(NOTIFICATION_ID, notification);
}
}
private void startUpdatingTimer() {
updateTimerRunnable = new Runnable() {
@Override
public void run() {
recordingSeconds++;
String time = String.format("%02d:%02d",
recordingSeconds / 60, recordingSeconds % 60);
updateNotification("录音中... " + time);
handler.postDelayed(this, 1000);
}
};
handler.post(updateTimerRunnable);
}
private void uploadAudio(String imageId, String audioPath, String token) {
// 1. 参数校验(在调用线程执行,避免不必要的后台任务)
if (audioPath == null || audioPath.equals("0")) {
BackgroundToast.show(this, "文件路径为空");
return;
}
File audioFile = new File(audioPath);
if (!audioFile.exists()) {
BackgroundToast.show(this, "文件不存在");
return;
}
// 2. 在后台线程执行上传逻辑
executor.execute(() -> {
// 3. 构建请求体(已在后台线程)
RequestBody requestBody = new MultipartBody.Builder()
.setType(MultipartBody.FORM)
.addFormDataPart("file", audioFile.getName(),
RequestBody.create(audioFile, MediaType.parse("audio/*")))
.build();
// 4. 构建请求
Request.Builder requestBuilder = new Request.Builder()
.url("http://pms.dtyx.net:9158/audio/upload/" + imageId)
.post(requestBody);
if (token != null && !token.isEmpty()) {
requestBuilder.addHeader("Authorization", token);
}
// 5. 同步执行请求try-with-resources 自动关闭 Response
try (Response response = httpClient.newCall(requestBuilder.build()).execute()) {
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.format(new Date());
if (response.isSuccessful()) {
// 上传成功
executor.execute(()->{ db.AudioDao().deleteByPath(audioPath);
db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,true));});
BackgroundToast.show(this, "录音上传成功: " + imageId);
stopSelf();
} else {
// 上传失败HTTP 错误)
executor.execute(()->{ db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime,false));});
BackgroundToast.show(this,
"录音上传失败: " + response.code() + " " + response.message());
stopSelf();
}
} catch (IOException e) {
// 网络异常
String currentTime = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss", Locale.getDefault())
.format(new Date());
executor.execute(() -> {
db.AudioDao().insert(new AudioEntity(currentAudioPath, ImageId, currentToken, false));
});
BackgroundToast.show(this, "录音上传失败: " + e.getMessage());
Log.e("录音上传失败:",e.toString());
stopSelf();
}
});
}
private void stopRecording() {
try {
// 1. 停止录音
if (mediaRecorder != null) {
mediaRecorder.stop();
mediaRecorder.release();
mediaRecorder = null;
}
dataManager.setIsRecording(false);
// 2. 停止计时器
if (handler != null && updateTimerRunnable != null) {
handler.removeCallbacks(updateTimerRunnable);
}
// 3. 更新通知
updateNotification("录音已保存");
// 4. 上传录音文件(如果有需要)
if (currentAudioPath != null && ImageId != "-1") {
BackgroundToast.show(this,"开始上传录音了");
uploadAudio(ImageId, currentAudioPath, currentToken);
} else
{
BackgroundToast.show(this,"Imageid不存在上传录音失败");
executor.execute(() -> {
db.AudioDao().insert(new AudioEntity(currentAudioPath, ImageId, currentToken, false));
});
}
// 5. 不立即停止服务,等待上传完成
// 可以通过其他机制如广播或LiveData在上传完成后通知服务停止
} catch (Exception e) {
Toast.makeText(this, "停止录音失败: " + e.getMessage(), Toast.LENGTH_SHORT).show();
dataManager.setIsRecording(false);
stopSelf();
}
}
private void createNotificationChannel() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
NotificationChannel channel = new NotificationChannel(
"recording_channel",
"Recording Service",
NotificationManager.IMPORTANCE_LOW);
NotificationManager manager = getSystemService(NotificationManager.class);
if (manager != null) {
manager.createNotificationChannel(channel);
}
}
}
public Notification buildNotification(String text) {
// 确保图标有效R.drawable.ic_mic_on必须存在
return new NotificationCompat.Builder(this, CHANNEL_ID)
.setSmallIcon(R.drawable.ic_mic_on)
.setContentTitle("录音服务")
.setContentText(text)
.setPriority(NotificationCompat.PRIORITY_LOW)
.build();
}
public void updateNotification(String text) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
if (ActivityCompat.checkSelfPermission(this,
android.Manifest.permission.POST_NOTIFICATIONS)
!= PackageManager.PERMISSION_GRANTED) {
// 无权限时不显示通知(避免崩溃)
return;
}
}
NotificationManagerCompat.from(this)
.notify(NOTIFICATION_ID, buildNotification(text));
}
@Override
public void onDestroy() {
super.onDestroy();
// 2. 停止并释放MediaRecorder
if (mediaRecorder != null) {
try {
mediaRecorder.stop();
mediaRecorder.release();
} catch (IllegalStateException e) {
Log.e("Service", "MediaRecorder释放异常", e);
}
mediaRecorder = null;
}
// 3. 移除Handler回调
if (handler != null && updateTimerRunnable != null) {
handler.removeCallbacks(updateTimerRunnable);
}
dataManager.removeListener(this);
// 5. 关闭数据库如果Room支持
if (db != null) {
db.close();
}
if (db2 != null) {
db2.close();
}
}
}