package com.kotei.overseas.navi.update;
import static com.kotei.overseas.navi.security.DecryptUtil.dataVerification;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.os.Handler;
import android.os.Looper;
import android.util.Log;
import androidx.annotation.NonNull;
import com.here.sdk.core.engine.SDKNativeEngine;
import com.here.sdk.maploader.MapDownloader;
import com.here.sdk.maploader.MapDownloaderConstructionCallback;
import com.kotei.overseas.navi.business.data.MapDataController;
import com.kotei.overseas.navi.distributeProxy.DistributeGearModuleAdapter;
import com.kotei.overseas.navi.distributeProxy.DistributePowerModuleAdapter;
import com.kotei.overseas.navi.externalInterface.distributeGear.DistributeGearAdapter;
import com.kotei.overseas.navi.navigation.utils.Logger;
import com.kotei.overseas.navi.security.DecryptUtil;
import com.kotei.overseas.navi.security.DfCert;
import java.io.File;
import java.io.IOException;
import java.io.UncheckedIOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.atomic.AtomicBoolean;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;
import java.util.stream.Stream;
//rxjava
import io.reactivex.rxjava3.core.Observable;
import io.reactivex.rxjava3.core.ObservableEmitter;
import io.reactivex.rxjava3.core.ObservableOnSubscribe;
import io.reactivex.rxjava3.core.Observer;
import io.reactivex.rxjava3.disposables.CompositeDisposable;
import io.reactivex.rxjava3.disposables.Disposable;
import io.reactivex.rxjava3.schedulers.Schedulers;
import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
import io.reactivex.rxjava3.subjects.PublishSubject;
/**
* USB离线更新系统
*/
public class USBOfflineUpdater {
private static final String TAG = "USBOfflineUpdater";
// 状态码定义
/**
* 操作成功
*/
public static final int SUCCESS = 0;
/**
* 错误:未检测到USB设备
*/
public static final int ERROR_NO_USB = 1;
/**
* 错误:未找到升级包文件
*/
public static final int ERROR_NO_UPDATE_PACKAGE = 2;
/**
* 错误:电池电量不足(低于安全阈值)
*/
public static final int ERROR_BATTERY_LOW = 3;
/**
* 错误:存储空间不足
*/
public static final int ERROR_STORAGE_INSUFFICIENT = 4;
/**
* 错误:系统正在执行其他升级任务
*/
public static final int ERROR_UPDATE_IN_PROGRESS = 5;
/**
* 错误:文件复制失败(检查存储权限或磁盘状态)
*/
public static final int ERROR_COPY_FAILED = 6;
/**
* 错误:升级包解压失败(文件可能损坏)
*/
public static final int ERROR_EXTRACT_FAILED = 7;
/**
* 错误:用户手动取消操作
*/
public static final int ERROR_USER_CANCELED = 8;
/**
* 错误:未预期的系统异常
*/
public static final int ERROR_UNEXPECTED = 9;
/**
* 错误:升级过程中USB设备被移除
*/
public static final int ERROR_USB_REMOVED = 10;
/**
* 错误:车辆档位未处于停车挡(P档)
*/
public static final int ERROR_VEHICLE_SHIFTED = 11;
/**
* 错误:电池电量极低(无法维持升级过程)
*/
public static final int ERROR_BATTERY_TOO_LOW = 12;
/**
* 错误:文件校验失败(MD5/SHA256校验不匹配)
*/
public static final int ERROR_FILE_VERIFY_FAILED = 13;
/**
* 错误:文件解密或验签失败
*/
public static final int ERROR_DECRYPT_OR_SIGN_FAILED = 14;
// 更新阶段定义
/**
* 空闲状态(未开始升级)
*/
private static final int PHASE_IDLE = 0;
/**
* 设备检测阶段(检查USB/存储设备)
*/
private static final int PHASE_DETECTING = 1;
/**
* 升级包校验阶段(验证完整性/签名)
*/
private static final int PHASE_CHECKING = 2;
/**
* 系统备份阶段(备份当前系统数据)
*/
private static final int PHASE_BACKUP = 3;
/**
* 文件复制阶段(写入升级包到临时分区)
*/
private static final int PHASE_COPYING = 4;
/**
* 解压阶段(解压升级包内容)
*/
private static final int PHASE_EXTRACTING = 5;
/**
* 清理阶段(删除临时文件)
*/
private static final int PHASE_CLEANUP = 6;
/**
* 回滚阶段(升级失败时恢复备份)
*/
private static final int PHASE_ROLLBACK = 7;
public static final int ERROR_PHASE_ROLLBACK = 101;
// 权重分配比例
private static final float BACKUP_WEIGHT = 0.1f; // 备份阶段权重10%
private static final float PACKAGE_COPY_WEIGHT = 0.29f; // 升级包拷贝阶段权重29%
private static final float PACKAGE_VERIFY_WEIGHT = 0.31f; // 升级包解密验签阶段权重31%
private static final float PACKAGE_EXTRACT_WEIGHT = 0.29f; // 升级包解压阶段权重29%
private static final float VERIFICATION_WEIGHT = 0.01f; // 校验阶段权重1%
// 声明 mProgress 为实例变量(非静态)
private float mProgress = 0; // 当前进度值(0~1)
private static USBOfflineUpdater instance;
private final Context context;
// private UpdateTask currentTask; 0903
private RxUpdateManager rxUpdateManager;
//
private UpdateListener updateListener;
private SDKNativeEngine sdkNativeEngine;
private MapDataController mapDataController;
// 目录配置
private File usbRoot;
private File cacheDir;
private File storageDir;//
private File backupDir; //
private File backupAlreadyDir;
// 更新控制
// public boolean isPaused = false;
public final AtomicBoolean isPaused = new AtomicBoolean(false);
public final AtomicBoolean isCancelled = new AtomicBoolean(false);
public final AtomicInteger currentPhase = new AtomicInteger(PHASE_IDLE);
private final AtomicBoolean isRecoveryPending = new AtomicBoolean(false);
// 错误信息
private String lastErrorMessage = "";
// 电量阈值
private static final int MIN_BATTERY_LEVEL = 30; // 最低电量百分比
private static final int MIN_BATTERY_LEVEL_CRITICAL = 15; // 严重低电量
//升级包格式
private static final String FILE_NAME_PATTERN = "^KVM_Navi_EU_"
+ "(?<version>\\d{1,3})" // 版本号(1-3位数字)
+ "_"
+ "(?<serial>\\d{1,2})" // 1-2位数字编号
+ "(\\.\\w+)?$"; // 可选的文件扩展名
// 进度计算相关变量(新增)
private long totalUpdateSize = 0; // 所有升级包总大小
private long UpdateSize = 0; // 当前升级包大小
private long currentCopiedBytes = 0; // 当前已拷贝字节数
private long currentVerifiedBytes = 0; // 当前已验签字节数
private long currentExtractedBytes = 0; // 当前已解压字节数
private long backupSize = 0; // 备份数据大小
private long storageSize = 0;
private int currentPackageIndex = 0; // 当前处理的升级包索引
private int totalPackageCount = 0; // 总升级包数量
// 用于跟踪回滚状态
public boolean isRollingBack = false;
public long rollbackTotalSize = 0;
public long rollbackProcessedSize = 0;
public boolean ishandleFailure = false;
private final Object updateLock = new Object(); // 用于更新与回滚的互斥
// USB监听器
private final BroadcastReceiver usbReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = intent.getAction();
if (Intent.ACTION_MEDIA_MOUNTED.equals(action)) {
//zwxtest-用于平板测试
// File usbPath = new File(intent.getData().getPath());
File usbPath = new File("/data/data/com.kotei.overseas.navi/files");
if (usbPath.exists() && usbPath.canRead()) {
usbRoot = usbPath;
Log.i(TAG, "USB mounted: " + usbRoot.getAbsolutePath());
}
} else if (Intent.ACTION_MEDIA_EJECT.equals(action) ||
Intent.ACTION_MEDIA_UNMOUNTED.equals(action)) {
// 0903 -省略 if (rx != null && currentPhase.get() > PHASE_CHECKING) {
// cancelUpdate(ERROR_USB_REMOVED, "USB设备被移除");
// }
usbRoot = null;
Log.e(TAG, "USB removed");
}
}
};
// // 车辆状态监听器(模拟)
private final BroadcastReceiver vehicleReceiver = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
if ("com.example.ACTION_SHIFT_CHANGE".equals(intent.getAction())) {
String shift = intent.getStringExtra("shift");
if (!"P".equals(shift) && currentPhase.get() > PHASE_CHECKING) {
// cancelUpdate(ERROR_VEHICLE_SHIFTED, "车辆已退出P挡");
}
}
}
};
// 单例模式
public static synchronized USBOfflineUpdater getInstance(Context context) {
if (instance == null) {
instance = new USBOfflineUpdater(context);
}
return instance;
}
public static synchronized USBOfflineUpdater getInstance() {
return instance;
}
private USBOfflineUpdater(Context context) {
this.context = context;
sdkNativeEngine = SDKNativeEngine.getSharedInstance();
mapDataController = MapDataController.getInstance();
// 初始化电量
DistributePowerModuleAdapter.getInstance().initAdapter();
DistributeGearModuleAdapter.getInstance().initAdapter();
// 注册档位变量回调实例
DistributeGearModuleAdapter.getInstance().addGearChangeListener(new DistributeGearAdapter.GearChangeListener() {
@Override
public void onGearChanged(int gear) {
// 待补充
checkEnvironment();
}
});
try {
DfCert.getInstance().getService();
} catch (Exception e) {
Log.e(TAG, "Exception:" + e.toString());
}
// 初始化目录(默认值)
cacheDir = this.context.getCacheDir();
storageDir = new File(sdkNativeEngine.getOptions().persistentMapStoragePath);
backupDir = new File(cacheDir, "backup");
backupAlreadyDir = new File(cacheDir, "backupAlready");
// 注册USB监听器
IntentFilter usbFilter = new IntentFilter();
usbFilter.addAction(Intent.ACTION_MEDIA_MOUNTED);
usbFilter.addAction(Intent.ACTION_MEDIA_EJECT);
usbFilter.addAction(Intent.ACTION_MEDIA_UNMOUNTED);
usbFilter.addDataScheme("file");
context.registerReceiver(usbReceiver, usbFilter);
// 注册车辆状态监听器(模拟)
IntentFilter vehicleFilter = new IntentFilter("com.example.ACTION_SHIFT_CHANGE");
context.registerReceiver(vehicleReceiver, vehicleFilter);
//清除数据存储目录下的预留数据
removeLegacy();
}
public void initialization(UpdateListener listener) {
// isRollingBack = false;
this.updateListener = listener;
// 启动时检查恢复
checkRecoveryOnStartup();
}
// 动态设置目录
public void setDirectories(File usbRoot, File cacheDir, File storageDir) {
if (usbRoot != null) {
this.usbRoot = usbRoot;
}
if (cacheDir != null) {
this.cacheDir = cacheDir;
}
if (storageDir != null) {
this.storageDir = storageDir;
}
}
/**
* 检测升级包
*
* @return 状态码 (SUCCESS 或错误码)
*/
public int detectUpdatePackages() {
//zwxtest
// 1. 检测USB是否插入
File usbPath = new File("/data/data/com.kotei.overseas.navi/files");
if (usbPath.exists() && usbPath.canRead()) {
usbRoot = usbPath;
Log.i(TAG, "USB mounted: " + usbRoot.getAbsolutePath());
// }
//zwxtest
if (usbRoot == null || !usbRoot.exists() || !usbRoot.isDirectory()) {
return ERROR_NO_USB;
}
File[] tempPackages = usbRoot.listFiles();
// 2. 查找升级包 (命名格式: update_v{版本号}_{日期}.zip)
File[] packages = usbRoot.listFiles(file ->
file.isFile() && file.getName().matches(FILE_NAME_PATTERN)
);
return (packages != null && packages.length > 0) ? SUCCESS : ERROR_NO_UPDATE_PACKAGE;
}
/**
* 环境检测
*
* @return 状态码 (SUCCESS 或错误码)
*/
public int checkEnvironment() {
// 1. 检测电量
int batteryLevel = DistributePowerModuleAdapter.getInstance().getCurrentPower();
// Logger.i(TAG,"【TEST】电量:" + batteryLevel);
if (batteryLevel < MIN_BATTERY_LEVEL) {
return batteryLevel < MIN_BATTERY_LEVEL_CRITICAL ?
ERROR_BATTERY_TOO_LOW : ERROR_BATTERY_LOW;
}
// 2. 检测缓存空间 (需大于15GB)
long requiredSpace = 15L * 1024 * 1024 * 1024; // 15GB
long availableSpace = StorageUtils.getAvailableSpace(cacheDir);
if (availableSpace < requiredSpace) {
Log.e(TAG, "缓存空间剩余:【" + availableSpace + "】");
return ERROR_STORAGE_INSUFFICIENT;
}
int gear = DistributeGearModuleAdapter.getInstance().getCurrentGear();
// Logger.i(TAG,"【TEST】档位:" + gear);
if(gear != DistributeGearAdapter.GearValue.GEAR_P) {
return ERROR_VEHICLE_SHIFTED;
}
return SUCCESS;
}
/**
* 判读是否正在进行离线更新
*/
public boolean isOfflineUpdate() {
return rxUpdateManager != null && currentPhase.get() != PHASE_IDLE;
}
/**
* 启动更新流程。
* <p>
* 在调用前,应确保已通过 {@link #detectUpdatePackages()} 和 {@link #checkEnvironment()} 进行了检查。
* 此方法会首先检查是否有待恢复的任务,如果有,则启动回滚;否则,启动正常的更新流程。
*
* @param listener 用于接收更新进度和结果回调的监听器。
*/
public void startUpdate(UpdateListener listener) {
this.updateListener = listener;
if (isRecoveryPending.get()) {
Log.w(TAG, "检测到待恢复任务,将启动回滚流程。");
rxUpdateManager = new RxUpdateManager();
rxUpdateManager.startRollbackOnly();
return;
}
if (isOfflineUpdate()) {
notifyListener(ERROR_UPDATE_IN_PROGRESS, "已有更新任务正在进行");
return;
}
int result = checkEnvironment();
if (result != SUCCESS) {
notifyListener(result, getErrorMessage(result));
return;
}
Log.i(TAG, "检测到更新任务触发,开始进行地图更新");
calculateTotalWorkload();
rxUpdateManager = new RxUpdateManager();
rxUpdateManager.startUpdate();
}
/**
* 开始更新
*/
// 计算总工作量(新增)
private void calculateTotalWorkload() {
totalUpdateSize = 0;
File[] packages = getUpdatePackages();
totalPackageCount = packages != null ? packages.length : 0;
if (packages != null) {
for (File pkg : packages) {
totalUpdateSize += pkg.length();
}
}
// backupSize = estimateBackupSize();//zwx
backupSize = FileUtils.getDirectorySize(storageDir);
Log.i(TAG, "总工作量计算: 升级包数量=" + totalPackageCount +
", 升级包大小=" + formatSize(totalUpdateSize) +
", 备份大小=" + formatSize(backupSize));
}
// 获取更新包(新增)
private File[] getUpdatePackages() {
if (usbRoot == null) return new File[0];
return usbRoot.listFiles(file ->
file.isFile() && file.getName().matches(FILE_NAME_PATTERN)
);
}
// 格式化文件大小(新增)
private String formatSize(long size) {
if (size < 1024) return size + "B";
else if (size < 1024 * 1024) return String.format("%.1fKB", size / 1024.0);
else if (size < 1024 * 1024 * 1024) return String.format("%.1fMB", size / (1024.0 * 1024));
else return String.format("%.1fGB", size / (1024.0 * 1024 * 1024));
}
/**
* 暂停更新
*/
public void pauseUpdate() {
isPaused.set(true);
notifyProgress("更新已暂停");
}
/**
* 恢复更新
*/
public void resumeUpdate() {
isPaused.set(false);
notifyProgress("更新已恢复");
}
/**
* 用户主动取消更新。
* 系统将安全地中断当前操作,并尝试回滚到更新前的状态。
*/
public void cancelUpdate() {
cancelUpdate(ERROR_USER_CANCELED, "用户取消更新");
}
public void cancelUpdate(int errorCode, String message) {
Log.i(TAG, "触发取消操作,错误码: " + errorCode);
isCancelled.set(true);
lastErrorMessage = message;
if (rxUpdateManager != null) {
rxUpdateManager.cancel();
// rxUpdateManager = null; // 0904 p1:目前点击更新进入更新流程后立刻点击取消,再次点击更新会卡到“已有更新任务,此处可能重置状态
}
}
//p3-4-回滚使用自定义进度计算
private void notifyProgress(String message) {
new Handler(Looper.getMainLooper()).post(() -> {
if (updateListener != null) {
float progress = calculateOverallProgress();
updateListener.onProgress(currentPhase.get(), progress, message);
}
});
}
/**
* 计算总体进度百分比 (0.00 - 100.00)。
* <p>
* 此方法是整个进度条显示的核心。它根据当前所处的阶段({@link #currentPhase})
* 和各个阶段的权重,计算出一个线性的、加权的总体进度值。
*
* @return 总体进度值,范围从0.00到100.00。
*/
private float calculateOverallProgress() {
if (totalPackageCount == 0 && currentPhase.get() > PHASE_BACKUP && currentPhase.get() != PHASE_ROLLBACK) {
return Math.round(mProgress * 10000) / 100.0f;
}
switch (currentPhase.get()) {
case PHASE_IDLE:
mProgress = 0.0f;
break;
case PHASE_ROLLBACK:
if (rollbackTotalSize > 0) {
mProgress = (float) rollbackProcessedSize / rollbackTotalSize;
} else {
mProgress = 0.0f;
}
break;
case PHASE_BACKUP:
if (backupSize > 0) {
mProgress = BACKUP_WEIGHT * (currentCopiedBytes / (float) backupSize);
} else {
mProgress = BACKUP_WEIGHT;
}
break;
case PHASE_COPYING: {
float packageTotalWeight = PACKAGE_COPY_WEIGHT + PACKAGE_VERIFY_WEIGHT + PACKAGE_EXTRACT_WEIGHT;
float packageCopyWeight = PACKAGE_COPY_WEIGHT / totalPackageCount;
float base = BACKUP_WEIGHT + (packageTotalWeight * currentPackageIndex / totalPackageCount);
float progress = (UpdateSize > 0) ? (currentCopiedBytes / (float) UpdateSize) : 0;
mProgress = base + packageCopyWeight * progress;
break;
}
case PHASE_CHECKING: {
float packageTotalWeight = PACKAGE_COPY_WEIGHT + PACKAGE_VERIFY_WEIGHT + PACKAGE_EXTRACT_WEIGHT;
float packageCopyWeight = PACKAGE_COPY_WEIGHT / totalPackageCount;
float packageVerifyWeight = PACKAGE_VERIFY_WEIGHT / totalPackageCount;
float base = BACKUP_WEIGHT + (packageTotalWeight * currentPackageIndex / totalPackageCount) + packageCopyWeight;
float progress = (UpdateSize > 0) ? (currentVerifiedBytes / (float) UpdateSize) : 0;
mProgress = base + packageVerifyWeight * progress;
break;
}
case PHASE_EXTRACTING: {
float packageTotalWeight = PACKAGE_COPY_WEIGHT + PACKAGE_VERIFY_WEIGHT + PACKAGE_EXTRACT_WEIGHT;
float packageCopyWeight = PACKAGE_COPY_WEIGHT / totalPackageCount;
float packageVerifyWeight = PACKAGE_VERIFY_WEIGHT / totalPackageCount;
float packageExtractWeight = PACKAGE_EXTRACT_WEIGHT / totalPackageCount;
float base = BACKUP_WEIGHT + (packageTotalWeight * currentPackageIndex / totalPackageCount) + packageCopyWeight + packageVerifyWeight;
float progress = (UpdateSize > 0) ? (currentExtractedBytes / (float) UpdateSize) : 0;
mProgress = base + packageExtractWeight * progress;
break;
}
case PHASE_CLEANUP:
mProgress = 0.99f;
break;
default:
break;
}
float finalProgress = Math.max(0.0f, Math.min(1.0f, mProgress));
return Math.round(finalProgress * 10000) / 100.0f;
}
// 结果通知
private void notifyListener(int resultCode, String message) {
new Handler(Looper.getMainLooper()).post(() -> {
if (updateListener != null) {
updateListener.onResult(resultCode, message);
}
});
}
/**
* 重置所有状态变量到初始值。
* 在一次完整的流程(成功、失败或取消)结束后调用。
*/
private void resetState() {
currentPhase.set(PHASE_IDLE);
isCancelled.set(false);
isPaused.set(false);
isRecoveryPending.set(false);
mProgress = 0;
currentCopiedBytes = 0;
currentVerifiedBytes = 0;
currentExtractedBytes = 0;
rollbackProcessedSize = 0;
lastErrorMessage = "";
rxUpdateManager = null;
}
/**
* 内部类,使用RxJava实现所有异步更新和回滚逻辑。
* <p>
* 这是更新系统的核心,它将更新的各个阶段(备份、复制、校验、解压)
* 和错误处理(回滚)串联在一个响应式流中,以保证操作的顺序性和原子性。
*/
/**
* 内部类,使用RxJava实现所有异步更新和回滚逻辑。
* <p>
* 这是更新系统的核心,它将更新的各个阶段(备份、复制、校验、解压)
* 和错误处理(回滚)串联在一个响应式流中,以保证操作的顺序性和原子性。
* 这种设计从根本上避免了多线程并发操作同一资源(如文件目录)导致的竞态条件。
*/
private class RxUpdateManager {
private final CompositeDisposable disposables = new CompositeDisposable();
/**
* 启动完整的更新流程。
* 在执行前会重置任务状态,确保每次运行都是一个全新的开始。
*/
public void startUpdate() {
resetTaskState();
Observable<Integer> updateObservable = createUpdateObservable();
subscribe(updateObservable);
}
/**
* 仅启动回滚流程。
* 主要用于处理启动时(initialization)检测到上次更新意外中断而留下的待恢复任务。
*/
public void startRollbackOnly() {
resetTaskState();
currentPhase.set(PHASE_ROLLBACK);
// 创建一个初始错误来触发回滚流程
Observable<Integer> rollbackObservable = createRollbackObservable(new IOException("启动恢复流程"));
subscribe(rollbackObservable);
}
/**
* 订阅并处理更新/回滚流。
* <p>
* 这是RxJava逻辑的核心部分。它通过 {@code .onErrorResumeNext} 操作符构建了一个强大的错误处理机制:
* <ul>
* <li>如果上游的Observable(如更新操作)成功完成,则直接进入最终的 {@code onNext} 回调。</li>
* <li>如果上游的Observable发射了错误信号,{@code .onErrorResumeNext} 会捕获该错误,
* 然后返回一个新的Observable(即回滚操作),并订阅它。</li>
* <li>如果这个新的回滚Observable成功,则其结果会进入最终的 {@code onNext} 回调。</li>
* <li>如果回滚Observable也失败了,则其错误会进入最终的 {@code onError} 回调。</li>
* </ul>
* 这种链式结构确保了“更新失败则回滚”这一事务性操作的完整性。
*
* @param observable 要订阅的初始Observable(通常是更新流)。
*/
private void subscribe(Observable<Integer> observable) {
//0904 p1:目前点击更新进入更新流程后立刻点击取消,再次点击更新会卡到“已有更新任务,此处可能重置状态
disposables.add(
Disposable.fromAction(() -> {
Log.i(TAG, "🔥 订阅已取消,执行清理"); // ✅ 当dispose()被调用时触发
resetState();
})
);
disposables.add(
observable
.onErrorResumeNext(error -> {
// 当更新流(或任何上游流)失败时,此代码块被执行。
Log.e(TAG, "更新流程发生错误,准备回滚...", error);
// 根据错误类型设置不同的用户提示信息
if (error instanceof InterruptedException) {
lastErrorMessage = "用户取消更新";
} else {
lastErrorMessage = "更新失败: " + error.getMessage();
}
// 关键检查点: 如果连备份文件都不存在,回滚无法进行,必须终止。
// 这是一个保护性检查,防止在没有备份的情况下继续执行并导致更严重的问题。
if (backupAlreadyDir == null || !backupAlreadyDir.exists()) {
return Observable.error(new IOException("无备份文件,无法执行回滚", error));
}
// 设置当前阶段为回滚,并返回一个新的回滚任务Observable
currentPhase.set(PHASE_ROLLBACK);
return createRollbackObservable(error);
})
.subscribeOn(Schedulers.io()) // 指定所有上游任务(更新/回滚)在IO线程执行
.observeOn(AndroidSchedulers.mainThread()) // 指定最终的回调在Android主线程执行
.subscribe(
resultCode -> { // onNext: 整个链条最终成功的回调
// 无论是"更新成功"还是"回滚成功",都会进入这里。
if (currentPhase.get() == PHASE_ROLLBACK) {
notifyListener(ERROR_PHASE_ROLLBACK, getErrorMessage(ERROR_PHASE_ROLLBACK));
} else {
notifyListener(SUCCESS, "更新成功,请重启车机");
}
// 流程结束,重置所有状态
resetState();
},
error -> { // onError: 整个链条最终失败的回调
// 只有在"更新失败"并且"回滚也失败"的极端情况下才会进入这里。
Log.e(TAG, "关键错误:更新或回滚流程最终失败", error);
int code = (isCancelled.get()) ? ERROR_USER_CANCELED : ERROR_UNEXPECTED;
String msg = (isCancelled.get()) ? lastErrorMessage : "更新失败且回滚也失败,请联系技术支持。";
notifyListener(code, msg);
// 流程结束,重置所有状态
resetState();
}
)
);
}
/**
* 创建执行核心更新操作的Observable。
*
* @return 一个发射单个整数(状态码)的Observable,若失败则发射错误。
*/
private Observable<Integer> createUpdateObservable() {
return Observable.create(emitter -> {
try {
// 阶段1: 备份数据
currentPhase.set(PHASE_BACKUP);
notifyProgress("开始备份数据...");
// 步骤 1.1: 清理环境,确保没有上次任务的残留文件
if (backupDir.exists() && !FileUtils.deleteRecursive(backupDir)) {
throw new IOException("无法删除旧的临时备份目录");
}
if (backupAlreadyDir.exists() && !FileUtils.deleteRecursive(backupAlreadyDir)) {
throw new IOException("无法删除已存在的备份目录");
}
// 步骤 1.2: 创建本次备份所用的临时目录
if (!backupDir.mkdirs()) {
throw new IOException("创建备份目录失败");
}
// 步骤 1.3: 如果原始数据不为空,则执行拷贝
if (backupSize > 0) {
checkInterrupted(emitter);
singlecopydirectory.copyDirectoryWithProgress(storageDir, backupDir, (copied, total) -> {
currentCopiedBytes = copied;
notifyProgress("备份中: " + formatSize(copied) + "/" + formatSize(total));
});
} else {
Log.i(TAG, "无需备份数据,跳过数据拷贝阶段。");
notifyProgress("无数据需要备份");
}
// [关键修正] 步骤 1.4: 原子性地标记“备份已完成”。
// 无论原始数据是否为空,都必须执行此操作。`backupDir` 重命名为 `backupAlreadyDir`
// 是一个原子操作,它的成功完成意味着我们拥有了一个安全的回滚点(即使是空)。
if (!backupDir.renameTo(backupAlreadyDir)) {
throw new IOException("重命名备份目录失败(标记备份完成的关键步骤失败)");
}
// --- 阶段2, 3, 4: 处理升级包、校验和清理 (代码保持不变) ---
File[] updatePackages = getUpdatePackages();
notifyProgress("开始处理升级包...");
for (currentPackageIndex = 0; currentPackageIndex < updatePackages.length; currentPackageIndex++) {
checkInterrupted(emitter);
File packageFile = updatePackages[currentPackageIndex];
UpdateSize = packageFile.length();
String packageName = packageFile.getName();
currentCopiedBytes = 0;
currentPhase.set(PHASE_COPYING);
File destFile = new File(storageDir, packageName);
if (!FileUtils.copyFileWithProgress(packageFile, destFile, (copied, total) -> {
currentCopiedBytes = copied;
notifyProgress(String.format("拷贝 %s: %s/%s", packageName, formatSize(copied), formatSize(total)));
})) throw new IOException("拷贝升级包失败: " + packageName);
currentVerifiedBytes = 0;
currentPhase.set(PHASE_CHECKING);
checkInterrupted(emitter);
if (!dataVerification(destFile.getAbsolutePath(), (processed, total) -> {
currentVerifiedBytes = processed;
notifyProgress(String.format("解密验签 %s: %s/%s", packageName, formatSize(processed), formatSize(total)));
})) throw new IOException("解密验签失败: " + packageName);
currentExtractedBytes = 0;
currentPhase.set(PHASE_EXTRACTING);
checkInterrupted(emitter);
if (!FileUtils.extractZipWithProgress(destFile, storageDir, (extracted, total) -> {
currentExtractedBytes = extracted;
notifyProgress(String.format("解压 %s: %s/%s", packageName, formatSize(extracted), formatSize(total)));
})) throw new IOException("解压失败: " + packageName);
if (!destFile.delete()) Log.w(TAG, "删除临时升级包失败: " + packageName);
}
if (!mapDataController.checkInstallationStatus()) {
throw new IOException("安装后数据校验失败");
}
currentPhase.set(PHASE_CLEANUP);
notifyProgress("清理缓存...");
if (backupAlreadyDir.exists() && !FileUtils.deleteRecursive(backupAlreadyDir)) {
Log.w(TAG, "清理已完成的备份文件失败");
}
emitter.onNext(SUCCESS);
emitter.onComplete();
} catch (Exception e) {
if (!emitter.isDisposed()) {
emitter.onError(e);
}
}
});
}
/**
* 创建执行回滚操作的Observable。
*
* @param originalError 导致回滚的原始错误(主要用于调试追溯)。
* @return 一个发射单个整数(状态码)的Observable,若失败则发射错误。
*/
private Observable<Integer> createRollbackObservable(Throwable originalError) {
return Observable.create(emitter -> {
try {
notifyProgress("更新失败,正在回滚数据...");
rollbackTotalSize = FileUtils.getDirectorySize(backupAlreadyDir);
rollbackProcessedSize = 0;
// 步骤 1: 清空当前可能已损坏的目标目录。这是所有回滚场景的共同前置操作。
checkInterrupted(emitter);
if (storageDir.exists() && !FileUtils.deleteDirectoryContents(storageDir)) {
throw new IOException("清空目标目录失败,无法回滚");
}
// [关键修正] 步骤 2: 处理备份恢复,将"空备份"视为一种正常情况。
if (rollbackTotalSize > 0) {
// 备份目录非空,执行正常的文件恢复流程。
Log.i(TAG, "开始从非空备份恢复数据。");
checkInterrupted(emitter);
if (!singlecopydirectory.copyDirectoryWithProgress(backupAlreadyDir, storageDir, (copied, total) -> {
rollbackProcessedSize = copied;
notifyProgress("恢复中: " + formatSize(copied) + "/" + formatSize(total));
})) {
throw new IOException("从备份恢复文件失败");
}
} else {
// 备份目录为空,意味着原始状态就是空的。此时清空目标目录后,回滚即已成功。
Log.i(TAG, "备份为空,回滚操作仅需清空目标目录,已完成。");
notifyProgress("数据恢复完成");
}
// 步骤 3: 清理已成功使用的备份目录
if (backupAlreadyDir.exists() && !FileUtils.deleteRecursive(backupAlreadyDir)) {
Log.w(TAG, "回滚成功后删除备份目录失败");
}
// 发射成功信号
emitter.onNext(ERROR_PHASE_ROLLBACK);
emitter.onComplete();
} catch (Exception e) {
// 如果在回滚过程中也发生错误,则发射一个包装后的最终错误。
if (!emitter.isDisposed()) {
emitter.onError(new RuntimeException("回滚过程中发生严重错误", e));
}
}
});
}
/**
* 在流的执行点检查暂停或取消信号。
* @param emitter 当前的Observable发射器。
* @throws InterruptedException 如果操作被取消,则抛出此异常以中断流。
*/
private void checkInterrupted(ObservableEmitter<?> emitter) throws InterruptedException {
while (isPaused.get() && !emitter.isDisposed()) {
Thread.sleep(500);
}
if (isCancelled.get() || emitter.isDisposed()) {
throw new InterruptedException("操作已被用户取消");
}
}
/**
* 取消并释放所有正在进行的RxJava任务。
*/
public void cancel() {
if (!disposables.isDisposed()) {
disposables.dispose();
}
}
/**
* 重置任务相关的状态,用于开始新的更新或回滚。
*/
private void resetTaskState() {
isCancelled.set(false);
isPaused.set(false);
currentCopiedBytes = 0;
currentVerifiedBytes = 0;
currentExtractedBytes = 0;
mProgress = 0;
}
// 暂停/恢复方法
public void pauseUpdate() {
isPaused.set(true);
}
public void resumeUpdate() {
isPaused.set(false);
}
}
// ================== 启动时恢复检查 ==================
private void checkRecoveryOnStartup() {
Log.i(TAG, "=== 执行恢复检查 ===");
if (backupDir != null && backupDir.exists()) {
Log.w(TAG, "检测到未完成的临时备份目录,将被删除: " + backupDir.getAbsolutePath());
if (!FileUtils.deleteRecursive(backupDir)) {
Log.e(TAG, "删除未完成的临时备份目录失败");
}
}
if (backupAlreadyDir != null && backupAlreadyDir.exists()) {
Log.w(TAG, "检测到上次更新失败后留下的备份,需要执行恢复操作。");
isRecoveryPending.set(true);
}
}
private void checkStoragePerformance() {
long writeSpeed = StorageUtils.measureWriteSpeed(storageDir);
Log.d(TAG, "存储写入速度: " + formatSize(writeSpeed) + "/s");
if (writeSpeed < 50 * 1024 * 1024) { // 低于 50MB/s
Log.w(TAG, "检测到低速存储设备,还原操作可能较慢");
}
}
// 释放资源
// ================== 接口定义 ==================
public void release() {
try {
context.unregisterReceiver(usbReceiver);
context.unregisterReceiver(vehicleReceiver);
} catch (Exception e) {
Log.w(TAG, "释放资源时出错", e);
}
}
public interface UpdateListener {
void onProgress(int phase, float progress, String message);
void onResult(int resultCode, String message);
}
public interface ProgressCallback {
void onProgress(long progress, long total) throws InterruptedException;
}
public File getUsbRoot() {
return usbRoot;
}
public File getCacheDir() {
return cacheDir;
}
public File getStorageDir() {
return storageDir;
}
public String getErrorMessage(int code) {
return switch (code) {
case ERROR_NO_USB -> "未检测到USB设备,请检查连接状态或更换接口";
case ERROR_NO_UPDATE_PACKAGE -> "升级包文件缺失,请确认存储路径";
case ERROR_BATTERY_LOW -> "电池电量不足(需≥20%)";
case ERROR_STORAGE_INSUFFICIENT -> "存储空间不足(需预留20gb以上)";
case ERROR_UPDATE_IN_PROGRESS -> "系统正在执行其他升级任务";
case ERROR_COPY_FAILED -> "文件复制失败,请检查存储权限";
case ERROR_EXTRACT_FAILED -> "升级包解压失败(可能文件损坏)";
case ERROR_USER_CANCELED -> "用户已取消升级操作";
case ERROR_UNEXPECTED -> "发生未预期的系统异常";
case ERROR_USB_REMOVED -> "升级过程中USB设备被移除";
case ERROR_VEHICLE_SHIFTED -> "请将车辆档位切换至P档";
case ERROR_BATTERY_TOO_LOW -> "电池电量极低(需≥10%)";
case ERROR_FILE_VERIFY_FAILED -> "文件校验失败(MD5/SHA256不匹配)";
case ERROR_DECRYPT_OR_SIGN_FAILED -> "文件解密/验签失败";
case ERROR_PHASE_ROLLBACK -> "回滚成功";
default -> "未知错误导致更新失败";
};
}
void removeLegacy() {
if (storageDir == null || !storageDir.exists() || !storageDir.isDirectory()) {
return;
}
Pattern pattern = Pattern.compile(FILE_NAME_PATTERN);
File[] files = storageDir.listFiles();
if (files == null) return;
for (File file : files) {
if (file.isFile() && pattern.matcher(file.getName()).matches()) {
// 删除匹配的文件
try {
Files.deleteIfExists(file.toPath());
} catch (IOException | SecurityException e) {
// 处理异常(记录日志等)
}
}
}
// 删除sign文件夹(如果存在)
Path signDir = Paths.get(storageDir.getAbsolutePath(), "sign");
if (Files.exists(signDir)) {
try {
// 递归删除整个目录
FileUtils.deleteDirectoryRecursively(signDir);
} catch (IOException | SecurityException e) {
// 处理异常
}
}
}
}
到底是哪报错