2025-09-02 12:22:06.313 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 13.3GB/13.6GB progress:9.79
2025-09-02 12:22:07.270 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 13.3GB/13.6GB progress:9.84
2025-09-02 12:22:07.964 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 13.4GB/13.6GB progress:9.87
2025-09-02 12:22:08.727 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 13.4GB/13.6GB progress:9.9
2025-09-02 12:22:09.458 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 13.5GB/13.6GB progress:9.94
2025-09-02 12:22:10.532 31575-31850 USBOfflineUpdater com.kotei.overseas.navi I 重命名备份目录成功
2025-09-02 12:22:10.538 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 13.6GB/13.6GB progress:10.0
2025-09-02 12:22:10.538 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 数据备份完成 progress:10.0
2025-09-02 12:22:15.390 31575-31850 USBOfflineUpdater com.kotei.overseas.navi I 开始处理升级包【拷贝、解密验签、解压】
2025-09-02 12:22:15.476 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 10.0MB/3.0GB progress:10.02
2025-09-02 12:22:16.504 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 260.0MB/3.0GB progress:10.49
2025-09-02 12:22:17.550 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 360.0MB/3.0GB progress:10.68
2025-09-02 12:22:18.593 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 460.0MB/3.0GB progress:10.87
2025-09-02 12:22:19.674 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 560.0MB/3.0GB progress:11.06
2025-09-02 12:22:20.697 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 660.0MB/3.0GB progress:11.25
2025-09-02 12:22:21.714 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 760.0MB/3.0GB progress:11.43
2025-09-02 12:22:22.774 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 860.0MB/3.0GB progress:11.62
2025-09-02 12:22:23.821 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 960.0MB/3.0GB progress:11.81
2025-09-02 12:22:24.854 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.0GB/3.0GB progress:12.0
2025-09-02 12:22:25.921 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.1GB/3.0GB progress:12.19
2025-09-02 12:22:27.009 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.2GB/3.0GB progress:12.38
2025-09-02 12:22:28.125 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.3GB/3.0GB progress:12.57
2025-09-02 12:22:29.225 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.4GB/3.0GB progress:12.75
2025-09-02 12:22:29.402 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2
2025-09-02 12:22:30.056 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 0
2025-09-02 12:22:30.056 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@44606a7
2025-09-02 12:22:30.094 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 1
2025-09-02 12:22:30.095 31575-31575 USBOfflineUpdater com.kotei.overseas.navi I 触发取消按钮
2025-09-02 12:22:32.413 31575-31575 USBOfflineUpdater com.kotei.overseas.navi W handFailure is running 已经完成备份的文件夹路径: /data/user/0/com.kotei.overseas.navi/cache/backupAlready
2025-09-02 12:22:32.413 31575-31575 USBOfflineUpdater com.kotei.overseas.navi W 更新失败,正在回滚数据,回滚目录: /data/user/0/com.kotei.overseas.navi/cache/backupAlready
2025-09-02 12:22:32.415 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I resultCode: 8 message:用户取消更新
2025-09-02 12:22:32.415 31575-31614 USBOfflineUpdater com.kotei.overseas.navi I resultCode: 8 message:用户取消更新
2025-09-02 12:22:32.416 31575-31924 USBOfflineUpdater com.kotei.overseas.navi W performRollback IS RUNNING
2025-09-02 12:22:32.416 31575-31924 USBOfflineUpdater com.kotei.overseas.navi W 检查storage目录是否可写:true
2025-09-02 12:22:32.420 31575-31575 BLASTBufferQueue_Java com.kotei.overseas.navi I update, w= 1920 h= 1200 mName = VRI[MainActivity]@44606a7 mNativeObject= 0xb400007b46467200 sc.mNativeObject= 0xb400007b464881c0 format= -3 caller= android.view.ViewRootImpl.updateBlastSurfaceIfNeeded:3386 android.view.ViewRootImpl.relayoutWindow:11361 android.view.ViewRootImpl.performTraversals:4544 android.view.ViewRootImpl.doTraversal:3708 android.view.ViewRootImpl$TraversalRunnable.run:12542 android.view.Choreographer$CallbackRecord.run:1751
2025-09-02 12:22:32.420 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I Relayout returned: old=(0,0,1920,1200) new=(0,0,1920,1200) relayoutAsync=true req=(1920,1200)0 dur=0 res=0x0 s={true 0xb400007b452a2000} ch=false seqId=0
2025-09-02 12:22:32.421 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I registerCallbackForPendingTransactions
2025-09-02 12:22:32.424 1360-16334 WindowManager system_server V Relayout Window{ba9dedf u0 com.kotei.overseas.navi/com.kotei.overseas.navi.base.MainActivity}: viewVisibility=0 req=1920x1200 ty=1 d0
2025-09-02 12:22:32.427 31575-31687 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I mWNT: t=0xb400007b463eda80 mBlastBufferQueue=0xb400007b46467200 fn= 363 HdrRenderState mRenderHdrSdrRatio=1.0 caller= android.view.ViewRootImpl$9.onFrameDraw:6276 android.view.ViewRootImpl$3.onFrameDraw:2440 android.view.ThreadedRenderer$1.onFrameDraw:761
2025-09-02 12:22:32.644 31575-31924 USBOfflineUpdater com.kotei.overseas.navi W 准备执行:singlecopydirectory.copyDirectoryWithProgress
2025-09-02 12:22:33.094 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@44606a7
2025-09-02 12:22:34.898 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2
2025-09-02 12:22:38.996 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 0
2025-09-02 12:22:38.996 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@44606a7
2025-09-02 12:22:39.071 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 1
2025-09-02 12:22:39.072 31575-31575 USBOfflineUpdater com.kotei.overseas.navi I USB mounted: /data/data/com.kotei.overseas.navi/files
2025-09-02 12:22:39.072 31575-31931 USBOfflineUpdater com.kotei.overseas.navi I === 启动时恢复检查 ===
2025-09-02 12:22:39.603 1360-16806 WindowManager system_server V Relayout Window{ba9dedf u0 com.kotei.overseas.navi/com.kotei.overseas.navi.base.MainActivity}: viewVisibility=0 req=1920x1200 ty=1 d0
2025-09-02 12:22:39.616 31575-31575 i.overseas.navi com.kotei.overseas.navi W Long monitor contention with owner binder:31575_1 (31588) at void android.hardware.display.DisplayManagerGlobal.handleDisplayEvent(int, int, boolean)(DisplayManagerGlobal.java:624) waiters=0 in android.view.DisplayInfo android.hardware.display.DisplayManagerGlobal.getDisplayInfo(int) for 500ms
2025-09-02 12:22:39.616 31575-31575 BLASTBufferQueue_Java com.kotei.overseas.navi I update, w= 1920 h= 1200 mName = VRI[MainActivity]@44606a7 mNativeObject= 0xb400007b46467200 sc.mNativeObject= 0xb400007b464881c0 format= -3 caller= android.view.ViewRootImpl.updateBlastSurfaceIfNeeded:3386 android.view.ViewRootImpl.relayoutWindow:11361 android.view.ViewRootImpl.performTraversals:4544 android.view.ViewRootImpl.doTraversal:3708 android.view.ViewRootImpl$TraversalRunnable.run:12542 android.view.Choreographer$CallbackRecord.run:1751
2025-09-02 12:22:39.617 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I Relayout returned: old=(0,0,1920,1200) new=(0,0,1920,1200) relayoutAsync=true req=(1920,1200)0 dur=511 res=0x0 s={true 0xb400007b452a2000} ch=false seqId=0
2025-09-02 12:22:39.617 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I registerCallbackForPendingTransactions
2025-09-02 12:22:39.619 31575-31688 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I mWNT: t=0xb400007aaf28a780 mBlastBufferQueue=0xb400007b46467200 fn= 364 HdrRenderState mRenderHdrSdrRatio=1.0 caller= android.view.ViewRootImpl$9.onFrameDraw:6276 android.view.ViewRootImpl$3.onFrameDraw:2440 android.view.ThreadedRenderer$1.onFrameDraw:761
2025-09-02 12:22:39.620 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2
2025-09-02 12:22:42.071 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@44606a7
2025-09-02 12:22:42.949 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2
2025-09-02 12:22:43.055 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 0
2025-09-02 12:22:43.056 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@44606a7
2025-09-02 12:22:43.091 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2
2025-09-02 12:22:43.126 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 1
2025-09-02 12:22:44.317 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 0
2025-09-02 12:22:44.400 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 1
2025-09-02 12:22:46.630 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 0
2025-09-02 12:22:46.654 31575-31575 Choreographer com.kotei.overseas.navi W Frame time is 0.098215 ms in the future! Check that graphics HAL is generating vsync timestamps using the correct timebase.
2025-09-02 12:22:46.723 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I ViewPostIme pointer 1
2025-09-02 12:22:46.724 31575-31575 USBOfflineUpdater com.kotei.overseas.navi I 检查是否处于回滚状态:true
2025-09-02 12:22:49.723 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@44606a7
2025-09-02 12:22:51.052 31575-31575 VRI[MainAc...y]@44606a7 com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2
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.AsyncTask;
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.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.AtomicInteger;
import java.util.regex.Pattern;
import java.util.stream.Stream;
/**
* 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;
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 boolean isCancelled = false;
public final AtomicInteger currentPhase = new AtomicInteger(PHASE_IDLE);
// 错误信息
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;
// 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)) {
if (currentTask != 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.getApplicationContext();
// 初始化SDK
sdkNativeEngine = SDKNativeEngine.getSharedInstance();
mapDataController = MapDataController.getInstance();
try {
DfCert.getInstance().getService();
} catch (Exception e) {
Log.e(TAG, "Exception:" + e.toString());
}
//zwx 执行顺序?
// 初始化目录(默认值)
Log.i(TAG, "zwx:=== 启动目录初始化 ===" );
cacheDir = this.context.getCacheDir();
storageDir = new File(sdkNativeEngine.getOptions().persistentMapStoragePath);
// ✅ 初始化 backupDir
backupDir = new File(cacheDir, "backup"); // ✅ 基于 cacheDir 固定路径
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;
Thread USBOfflineUpdaterInitialization = new Thread(new Runnable() {
@Override
public void run() {
// 启动时检查恢复
checkRecoveryOnStartup();
}
});
USBOfflineUpdaterInitialization.setName("USBOfflineUpdaterInitialization");
USBOfflineUpdaterInitialization.start();
}
// 动态设置目录
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 = PowerUtils.getBatteryLevel(context);
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;
}
return SUCCESS;
}
/**
* 判读是否正在进行离线更新
*/
public boolean isOfflineUpdate() {
return currentTask != null && !currentTask.isCancelled();
}
/**
* 开始更新
*/
public void startUpdate(UpdateListener listener) {
Log.i(TAG,"检查是否处于回滚状态:"+ishandleFailure);
this.updateListener = listener;
if (ishandleFailure && backupAlreadyDir.exists()) {
new Handler(Looper.getMainLooper()).post(() -> {
if (updateListener != null) {
// 显示回滚阶段和进度
float progress = (float) (rollbackProcessedSize * 100) / rollbackTotalSize;
progress = Math.round(progress * 100) / 100.0f; // 保留两位小数
updateListener.onProgress(PHASE_ROLLBACK, progress, "正在回滚数据...");
}
});
return;
}
int result = checkEnvironment();
if (result != SUCCESS) {
notifyListener(result, "环境检测不合格");
return;
}
if (isRollingBack) {
notifyListener(ERROR_UPDATE_IN_PROGRESS, "正在进行数据回滚");
return;
}
if (isOfflineUpdate()) {
notifyListener(ERROR_UPDATE_IN_PROGRESS, "已有更新任务正在进行");
return;
}
Log.i(TAG, "检测到更新任务触发,开始进行地图更新");
// 计算总工作量(新增)
calculateTotalWorkload();//p3-2
notifyProgress("开始进行更新");
this.updateListener = listener;//zwx-恢复
currentTask = new UpdateTask();
currentTask.execute();
}
// 计算总工作量(新增)
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));
}
// // 估算备份大小方法(避免返回0导致除0错误)
// private long estimateBackupSize() {
// long storageSize = FileUtils.getDirectorySize(storageDir);
// long size = (long) (storageSize * 1.2);
// return size > 0 ? size : 1; // 确保不为0
// }
// 获取更新包(新增)
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 = true;
notifyProgress("更新已暂停");
}
/**
* 恢复更新
*/
public void resumeUpdate() {
isPaused = false;
notifyProgress("更新已恢复");
}
/**
* 取消更新
*/
public void cancelUpdate() {
cancelUpdate(ERROR_USER_CANCELED, "用户取消更新");
}
// private void cancelUpdate(int errorCode, String message) {
// isCancelled = true;
// lastErrorMessage = message;
// notifyListener(errorCode, message);
// }
public void cancelUpdate(int errorCode, String message) {
Log.i(TAG,"触发取消按钮");
isCancelled = true;
lastErrorMessage = message;
//zwx-focus-p3-回滚触发了计算总进度
if (currentTask != null) {
currentTask.cancel(true); // 请求中断线程
}
//p7
// notifyListener(errorCode, message);
if (!ishandleFailure && backupAlreadyDir.exists()) {
handleFailure(ERROR_USER_CANCELED);//p4-3
}
notifyListener(errorCode, message);//p7
}
// 进度通知
// private void notifyProgress(String message) {
// new Handler(Looper.getMainLooper()).post(() -> {
// if (updateListener != null) {
// // 计算当前总进度(修改)
// float progress = calculateOverallProgress();
// updateListener.onProgress(currentPhase.get(), progress, message);
// }
// });
// }
//p3-4-回滚使用自定义进度计算
private void notifyProgress(String message) {
new Handler(Looper.getMainLooper()).post(() -> {
if (updateListener != null) {
float progress;
if (isRollingBack) {
float progress_orin = (float) (rollbackProcessedSize * 100) / rollbackTotalSize;
progress = Math.round(progress_orin * 100) / 100.0f;
} else {
progress = calculateOverallProgress();
}
updateListener.onProgress(currentPhase.get(), progress, message);
}
});
}
private float calculateOverallProgress() {
// if (isRollingBack) {
// // 回滚阶段:直接计算回滚进度
// if (rollbackTotalSize > 0) {
// mProgress = 99;
// return mProgress;
// }
// return 0;
// }
// if (totalPackageCount == 0 && currentPhase.get() != PHASE_ROLLBACK) {
// throw new IllegalStateException("totalPackageCount should not be 0 here!");
// }//p3-3
if (totalPackageCount == 0 && currentPhase.get() != PHASE_ROLLBACK) return 0;
// 每个包的总权重(拷贝+验签+解压)
float packageTotalWeight = PACKAGE_COPY_WEIGHT +
PACKAGE_VERIFY_WEIGHT +
PACKAGE_EXTRACT_WEIGHT;
//再次确认totalPackageCount是否等于0
if (totalPackageCount == 0) {
throw new IllegalStateException("totalPackageCount should not be 0 here!");
}
// 每个包的阶段权重
float packageCopyWeight = PACKAGE_COPY_WEIGHT / totalPackageCount;
float packageVerifyWeight = PACKAGE_VERIFY_WEIGHT / totalPackageCount;
float packageExtractWeight = PACKAGE_EXTRACT_WEIGHT / totalPackageCount;
switch (currentPhase.get()) {
case PHASE_BACKUP:
if(backupSize > 0) {
mProgress = BACKUP_WEIGHT * (currentCopiedBytes / (float) backupSize);
}else{
mProgress = BACKUP_WEIGHT;
}
if(mProgress > BACKUP_WEIGHT) {
mProgress = BACKUP_WEIGHT;
}
break;
case PHASE_COPYING:
// 基础:备份 + 已完成包的完整进度
float copyBase = BACKUP_WEIGHT +
(packageTotalWeight * currentPackageIndex) / totalPackageCount;
// 增量:当前包拷贝进度
float copyProgress = currentCopiedBytes / (float) UpdateSize;
mProgress = copyBase + packageCopyWeight * copyProgress;
break;
case PHASE_CHECKING:
// 基础:备份 + 已完成包的完整进度 + 当前包拷贝完成
float verifyBase = BACKUP_WEIGHT +
(packageTotalWeight * currentPackageIndex) / totalPackageCount +
packageCopyWeight;
// 增量:当前包验签进度
float verifyProgress = currentVerifiedBytes / (float) UpdateSize;
mProgress = verifyBase + packageVerifyWeight * verifyProgress;
break;
case PHASE_EXTRACTING:
// 修复:添加当前包验签完成
float extractBase = BACKUP_WEIGHT +
(packageTotalWeight * currentPackageIndex) / totalPackageCount +
packageCopyWeight +
packageVerifyWeight; // 添加这行
// 增量:当前包解压进度
float extractProgress = currentExtractedBytes / (float) UpdateSize;
mProgress = extractBase + packageExtractWeight * extractProgress;
break;
case PHASE_DETECTING:
mProgress = BACKUP_WEIGHT + packageTotalWeight +
VERIFICATION_WEIGHT * (currentVerifiedBytes / (float) totalUpdateSize);
break;
case PHASE_CLEANUP:
// case PHASE_ROLLBACK:
// mProgress = 0.99f;
// p3-5 break;
}
return Math.min(Math.round(mProgress * 10000) / 100.00f, 100.00f);
}
// 结果通知
private void notifyListener(int resultCode, String message) {
new Handler(Looper.getMainLooper()).post(() -> {
if (updateListener != null) {
updateListener.onResult(resultCode, message);
}
});
}
// 获取当前进度百分比
private int getCurrentProgress() {
// 此处可添加子任务进度计算
return (int) mProgress;
}
// ================== 核心更新逻辑 ==================
private class UpdateTask extends AsyncTask<Void, Void, Integer> {
// private File backupFile;//zwx-s
private File[] updatePackages;
@Override
protected void onPreExecute() {
currentPhase.set(PHASE_DETECTING);
isCancelled = false;
isPaused = false;
currentCopiedBytes = 0;
currentExtractedBytes = 0;
mProgress = 0; // 重置进度为0
}
@Override
protected Integer doInBackground(Void... voids) {
try {
// 阶段1: 备份数据
currentPhase.set(PHASE_BACKUP);
notifyProgress("开始备份数据...");
backupDir = new File(cacheDir, "backup");
Log.i(TAG, "初始化备份目录为:" + backupDir.getAbsolutePath());//ZWX
if (backupDir.exists()) {
if (!FileUtils.deleteRecursive(backupDir)) {
throw new IOException("删除备份文件失败: " + backupDir.getAbsolutePath());
}
}
if (!backupDir.mkdirs()) {
throw new IOException("创建备份目录失败: " + backupDir.getAbsolutePath());
}
// 计算实际备份大小
Log.i(TAG, "核对实际需要备份的数据大小");
// backupSize = estimateBackupSize();
backupSize = FileUtils.getDirectorySize(storageDir);
Log.i(TAG, "需要备份的数据大小:[" + formatSize(backupSize) + "]");
Log.i(TAG, "storage目录为:" + storageDir.getAbsolutePath());//ZWX
if (backupSize > 0) {
Log.i(TAG, "开始进行数据备份");
try {
// 执行目录复制
if (!singlecopydirectory.copyDirectoryWithProgress(storageDir, backupDir, (copied, total) -> {
currentCopiedBytes = copied;
notifyProgress("备份中: " + formatSize(copied) + "/" + formatSize(total));
})) {
throw new IOException("备份失败: " + storageDir.getAbsolutePath() + " -> " + backupDir.getAbsolutePath());
}
notifyProgress("数据备份完成");//P4
if (!backupDir.renameTo(backupAlreadyDir)) {
throw new IOException("重命名备份目录失败: " + backupDir.getAbsolutePath() + " -> " + backupAlreadyDir.getAbsolutePath());
}
Log.i(TAG, "重命名备份目录成功");
} catch (Exception e) {
Log.e(TAG, "备份过程中发生错误", e);
// 可选:尝试二次备份或记录日志,这里直接抛出错误
return ERROR_COPY_FAILED;
}
} else {
// 无数据需要备份,直接标记完成
Log.i(TAG, "无备份数据,直接进行数据更新");
currentCopiedBytes = 1;
backupSize = 1;
notifyProgress("无数据需要备份");
}
// 检查是否被取消(场景1)
if (isCancelled) {
return ERROR_USER_CANCELED;
}
//zwx-end
// 阶段2: 处理升级包
//p5:更新前清理压缩包
if (storageDir.exists() && storageDir.isDirectory()) {
if (!singlecopydirectory.clearDirectoryContentsOnly(storageDir.toPath())) {
throw new IOException("无法清空目标目录: " + storageDir.getAbsolutePath());
}
}
updatePackages = getUpdatePackages();
Log.i(TAG, "开始处理升级包【拷贝、解密验签、解压】");
for (currentPackageIndex = 0; currentPackageIndex < updatePackages.length; currentPackageIndex++) {
if (isCancelled) return ERROR_USER_CANCELED;
// 处理暂停状态
while (isPaused) {
Thread.sleep(500);
}
File packageFile = updatePackages[currentPackageIndex];
UpdateSize = updatePackages[currentPackageIndex].length();
String packageName = packageFile.getName();
long packageSize = packageFile.length();
// 备份完成后重置拷贝计数器
currentCopiedBytes = 0;
// 阶段3: 拷贝升级包
currentPhase.set(PHASE_COPYING);
File destFile = new File(storageDir, packageName);
// 拷贝时更新进度(修改)
boolean copyResult = FileUtils.copyFileWithProgress(
packageFile,
destFile,
(copied, total) -> {
currentCopiedBytes = copied;
notifyProgress(String.format("拷贝 %s: %s/%s",
packageName,
formatSize(copied),
formatSize(total)));
}
);
if (!copyResult) {
lastErrorMessage = "拷贝失败: " + packageName;
return ERROR_COPY_FAILED;
}
// 阶段4:解密验签
currentPhase.set(PHASE_CHECKING);
currentVerifiedBytes = 0; // 重置验签计数器
// 创建进度回调适配器
DecryptUtil.ProgressCallback decryptCallback = new DecryptUtil.ProgressCallback() {
@Override
public void onProgress(long processed, long total) {
// 直接更新验签进度计数器
currentVerifiedBytes = processed;
// 触发进度通知
notifyProgress(String.format("解密验签 %s: %s/%s",
packageName,
formatSize(processed),
formatSize(total)));
}
};
// 执行解密验签(传入回调)
if (!dataVerification(destFile.getAbsolutePath(), decryptCallback)) {
if (!isCancelled) {
return ERROR_DECRYPT_OR_SIGN_FAILED;
}
}
// 确保进度设置为100%
currentVerifiedBytes = UpdateSize;
// 阶段5: 解压升级包
currentPhase.set(PHASE_EXTRACTING);
// 修复:重置解压计数器
currentExtractedBytes = 0; // 重置计数器
notifyProgress("解压升级包: " + packageName);
// 解压时更新进度(修改)
boolean extractResult = FileUtils.extractZipWithProgress(
destFile,
storageDir,
(extracted, total) -> {
currentExtractedBytes = extracted;
notifyProgress(String.format("解压 %s: %s/%s",
packageName,
formatSize(extracted),
formatSize(total)));
}
);
if (!extractResult) {
lastErrorMessage = "解压失败: " + packageName;
return ERROR_EXTRACT_FAILED;
}
// 删除已解压的升级包以节省空间
if (!destFile.delete()) {
Log.w(TAG, "删除升级包失败: " + destFile.getName());
}
// 更新解压进度(完成当前包)
currentExtractedBytes += packageSize;
}
if (!mapDataController.checkInstallationStatus()) {
notifyProgress("校验失败");
return ERROR_FILE_VERIFY_FAILED;
} else {
notifyProgress("校验成功");
}
// MapDownloader.fromEngineAsync(sdkNativeEngine, new MapDownloaderConstructionCallback() {
// @Override
// public void onMapDownloaderConstructedCompleted(@NonNull MapDownloader downloader) {
// Log.i(TAG, "数据同步成功");
// }
// });
// 阶段5: 清理工作
currentPhase.set(PHASE_CLEANUP);
notifyProgress("清理缓存...");
if (backupDir.exists() && !FileUtils.deleteRecursive(backupDir)) {
Log.w(TAG, "删除备份文件失败");
}
// 最终进度设为100%
notifyProgress("更新完成");
return SUCCESS;
} catch (InterruptedException e) {
// 场景1:备份未完成时被取消
// if (backupDir != null && backupDir.exists()) {
//// backupFile.delete();
// if (!FileUtils.deleteRecursive(backupDir)) {
// Log.w(TAG, "删除备份文件失败: " + backupDir.getAbsolutePath());
// }
// }
lastErrorMessage = "更新任务被中断";
return ERROR_USER_CANCELED;
} catch (Exception e) {
lastErrorMessage = "未知错误: " + e.getMessage();
Log.e(TAG, "更新失败", e);
return ERROR_UNEXPECTED;
}
}
@Override
protected void onPostExecute(Integer resultCode) {
if (resultCode == SUCCESS) {
if (backupAlreadyDir != null && backupAlreadyDir.exists()) {
Log.w(TAG, "开始删除备份文件: " + backupAlreadyDir.getAbsolutePath());
if (!FileUtils.deleteRecursive(backupAlreadyDir)) {
Log.w(TAG, "删除备份文件失败: " + backupAlreadyDir.getAbsolutePath());
}
}
notifyListener(SUCCESS, "更新成功,请重启车机");
currentPhase.set(PHASE_IDLE);
currentTask = null;
}
else {
if (!ishandleFailure && backupAlreadyDir.exists()) {
handleFailure(resultCode);//p4-1
}
}
}
}
// ================== 启动时恢复检查 ==================
private void checkRecoveryOnStartup() {
// File backupFile = findLatestBackupFile();
Log.i(TAG, "=== 启动时恢复检查 ===" );
// storageSize = FileUtils.getDirectorySize(storageDir);
// backupAlreadyDir =
//backAlreadysize
if (backupDir != null && backupDir.exists()) {
// 场景1:存在未完成的备份目录(backup)
Log.w(TAG, "检测到残留备份目录: " + backupDir.getAbsolutePath() + ",该目录将被删除");
// 删除未完成的备份目录
if (!FileUtils.deleteRecursive(backupDir)) {
Log.e(TAG, "无法删除未完成的备份目录: " + backupDir.getAbsolutePath());
}
}
if (!ishandleFailure && backupAlreadyDir.exists()) {
handleFailure(ERROR_PHASE_ROLLBACK);//p4
}
notifyProgress("数据恢复完成");
this.updateListener = null;
}
private void checkStoragePerformance() {
long writeSpeed = StorageUtils.measureWriteSpeed(storageDir);
Log.d(TAG, "存储写入速度: " + formatSize(writeSpeed) + "/s");
if (writeSpeed < 50 * 1024 * 1024) { // 低于 50MB/s
Log.w(TAG, "检测到低速存储设备,还原操作可能较慢");
}
}
//p3-7 p4-2
protected void handleFailure(Integer resultCode) {
if (ishandleFailure) {
Log.w(TAG, "handFailure is already running, skip duplicate call");
return;
}
ishandleFailure = true;
isRollingBack = true;
rollbackTotalSize = FileUtils.getDirectorySize(backupAlreadyDir);
Log.w(TAG, "handFailure is running " +
"已经完成备份的文件夹路径: " + backupAlreadyDir.getAbsolutePath());
if (backupDir != null && backupDir.exists()) {
// 场景1:存在未完成的备份目录(backup)
Log.w(TAG, "检测到残留备份目录: " + backupDir.getAbsolutePath() + ",该目录将被删除");
// 删除未完成的备份目录
if (!FileUtils.deleteRecursive(backupDir)) {
Log.e(TAG, "无法删除未完成的备份目录: " + backupDir.getAbsolutePath());
}
}
if (backupAlreadyDir != null && backupAlreadyDir.exists()) {
// 场景2:进入回滚流程
currentPhase.set(PHASE_ROLLBACK);
// 先发送回滚进度通知(99%)
notifyProgress("更新失败,正在回滚数据...");
Log.w(TAG, "更新失败,正在回滚数据,回滚目录: " + backupAlreadyDir.getAbsolutePath());
// 启动回滚线程
new Thread(() -> {
try {
// 执行回滚
if (performRollback(backupAlreadyDir)) {
if (backupAlreadyDir.exists() && !FileUtils.deleteRecursive(backupAlreadyDir)) {
Log.w(TAG, "删除备份文件失败: " + backupAlreadyDir.getAbsolutePath());
}
}
} finally {
// 回滚完成后删除备份
// backupFile.delete();
if (!isCancelled) {
// 回滚完成后发送最终结果
notifyListener(resultCode, getErrorMessage(resultCode));
}
// 重置状态
ishandleFailure = false;
currentPhase.set(PHASE_IDLE);
currentTask = null;
isRollingBack = false;
}
}).start();
} else {
// 场景1:没有备份文件,直接报告错误
notifyListener(resultCode, lastErrorMessage);
currentPhase.set(PHASE_IDLE);
currentTask = null;
}
}
//p3-6
private boolean performRollback(File backupAlreadyDir) {
Log.w(TAG, "performRollback IS RUNNING ");
try {
// 设置回滚标志
// 获取备份大小
if (rollbackTotalSize <= 0) {
throw new IOException("备份目录为空或不可读: " + backupAlreadyDir.getAbsolutePath());
}
// 设置回滚总大小
rollbackProcessedSize = 0;
if (!storageDir.canWrite()) {
throw new IOException("目标目录不可写: " + storageDir.getAbsolutePath());
}
Log.w(TAG, "检查storage目录是否可写:"+storageDir.canWrite());
// 删除当前升级后的数据 p3.4:回滚前置步骤删除失败->更改删除方法
if (storageDir.exists() && storageDir.isDirectory()) {
if (!singlecopydirectory.clearDirectoryContentsOnly(storageDir.toPath())) {
throw new IOException("无法清空目标目录: " + storageDir.getAbsolutePath());
}
}
// 开始恢复备份
notifyProgress("开始恢复备份...");
// 执行目录复制并监听进度
Log.w(TAG, "准备执行:singlecopydirectory.copyDirectoryWithProgress");
//p7
boolean success = singlecopydirectory.copyDirectoryWithProgress(backupAlreadyDir, storageDir, (copied, total) -> {
rollbackProcessedSize = copied;
rollbackTotalSize = total;
new Handler(Looper.getMainLooper()).post(() -> {
notifyProgress("恢复中: " + formatSize(copied) + "/" + formatSize(total));
});
});
if (!success) {
throw new IOException("恢复备份失败");
}
ishandleFailure = false;
// 回滚完成
notifyProgress("数据恢复完成");
return true;
} catch (Exception e) {
Log.e(TAG, "回滚过程中发生错误", e);
return false;
}
}
// 释放资源
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 {
// 递归删除整个目录
deleteDirectoryRecursively(signDir);
} catch (IOException | SecurityException e) {
// 处理异常
}
}
}
private void deleteDirectoryRecursively(Path path) throws IOException {
if (Files.isDirectory(path)) {
// 使用 try-with-resources 确保 Stream 关闭
try (Stream<Path> children = Files.list(path)) {
children.forEach(child -> {
try {
deleteDirectoryRecursively(child);
} catch (IOException e) {
// 处理子项删除异常
throw new UncheckedIOException(e); // 转换为 RuntimeException 以便在 Stream 中抛出
}
});
} catch (UncheckedIOException e) {
// 重新抛出原始 IOException
throw e.getCause();
}
}
// 删除空目录或文件
Files.deleteIfExists(path);
}
}
取消已经成功触发了回滚,并且走到了:准备执行:singlecopydirectory.copyDirectoryWithProgress
但是实际上却并没有执行
最新发布