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.content.SharedPreferences; 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.appcompat.app.AlertDialog; 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 org.json.JSONObject; 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.HttpUrl; 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 long lastSuccessToastTime = 0; private static final long TOAST_THROTTLE_INTERVAL = 1500; // 状态标志 private boolean isMonitoring = false; private boolean isUploading = false; private boolean isRecording = false; private int successCount = 0; private int totalUploadedCount = 0; // 新增:总上传成功计数 // 组件 private ContentObserver contentObserver; private final ExecutorService executor = Executors.newFixedThreadPool(5); 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="-1"; private String imgpath="-1"; private String partid="-1"; private String UserId; @Override public void onImageIdChanged(String newId) { } @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; } @Override public void onLocationChanged(Location newLocation) { lastKnownLocation=newLocation; } @Override public void onOtherMsgChanged(String msg) { } SharedDataManager dataManager; @Override public void onCreate() { super.onCreate(); createNotificationChannel(); SharedPreferences prefs = getSharedPreferences("WeatherPrefs", MODE_PRIVATE); Weather = prefs.getString("weather", "0"); Temperator = prefs.getString("temperature", "0"); Humidity = prefs.getString("humidity", "0"); SharedPreferences sharedPreferences = getSharedPreferences("LoginPrefs", Context.MODE_PRIVATE); // 初始化数据库 UserId=sharedPreferences.getString("userid","-1"); 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(); // 初始化时从数据库获取已上传成功的数量 totalUploadedCount = 0; updateNotification("partid"+partid); } 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(); this.lastKnownLocation=dataManager.getLocation(); } @Override public int onStartCommand(Intent intent, int flags, int startId) { 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; 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; } isMonitoring=false; } } private String getPartName(int b) { return "叶片" +b; } 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; long time = cursor.getLong(2) * 1000; double latitude = cursor.getDouble(3); double longitude = cursor.getDouble(4); double altitude = 0.0; 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; lastPhotoId = Math.max(lastPhotoId, id); if (isNetworkAvailable() && !isRecording) { uploadNewPhoto(path, time, audioPath); } else { queuePhotoForLater(path, time, audioPath, latitude, longitude, altitude); } } private void uploadNewPhoto(String path, long time, String audioPath) { 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()) { return; } double latitude = location != null ? location.getLatitude() : 0; double longitude = location != null ? location.getLongitude() : 0; double altitude = location != null ? location.getAltitude() : 0; // 构建请求URL HttpUrl.Builder urlBuilder = HttpUrl.parse("http://pms.dtyx.net:9158/image/" + imageSource + "/upload/" + partId) .newBuilder() .addQueryParameter("collectorId",UserId) .addQueryParameter("collectorName", user) .addQueryParameter("humidness", String.valueOf(Humidity)) .addQueryParameter("shootingDistance", String.valueOf(0)) // 根据需要设置实际值 .addQueryParameter("temperatureMax", String.valueOf(Temperator)) .addQueryParameter("temperatureMin", String.valueOf(Temperator)) .addQueryParameter("weather", Weather) .addQueryParameter("windLevel", String.valueOf(0)); // 根据需要设置实际值 // 创建请求体 RequestBody requestBody = new MultipartBody.Builder() .setType(MultipartBody.FORM) .addFormDataPart("file", imageFile.getName(), RequestBody.create(imageFile, MediaType.parse("image/*"))) .build(); Request request = new Request.Builder() .url(urlBuilder.build()) .post(requestBody) .addHeader("Authorization", token) .addHeader("Content-Type", "application/x-www-form-urlencoded") .build(); try (Response response = httpClient.newCall(request).execute()) { if (response.isSuccessful()) { // 解析响应数据 String responseData = response.body().string(); JSONObject jsonResponse = new JSONObject(responseData); JSONObject data = jsonResponse.optJSONObject("data"); System.out.println(responseData); String imageId = data != null ? data.optString("imageId") : ""; dataManager.setImageId(imageId); dataManager.setOthermsg("partid:"+partid+"\n"+"imageid:"+imageId+"\n"+"response:"+responseData); db.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, true, unitName, Temperator, Humidity, Weather, ChooseImageSource, partId)); totalUploadedCount++; updateNotification(imageId); Intent intent = new Intent("RESPONSE_ACTION"); intent.putExtra("response", response.toString()); sendBroadcast(intent); long currentTime = System.currentTimeMillis(); if (currentTime - lastSuccessToastTime > TOAST_THROTTLE_INTERVAL) { BackgroundToast.show(this, "图片上传成功,已上传" + totalUploadedCount + "张"); lastSuccessToastTime = currentTime; } } else { db2.imageDao().insert(new ImageEntity(filePath, time, latitude, longitude, altitude, user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather, ChooseImageSource, partId)); dataManager.setImageId("-1"); } } } catch (Exception e) { db2.imageDao().insert(new ImageEntity(filePath, time, 0, 0, 0, user, audioPath, project, unit, blade, false, unitName, Temperator, Humidity, Weather, ChooseImageSource, partId)); dataManager.setImageId("-1"); } }); } private void updateNotification(String msg) { String content = "已成功上传 " + totalUploadedCount + " 张照片"+msg; Notification notification = buildNotification("照片上传服务运行中", content); NotificationManager manager = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE); if (manager != null) { manager.notify(NOTIFICATION_ID, notification); } } 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"); PendingIntent stopPendingIntent = PendingIntent.getBroadcast( this, 0, stopIntent, PendingIntent.FLAG_IMMUTABLE | PendingIntent.FLAG_UPDATE_CURRENT); 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); cursor.close(); } return id; } }