在window分区上是无法创建/解压link文件的

在linux中,在挂载的window分区下解压ST开发环境,失败。 原因:在window分区上,无法创建link文件~

         。。。

tar: STLinux-2.4/devkit/sh4/target/usr/include/tcl-private/generic/tcl.h: Cannot create symlink to `../../tcl.h': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/include/tcl-private/generic/tclTomMath.h: Cannot create symlink to `../../tclTomMath.h': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/include/tcl-private/generic/tclDecls.h: Cannot create symlink to `../../tclDecls.h': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/include/sysfs/libsysfs.h: Cannot create symlink to `../libsysfs.h': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/T00hwinfo: Cannot create symlink to `../test_scripts/get_hw_info': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/T10single/T00xterm_stress: Cannot create symlink to `../../test_scripts/xterm_stress': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/T10single/T01ltp: Cannot create symlink to `../../test_scripts/ltp': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/T99screen_blank: Cannot create symlink to `../test_scripts/screen_blank': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/D01stats: Cannot create symlink to `../test_scripts/statslogging': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/T00sysrq_on: Cannot create symlink to `../test_scripts/sysrq-on': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/tests/ltp/tools/pounder21/test_repo/T01screen_noblank: Cannot create symlink to `../test_scripts/screen_noblank': Protocol error
tar: STLinux-2.4/devkit/sh4/target/usr/bin: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/usr/share/locale: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/usr/share: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/dev: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/fonts/conf.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/fonts: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc2.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc3.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rcS.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc6.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc4.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc5.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc1.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d/rc0.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc/rc.d: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/etc: Directory renamed before its status could be extracted
tar: STLinux-2.4/devkit/sh4/target/bin: Directory renamed before its status could be extracted
tar: STLinux-2.4/host/share/libtool/config: Directory renamed before its status could be extracted
tar: STLinux-2.4/host/share/libtool: Directory renamed before its status could be extracted
tar: STLinux-2.4/host/share/automake-1.11: Directory renamed before its status could be extracted
tar: STLinux-2.4/host/share: Directory renamed before its status could be extracted
tar: STLinux-2.4/host: Directory renamed before its status could be extracted
tar: Exiting with failure status due to previous errors
[root@ Inspur 13:32:47]$

 

 

