diff --git a/ImageEntity.java b/ImageEntity.java index 45da439..6525d28 100644 --- a/ImageEntity.java +++ b/ImageEntity.java @@ -24,8 +24,8 @@ import androidx.room.PrimaryKey; public String humidity="0"; // 湿度 public String weather="0"; // 天气状况 public String imageSource="-1"; - - public ImageEntity(String path, long time, double latitude, double longitude, double altitude, String user, String audioPath, String project, String unit, int blade, boolean b,String unitName,String temperature,String humidity,String weather,String imageSource) { + public String partid; + public ImageEntity(String path, long time, double latitude, double longitude, double altitude, String user, String audioPath, String project, String unit, int blade, boolean b,String unitName,String temperature,String humidity,String weather,String imageSource,String partid) { this.path = path; this.time = time; @@ -43,6 +43,7 @@ import androidx.room.PrimaryKey; this.humidity=humidity; this.unitName=unitName; this.imageSource=imageSource; + this.partid=partid; } diff --git a/PhotoMonitoringService.java b/PhotoMonitoringService.java new file mode 100644 index 0000000..f53777e --- /dev/null +++ b/PhotoMonitoringService.java @@ -0,0 +1,575 @@ +package com.example.myapplication.Service; + +import android.app.Notification; +import android.app.NotificationChannel; +import android.app.NotificationManager; +import android.app.PendingIntent; +import android.app.Service; +import android.content.Context; +import android.content.Intent; +import android.database.ContentObserver; +import android.database.Cursor; +import android.location.Location; +import android.location.LocationManager; +import android.media.ExifInterface; +import android.net.ConnectivityManager; +import android.net.NetworkCapabilities; +import android.net.Uri; +import android.os.Build; +import android.os.Handler; +import android.os.IBinder; +import android.os.Looper; +import android.provider.MediaStore; +import android.util.Log; + +import androidx.annotation.Nullable; +import androidx.core.app.NotificationCompat; +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.Tool.NotificationReceiver; +import com.example.myapplication.model.ImageEntity; +import com.example.myapplication.model.SharedDataManager; + +import java.io.File; +import java.io.IOException; +import java.util.List; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.TimeUnit; + +import okhttp3.MediaType; +import okhttp3.MultipartBody; +import okhttp3.OkHttpClient; +import okhttp3.Request; +import okhttp3.RequestBody; +import okhttp3.Response; + +public class PhotoMonitoringService extends Service implements SharedDataManager.DataChangeListener { + private static final String CHANNEL_ID = "PhotoMonitoringChannel"; + private static final int NOTIFICATION_ID = 101; + private static final int THUMBNAIL_SIZE = 100; + + // 锁对象 + private final Object monitoringLock = new Object(); + private final Object uploadLock = new Object(); + + // 状态标志 + private boolean isMonitoring = false; + private boolean isUploading = false; + private boolean isRecording = false; + private int successCount = 0; + private int failureCount = 0; + + // 组件 + private ContentObserver contentObserver; + private final ExecutorService executor = Executors.newFixedThreadPool(6); + private final OkHttpClient httpClient = new OkHttpClient.Builder() + .connectTimeout(30, TimeUnit.SECONDS) // 连接超时 + .readTimeout(60, TimeUnit.SECONDS) // 读取超时 + .writeTimeout(60, TimeUnit.SECONDS) // 写入超时 + .build(); + + // 数据库 + private AppDatabase db; + private AppDatabase db2; + + // 位置相关 + private Location lastKnownLocation; + private LocationManager locationManager; + + // 配置参数 + private String token; + private String user; + private String projectId; + private String unit; + private int blade; + private String unitName; + private String Temperator; + private String Humidity; + private String Weather; + private String ChooseImageSource; + + // 其他状态 + private long lastPhotoId = 0; + private String currentAudioPath="0"; + private String imgpath="-1"; + private String partid="-1"; + + // 回调接口 + private MonitoringCallback callback; + + @Override + public void onImagePathChanged(String newPath) { + + } + + @Override + public void onTokenChanged(String newToken) { + + } + + @Override + public void onUserChanged(String newUser) { + user=newUser; + } + + @Override + public void onAudioPathChanged(String newAudioPath) { + currentAudioPath=newAudioPath; + } + + @Override + public void onProjectIdChanged(String newProjectId) { + projectId=newProjectId; + } + + @Override + public void onUnitChanged(String newUnit) { + unit=newUnit; + } + + @Override + public void onBladeChanged(int newBlade) { + blade=newBlade; + } + + @Override + public void onPartidChanged(String newPartid) { + partid=newPartid; + } + + @Override + public void onChooseImageSourceChanged(String newChooseImageSource) { + ChooseImageSource=newChooseImageSource; + } + + @Override + public void onUnitNameChanged(String newUnitName) { + unitName=newUnitName; + } + + @Override + public void onIsRecordingChanged(boolean newisRecording) { + isRecording=newisRecording; + } + + @Override + public void onUploadingStatusChanged(boolean isUploading) { + this.isUploading=isUploading; + } + + public interface MonitoringCallback { + void onUploadSuccess(String filePath); + void onUploadFailure(String filePath, String error); + void onStatusChanged(boolean isMonitoring); + } + SharedDataManager dataManager ; + @Override + public void onCreate() { + super.onCreate(); + createNotificationChannel(); + + // 初始化数据库 + db = Room.databaseBuilder(getApplicationContext(), + AppDatabase.class, "image-database").build(); + db2 = Room.databaseBuilder(getApplicationContext(), + AppDatabase.class, "image-database2").build(); + + // 初始化位置管理器 + locationManager = (LocationManager) getSystemService(Context.LOCATION_SERVICE); +dataManager=SharedDataManager.getInstance(); + + dataManager.addListener(this); + updateFromSharedData(); + } + private void updateFromSharedData() { + dataManager = SharedDataManager.getInstance(); + this.token = dataManager.getToken(); + this.user = dataManager.getUser(); + this.currentAudioPath = dataManager.getAudioPath(); + this.projectId = dataManager.getProjectId(); + this.unit = dataManager.getUnit(); + this.blade = dataManager.getBlade(); + this.ChooseImageSource = dataManager.getChooseImageSource(); + this.unitName = dataManager.getUnitName(); + this.partid=dataManager.getPartid(); + this.isUploading=dataManager.getisUploading(); + } + @Override + public int onStartCommand(Intent intent, int flags, int startId) { + // 从intent中获取配置参数 + if (intent != null && "start_monitoring".equals(intent.getStringExtra("command"))) { + startMonitoring(); + } + + startForeground(NOTIFICATION_ID, buildNotification("服务正在运行", "监听照片变化中")); + + return START_STICKY; + } + + @Nullable + @Override + public IBinder onBind(Intent intent) { + return null; + } + + @Override + public void onDestroy() { + stopMonitoring(); + super.onDestroy(); + } + + // 公共接口方法 + public void startMonitoring() { + + synchronized (monitoringLock) { + if (isMonitoring) return; + + lastPhotoId = getLastPhotoId(); + + contentObserver = new ContentObserver(new Handler(Looper.getMainLooper())) { + @Override + public void onChange(boolean selfChange, Uri uri) { + super.onChange(selfChange, uri); + if (!isRecording && uri != null && uri.toString().contains("images/media")) { + checkNewPhotos(); + } + } + }; + + getContentResolver().registerContentObserver( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + true, + contentObserver + ); + + isMonitoring = true; + if (callback != null) { + callback.onStatusChanged(true); + } + + // 检查已有照片 + executor.execute(() -> { + List history = db2.imageDao().getAll(); + if (history != null && !history.isEmpty()) { + synchronized (uploadLock) { + for (ImageEntity image : history) { + String partname = getPartName(image.blade); + if (partid == null) partid = partname; + uploadPhoto(image.path, image.time, lastKnownLocation, image.user, + image.audioPath, image.project, image.unit, image.blade, + partid, image.imageSource, image.unitName); + } + } + } + db2.imageDao().deleteAll(); + }); + } + } + + public void stopMonitoring() { + synchronized (monitoringLock) { + if (!isMonitoring) return; + + if (contentObserver != null) { + getContentResolver().unregisterContentObserver(contentObserver); + contentObserver = null; + } + if (callback != null) { + callback.onStatusChanged(false); + } + isMonitoring=false; + } + } + private String getPartName(int b) + { + return "叶片" +b; + } + public void setCallback(MonitoringCallback callback) { + this.callback = callback; + } + + public boolean isMonitoring() { + return isMonitoring; + } + + // 私有方法 + private void checkNewPhotos() { + String[] projection = { + MediaStore.Images.Media._ID, + MediaStore.Images.Media.DATA, + MediaStore.Images.Media.DATE_ADDED, + MediaStore.Images.Media.DATE_TAKEN, + MediaStore.Images.Media.LATITUDE, + MediaStore.Images.Media.LONGITUDE + }; + + String selection = MediaStore.Images.Media._ID + " > ? AND " + + MediaStore.Images.Media.DATE_TAKEN + " > 0 AND " + + "(" + MediaStore.Images.Media.DATA + " LIKE '%/DCIM/%' OR " + + MediaStore.Images.Media.DATA + " LIKE '%/Camera/%')"; + String[] selectionArgs = new String[]{String.valueOf(lastPhotoId)}; + + try (Cursor cursor = getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + projection, + selection, + selectionArgs, + MediaStore.Images.Media._ID + " DESC" + )) { + if (cursor != null) { + while (cursor.moveToNext()) { + processNewPhoto(cursor); + } + } + } catch (Exception e) { + Log.e("PhotoMonitoringService", "Error checking new photos", e); + } + } + + private void processNewPhoto(Cursor cursor) { + long id = cursor.getLong(0); + String path = cursor.getString(1); + imgpath = path; + dataManager.setImagePath(imgpath); + long time = cursor.getLong(2) * 1000; + double latitude = cursor.getDouble(3); + double longitude = cursor.getDouble(4); + double altitude = 0.0; + + try { + Location lastLocation = locationManager.getLastKnownLocation(LocationManager.GPS_PROVIDER); + if (lastLocation != null) { + lastKnownLocation = lastLocation; + } + } catch (SecurityException e) { + Log.w("PhotoMonitoringService", "Location permission not granted"); + } + + if (lastKnownLocation != null) { + latitude = lastKnownLocation.getLatitude(); + longitude = lastKnownLocation.getLongitude(); + altitude = lastKnownLocation.getAltitude(); + } + + try { + ExifInterface exif = new ExifInterface(path); + String altitudeStr = exif.getAttribute(ExifInterface.TAG_GPS_ALTITUDE); + if (altitudeStr != null) { + altitude = Double.parseDouble(altitudeStr); + } + } catch (Exception e) { + Log.e("PhotoMonitoringService", "Error reading EXIF data", e); + } + + String audioPath = currentAudioPath == null ? "0" : currentAudioPath; + lastPhotoId = Math.max(lastPhotoId, id); + + if (isNetworkAvailable() && !isRecording) { + uploadNewPhoto(path, time, audioPath, latitude, longitude, altitude); + } else { + queuePhotoForLater(path, time, audioPath, latitude, longitude, altitude); + } + + currentAudioPath = null; + } + + private void uploadNewPhoto(String path, long time, String audioPath, + double latitude, double longitude, double altitude) { + executor.execute(() -> { + List history = db2.imageDao().getAll(); + if (history != null && !history.isEmpty()) { + synchronized (uploadLock) { + for (ImageEntity image : history) { + String partname = getPartName(image.blade); + String ipartid = image.partid; + if (ipartid == null) ipartid = partname; + uploadPhoto(image.path, image.time, lastKnownLocation, + image.user, image.audioPath, image.project, + image.unit, image.blade, ipartid, image.imageSource, image.unitName); + } + } + } + db2.imageDao().deleteAll(); + + String partname = getPartName(blade); + + if (partid == null) partid = partname; + + uploadPhoto(path, time, lastKnownLocation, user, + audioPath, projectId, unit, blade, partid, ChooseImageSource, unitName); + }); + } + + private void queuePhotoForLater(String path, long time, String audioPath, + double latitude, double longitude, double altitude) { + executor.execute(() -> { + String partname = getPartName(blade); + + if (partid == null) partid = partname; + + db2.imageDao().insert(new ImageEntity(path, time, latitude, longitude, altitude, + user, audioPath, projectId, unit, blade, false, unitName, + Temperator, Humidity, Weather, ChooseImageSource,partid)); + + + }); + } + + private void uploadPhoto(String filePath, long time, Location location, String user, + String audioPath, String project, String unit, int blade, + String partid, String imageSource, String unitName) { + synchronized (uploadLock) { + if (isUploading) { + queuePhotoForLater(filePath, time, audioPath, + location != null ? location.getLatitude() : 0, + location != null ? location.getLongitude() : 0, + location != null ? location.getAltitude() : 0); + return; + } + + } + + executor.execute(() -> { + try { + File imageFile = new File(filePath); + if (!imageFile.exists()) { + if (callback != null) { + callback.onUploadFailure(filePath, "File does not exist"); + } + return; + } + + double latitude = location != null ? location.getLatitude() : 0; + double longitude = location != null ? location.getLongitude() : 0; + double altitude = location != null ? location.getAltitude() : 0; + + MultipartBody.Builder builder = new MultipartBody.Builder() + .setType(MultipartBody.FORM) + .addFormDataPart("image", imageFile.getName(), + RequestBody.create(imageFile, MediaType.parse("image/*"))) + .addFormDataPart("time", String.valueOf(time)) + .addFormDataPart("latitude", String.valueOf(latitude)) + .addFormDataPart("longitude", String.valueOf(longitude)) + .addFormDataPart("altitude", String.valueOf(altitude)) + .addFormDataPart("uploadUser", user) + .addFormDataPart("projectId", project) + .addFormDataPart("turbineId", unit) + .addFormDataPart("partId", partid) + .addFormDataPart("imageSource", imageSource); + + RequestBody requestBody = builder.build(); + Request request = new Request.Builder() + .url("http://pms.dtyx.net:9158/common/upload-image/file") + .post(requestBody) + .addHeader("Authorization", token) + .build(); + + try (Response response = httpClient.newCall(request).execute()) { + if (response.isSuccessful()) { + db.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, + user, audioPath, project, unit, blade, true, unitName, + Temperator, Humidity, Weather, ChooseImageSource,partid)); + + if (callback != null) { + successCount++; + Log.d("上传成功:","已经上传成功"+successCount+"张图片"); + BackgroundToast.show(this,"已经上传成功"+successCount+"张图片"); + } + } else { + db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, + user, audioPath, project, unit, blade, false, unitName, + Temperator, Humidity, Weather, ChooseImageSource,partid)); + + if (callback != null) { + failureCount++; + BackgroundToast.show(this,"上传失败"+response.code()+response.message()); + } + } + } + } catch (IOException e) { + db2.imageDao().insert(new ImageEntity(filePath, time, 0, 0, 0, + user, audioPath, project, unit, blade, false, unitName, + Temperator, Humidity, Weather, ChooseImageSource,partid)); + + if (callback != null) { + BackgroundToast.show(this,"io错误"+e.getMessage()); + } + } finally { + + } + }); + } + + private boolean isNetworkAvailable() { + ConnectivityManager cm = (ConnectivityManager) getSystemService(Context.CONNECTIVITY_SERVICE); + if (cm == null) return false; + + NetworkCapabilities capabilities = cm.getNetworkCapabilities(cm.getActiveNetwork()); + return capabilities != null && + (capabilities.hasTransport(NetworkCapabilities.TRANSPORT_WIFI) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_CELLULAR) || + capabilities.hasTransport(NetworkCapabilities.TRANSPORT_ETHERNET)); + } + + + + private void createNotificationChannel() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + NotificationChannel channel = new NotificationChannel( + CHANNEL_ID, + "照片上传服务", + NotificationManager.IMPORTANCE_HIGH // 使用高优先级确保通知显示 + ); + channel.setDescription("显示照片上传状态和进度"); + NotificationManager manager = getSystemService(NotificationManager.class); + manager.createNotificationChannel(channel); + } + } + private Notification buildNotification(String title, String content) { + return new NotificationCompat.Builder(this, CHANNEL_ID) + .setContentTitle(title) + .setContentText(content) + .setSmallIcon(R.drawable.ic_upload) // 使用上传图标 + .setPriority(NotificationCompat.PRIORITY_HIGH) + .setOngoing(true) // 设置为持续通知 + .setOnlyAlertOnce(true) // 更新时不重复提醒 + .addAction(createStopAction()) + .build(); + } + private NotificationCompat.Action createStopAction() { + Intent stopIntent = new Intent(this, NotificationReceiver.class); + stopIntent.setAction("ACTION_STOP_SERVICE"); + // 修改这一行,添加 FLAG_IMMUTABLE 或 FLAG_MUTABLE + PendingIntent stopPendingIntent = PendingIntent.getBroadcast( + this, + 0, + stopIntent, + PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); // 添加 FLAG_IMMUTABLE + + return new NotificationCompat.Action( + R.drawable.ic_stop, "停止", stopPendingIntent); + } + + + private long getLastPhotoId() { + long id = -1; + Cursor cursor = getContentResolver().query( + MediaStore.Images.Media.EXTERNAL_CONTENT_URI, + new String[]{"MAX(" + MediaStore.Images.Media._ID + ")"}, + null, + null, + null + ); + + if (cursor != null && cursor.moveToFirst()) { + id = cursor.getLong(0); // MAX(_ID) 的结果在索引 0 + cursor.close(); + } + return id; + } + +} \ No newline at end of file diff --git a/RecordingService.java b/RecordingService.java index 05eee74..7f941e2 100644 --- a/RecordingService.java +++ b/RecordingService.java @@ -7,8 +7,12 @@ import android.app.Service; import android.content.Intent; import android.content.pm.PackageManager; import android.content.pm.ServiceInfo; +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; @@ -16,12 +20,60 @@ 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; -public class RecordingService extends Service { +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 = 1; + 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 currentImagePath="-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() { @@ -43,20 +95,250 @@ public class RecordingService extends Service { 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); + currentImagePath = dataManager.getImagePath(); + currentToken = dataManager.getToken(); +// 在onCreate()中改为使用成员变量观察 + } + @Override + public void onImagePathChanged(String newPath) { + currentImagePath = newPath; + Log.d("Service", "ImagePath updated: " + newPath); + } + + @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) { + + + } + + + // 获取当前数据(可选,如果 Activity 需要读取) @Override public int onStartCommand(Intent intent, int flags, int startId) { - return START_STICKY; + if (intent != null && "start_recording".equals(intent.getAction())) { + startRecording(); + } else if (intent != null && "stop_recording".equals(intent.getAction())) { + stopRecording(); + } + return START_REDELIVER_INTENT; } - @Override - public IBinder onBind(Intent intent) { - return null; + private void startRecording() { + try { + // 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()) { + // 上传成功 + db.AudioDao().delete(new AudioEntity(audioPath, imageId, currentTime)); + db2.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + BackgroundToast.show(this, "录音上传成功: " + imageId); + stopSelf(); + } else { + // 上传失败(HTTP 错误) + db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + 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()); + db.AudioDao().insert(new AudioEntity(audioPath, imageId, currentTime)); + BackgroundToast.show(this, "录音上传失败: " + e.getMessage()); + Log.e("录音上传失败:",e.toString()); + stopSelf(); + } + }); + } + + private void stopRecording() { + try { + // 1. 停止录音 + if (mediaRecorder != null) { + mediaRecorder.stop(); + mediaRecorder.release(); + mediaRecorder = null; + } + + // 2. 停止计时器 + if (handler != null && updateTimerRunnable != null) { + handler.removeCallbacks(updateTimerRunnable); + } + + // 3. 更新通知 + updateNotification("录音已保存"); + + // 4. 上传录音文件(如果有需要) + if (currentAudioPath != null) { + uploadAudio(currentImagePath, currentAudioPath, currentToken); + } + + // 5. 不立即停止服务,等待上传完成 + // 可以通过其他机制(如广播或LiveData)在上传完成后通知服务停止 + + } catch (Exception e) { + Toast.makeText(this, "停止录音失败: " + e.getMessage(), Toast.LENGTH_SHORT).show(); + stopSelf(); + } + } + + + private void createNotificationChannel() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { NotificationChannel channel = new NotificationChannel( @@ -70,7 +352,7 @@ public class RecordingService extends Service { } } - private Notification buildNotification(String text) { + public Notification buildNotification(String text) { // 确保图标有效(R.drawable.ic_mic_on必须存在) return new NotificationCompat.Builder(this, CHANNEL_ID) .setSmallIcon(R.drawable.ic_mic_on) @@ -92,4 +374,35 @@ public class RecordingService extends Service { 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(); + } + } } \ No newline at end of file diff --git a/SharedDataManager.java b/SharedDataManager.java new file mode 100644 index 0000000..ab2010c --- /dev/null +++ b/SharedDataManager.java @@ -0,0 +1,200 @@ +package com.example.myapplication.model; + +import java.util.ArrayList; +import java.util.List; + +// SharedDataManager.java +public class SharedDataManager { + private static SharedDataManager instance; + private String imagePath ="-1"; + private String token="-1"; + private String user="-1"; + private String audioPath="-1"; + private String projectId="-1"; + private String unit="-1"; + private int blade=-1; + private String partid="-1"; + private String chooseImageSource="-1"; + private String unitName="-1"; + private boolean isRecording=false; + private boolean isUploading=false; // 新增上传状态字段 + + private final List listeners = new ArrayList<>(); + + public interface DataChangeListener { + void onImagePathChanged(String newPath); + void onTokenChanged(String newToken); + void onUserChanged(String newUser); + void onAudioPathChanged(String newAudioPath); + void onProjectIdChanged(String newProjectId); + void onUnitChanged(String newUnit); + void onBladeChanged(int newBlade); + void onPartidChanged(String newPartid); + void onChooseImageSourceChanged(String newChooseImageSource); + void onUnitNameChanged(String newUnitName); + void onIsRecordingChanged(boolean newisRecording); + void onUploadingStatusChanged(boolean isUploading); // 新增上传状态变化回调 + + + } + + private SharedDataManager() {} + + public static synchronized SharedDataManager getInstance() { + if (instance == null) { + instance = new SharedDataManager(); + } + return instance; + } + public boolean getIsRecording(){return isRecording;} + public String getImagePath() {return imagePath;} + public String getUser() { return user; } + public String getAudioPath() { return audioPath; } + public String getProjectId() { return projectId; } + public String getUnit() { return unit; } + public int getBlade() { return blade; } + public String getPartid() { return partid; } + public String getChooseImageSource() { return chooseImageSource; } + public String getUnitName() { return unitName; } + public String getToken() {return token;} + public boolean getisUploading() { + return isUploading; + } + + public void setUploading(boolean uploading) { + if (this.isUploading != uploading) { + this.isUploading = uploading; + notifyUploadingStatusChanged(uploading); + } + } + public void setImagePath(String imagePath) { + this.imagePath = imagePath; + notifyImagePathChanged(imagePath); + } + + public void setUser(String user) { + this.user = user; + notifyUserChanged(user); + } + + public void setAudioPath(String audioPath) { + this.audioPath = audioPath; + notifyAudioPathChanged(audioPath); + } + + public void setProjectId(String projectId) { + this.projectId = projectId; + notifyProjectIdChanged(projectId); + } + + public void setUnit(String unit) { + this.unit = unit; + notifyUnitChanged(unit); + } + + public void setBlade(int blade) { + this.blade = blade; + notifyBladeChanged(blade); + } + + public void setPartid(String partid) { + this.partid = partid; + notifyPartidChanged(partid); + } + + public void setChooseImageSource(String chooseImageSource) { + this.chooseImageSource = chooseImageSource; + notifyChooseImageSourceChanged(chooseImageSource); + } + + public void setUnitName(String unitName) { + this.unitName = unitName; + notifyUnitNameChanged(unitName); + } + + + public void setToken(String token) { + this.token = token; + notifyTokenChanged(token); + } + public void setIsRecording(boolean isRecording) { + this.isRecording =isRecording ; + notifyIsRecordingChanged(isRecording); + } + public void addListener(DataChangeListener listener) { + listeners.add(listener); + } + + public void removeListener(DataChangeListener listener) { + listeners.remove(listener); + } + + private void notifyImagePathChanged(String newPath) { + for (DataChangeListener listener : listeners) { + listener.onImagePathChanged(newPath); + } + } + + private void notifyTokenChanged(String newToken) { + for (DataChangeListener listener : listeners) { + listener.onTokenChanged(newToken); + } + } + private void notifyUserChanged(String newUser) { + for (DataChangeListener listener : listeners) { + listener.onUserChanged(newUser); + } + } + + private void notifyAudioPathChanged(String newAudioPath) { + for (DataChangeListener listener : listeners) { + listener.onAudioPathChanged(newAudioPath); + } + } + + private void notifyProjectIdChanged(String newProjectId) { + for (DataChangeListener listener : listeners) { + listener.onProjectIdChanged(newProjectId); + } + } + + private void notifyUnitChanged(String newUnit) { + for (DataChangeListener listener : listeners) { + listener.onUnitChanged(newUnit); + } + } + + private void notifyBladeChanged(int newBlade) { + for (DataChangeListener listener : listeners) { + listener.onBladeChanged(newBlade); + } + } + + private void notifyPartidChanged(String newPartid) { + for (DataChangeListener listener : listeners) { + listener.onPartidChanged(newPartid); + } + } + + private void notifyChooseImageSourceChanged(String newChooseImageSource) { + for (DataChangeListener listener : listeners) { + listener.onChooseImageSourceChanged(newChooseImageSource); + } + } + + private void notifyUnitNameChanged(String newUnitName) { + for (DataChangeListener listener : listeners) { + listener.onUnitNameChanged(newUnitName); + } + } + private void notifyIsRecordingChanged(boolean newIsRecording) { + for (DataChangeListener listener : listeners) { + listener.onIsRecordingChanged(newIsRecording); + } + } + private void notifyUploadingStatusChanged(boolean isUploading) { + for (DataChangeListener listener : listeners) { + listener.onUploadingStatusChanged(isUploading); + } + } +} \ No newline at end of file