AndroidApp/PhotoMonitoringService.java

575 lines
21 KiB
Java

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