10:11:13.311 I message: 备份中: 1.1GB/13.6GB progress:0.78 10:11:14.073 I message: 备份中: 1.1GB/13.6GB progress:0.85 10:11:14.633 I ViewPostIme pointer 0 10:11:14.633 I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@cce4c8e 10:11:14.668 I onDisplayChanged oldDisplayState=2 newDisplayState=2 10:11:14.771 I ViewPostIme pointer 1 10:11:14.772 I 触发取消操作,错误码: 8 10:11:14.781 I update, w= 1920 h= 1200 mName = VRI[MainActivity]@cce4c8e mNativeObject= 0xb400006f04975f00 sc.mNativeObject= 0xb400006f8077dac0 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 10:11:14.781 V Relayout Window{f7811ab u0 com.kotei.overseas.navi/com.kotei.overseas.navi.base.MainActivity}: viewVisibility=0 req=1920x1200 ty=1 d0 10:11:14.781 I Relayout returned: old=(0,0,1920,1200) new=(0,0,1920,1200) relayoutAsync=true req=(1920,1200)0 dur=1 res=0x0 s={true 0xb400006f04a87800} ch=false seqId=0 10:11:14.782 I registerCallbackForPendingTransactions 10:11:14.787 I mWNT: t=0xb400006efcea5480 mBlastBufferQueue=0xb400006f04975f00 fn= 156 HdrRenderState mRenderHdrSdrRatio=1.0 caller= android.view.ViewRootImpl$9.onFrameDraw:6276 android.view.ViewRootImpl$3.onFrameDraw:2440 android.view.ThreadedRenderer$1.onFrameDraw:761 10:11:17.761 I ViewPostIme pointer 0 10:11:17.772 I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@cce4c8e 10:11:17.789 I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@cce4c8e 10:11:17.979 I ViewPostIme pointer 1 10:11:19.347 I ViewPostIme pointer 0 10:11:19.490 I ViewPostIme pointer 1 10:11:19.492 I === 启动时恢复检查 === 10:11:19.492 W 检测到未完成的临时备份目录,将被删除: /data/user/0/com.kotei.overseas.navi/cache/backup 10:11:19.830 I USB mounted: /data/data/com.kotei.overseas.navi/files 10:11:19.848 I Skipped 32 frames! The application may be doing too much work on its main thread. 10:11:19.857 I update, w= 1920 h= 1200 mName = VRI[MainActivity]@cce4c8e mNativeObject= 0xb400006f04975f00 sc.mNativeObject= 0xb400006f8077dac0 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 10:11:19.857 V Relayout Window{f7811ab u0 com.kotei.overseas.navi/com.kotei.overseas.navi.base.MainActivity}: viewVisibility=0 req=1920x1200 ty=1 d0 10:11:19.857 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 0xb400006f04a87800} ch=false seqId=0 10:11:19.857 I registerCallbackForPendingTransactions 10:11:19.860 I mWNT: t=0xb400006f04ab4600 mBlastBufferQueue=0xb400006f04975f00 fn= 235 HdrRenderState mRenderHdrSdrRatio=1.0 caller= android.view.ViewRootImpl$9.onFrameDraw:6276 android.view.ViewRootImpl$3.onFrameDraw:2440 android.view.ThreadedRenderer$1.onFrameDraw:761 10:11:20.150 I ViewPostIme pointer 0 10:11:20.275 I ViewPostIme pointer 1 10:11:23.119 W ApkAssets: Deleting an ApkAssets object '<empty> and /data/app/~~lguoqflx_aEwrmTF2ebzpA==/com.kotei.overseas.navi-9-WqdwsYgZDia2MaCVcA5g==/base.apk' with 1 weak references 10:11:23.276 I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@cce4c8e 10:11:24.971 I onDisplayChanged oldDisplayState=2 newDisplayState=2 10:11:26.959 I ViewPostIme pointer 0 10:11:26.960 I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@cce4c8e 10:11:26.995 I onDisplayChanged oldDisplayState=2 newDisplayState=2 10:11:27.074 I ViewPostIme pointer 1 10:11:30.076 I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@cce4c8e 10:11:31.751 I ViewPostIme pointer 0 10:11:31.751 I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@cce4c8e 10:11:31.869 I ViewPostIme pointer 1 10:11:34.870 I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@cce4c8e 10:11:34.993 I onDisplayChanged oldDisplayState=2 newDisplayState=2 10:11:36.352 I ViewPostIme pointer 0 10:11:36.353 I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@cce4c8e 10:11:36.388 I onDisplayChanged oldDisplayState=2 newDisplayState=2 10:11:36.468 I ViewPostIme pointer 1 10:11:36.483 I resultCode: 5 message:已有更新任务正在进行 10:11:36.483 I resultCode: 5 message:已有更新任务正在进行 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.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 (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; sdkNativeEngine = SDKNativeEngine.getSharedInstance(); mapDataController = MapDataController.getInstance(); 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"); 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 = 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 rxUpdateManager != null && currentPhase.get() != PHASE_IDLE; // } // 0904 p1:目前点击更新进入更新流程后立刻点击取消,再次点击更新会卡到“已有更新任务,此处可能重置状态 public boolean isOfflineUpdate() { int currentPhaseVal = currentPhase.get(); return rxUpdateManager != null && currentPhaseVal != PHASE_IDLE && currentPhaseVal != PHASE_ROLLBACK; } /** * 开始更新 */ /** * 启动更新流程。 * <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 int getCurrentProgress() { // 此处可添加子任务进度计算 return (int) mProgress; } //rxjavazwx---异步任务---zwx /** * 重置所有状态变量到初始值。 * 在一次完整的流程(成功、失败或取消)结束后调用。 */ 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) { 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; } // 关键改进:统一的暂停/取消检查方法 // private void checkInterrupted(ObservableEmitter<Integer> emitter) throws InterruptedException { // while (isPaused && !emitter.isDisposed()) { // Thread.sleep(500); // } // if (emitter.isDisposed() || isCancelled.get()) { // throw new InterruptedException("操作被取消"); // } // } // 暂停/恢复方法 public void pauseUpdate() { isPaused.set(true); } public void resumeUpdate() { isPaused.set(false); } // 成功处理(与AsyncTask的onPostExecute一致) private void handleSuccess() { if (backupAlreadyDir.exists() && !FileUtils.deleteRecursive(backupAlreadyDir)) { System.out.println("删除最终备份失败"); } notifyListener(SUCCESS, "更新成功,请重启车机"); currentPhase.set(PHASE_IDLE); rxUpdateManager = null; } } // ================== 启动时恢复检查 ================== 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) { // 处理异常 } } } }
最新发布
09-05
评论 2
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值