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; } }