7.12.B树的插入和删除

一.B树的插入:

1.实例:

如上图,

从NULL开始建立一个B树,并且规定是一个5阶B树,也就是结点内的关键字个数至少为2个,至多为4个,且结点内的关键字要求按照递增排序,

现在插入关键字25,显然要放入根结点内,

如下图:

如上图,

  • 当前处理的结点内只有1个关键字,根据"结点内的关键字个数至少为2个,至多为4个"接下来必须要在该结点内插入至少一个关键字

接下来插入关键字38,38比25更大,所以放到28的后面即可,

如下图:

如上图,

  • 当前处理的结点内有2个关键字,根据"结点内的关键字个数至少为2个,至多为4个"接下来可以在该结点内继续插入关键字,也可以建立一个新结点进行插入操作->现在选择"在该结点内继续插入关键字"

接下来插入关键字49,49比38更大,所以放到38的后面即可,

如下图:

如上图,

  • 当前处理的结点内有3个关键字,根据"结点内的关键字个数至少为2个,至多为4个"接下来可以在该结点内继续插入关键字,也可以建立一个新结点进行插入操作->现在选择"在该结点内继续插入关键字"

接下来插入关键字60,60比49更大,所以放到49的后面即可,

如下图:

如上图,

  • 当前处理的结点内有4个关键字,根据"结点内的关键字个数至少为2个,至多为4个"接下来只能建立一个新结点进行插入操作

因此接下来如果往当前结点即根结点内继续插入一个关键字80,就会导致根结点内关键字的个数超出4个,

注:这里先在当前结点即根结点内按照递增顺序插入关键字80,

如下图:

如上图,

对于这种情况,需要把当前结点即根结点分裂成2个结点,

分裂规则如下图:

如上图,

接下来插入关键字90,

  • 需要注意的是每一次新插入的元素一定是插入到最底层的终端结点中,用"查找"规则来确定插入位置(详情见"7.11.B树"),

从根结点出发,由于关键字90大于49,且49的右边已经没有关键字了,所以90要插在49的右孩子指针所指的子树中,

49的右孩子指针所指的子树中根结点包含的关键字为60、80,该结点内的关键字个数没有达到上限,所以可以继续插入关键字,接下来检查该结点内的关键字,

第一个关键字60比90更小,所以会检查下一个关键字80,

第二个关键字80比90更小,又因为80后已经没有关键字了,所以90应插在80的后面,

如下图:

注:如果新插入的元素90没有插在终端结点而是插在根结点中,这显然是错误的,因为如果90插在根结点中,90就需要插在49的后面,由于90后面没有关键字了,因此90后面就是一个空指针NULL,也就是紫色的叶子结点,这时意味着B树中各个紫色的失败结点已经不属于同一层了,这样就不满足B树的特性了,所以新结点一定插入到最下面的终端结点中,

如下图:

如上图,

接下来插入关键字99,

  • 需要注意的是每一次新插入的元素一定是插入到最底层的终端结点中,用"查找"规则来确定插入位置(详情见"7.11.B树"),

从根结点出发,由于关键字99大于49,且49的右边已经没有关键字了,所以99要插在49的右孩子指针所指的子树中,

49的右孩子指针所指的子树中根结点包含的关键字为60、80、90,该结点内的关键字个数没有达到上限,所以可以继续插入关键字,接下来检查该结点内的关键字,

第一个关键字60比99更小,所以会检查下一个关键字80,

第二个关键字80比99更小,所以会检查下一个关键字90,

第三个关键字90比99更小,又因为90后已经没有关键字了,所以99应插在90的后面,

如下图:

如上图,

接下来插入关键字88,

  • 需要注意的是每一次新插入的元素一定是插入到最底层的终端结点中,用"查找"规则来确定插入位置(详情见"7.11.B树"),

从根结点出发,由于关键字88大于49,且49的右边已经没有关键字了,所以88要插在49的右孩子指针所指的子树中,

49的右孩子指针所指的子树中根结点包含的关键字为60、80、90、99,该结点内的关键字个数已经达到上限,所以如果继续插入关键字,就必须在新的结点内插入,但首先需要检查该结点内的关键字,

第一个关键字60比88更小,所以会检查下一个关键字80,

第二个关键字80比88更小,所以会检查下一个关键字90,

第三个关键字90比88更大,所以88应插在80和90之间,

如下图:

如上图,

  • 注:此时是5阶B树

当前处理的结点中包含了关键字60、80、88、90、99,关键字个数为5个,已经超出了上限,

所以要把中间位置⌈m/2⌉=⌈5/2⌉=3索引上的元素88提到其父结点中,然后把88左、右两个部分的元素分别放到两个不同的结点中,

元素88右边的元素都比88大,所以把元素88右边的元素都连到元素88右边的指针中,这么做刚好也保证了"左<中<右"的特性(元素88左边的元素同理),

如下图:

如上图,

接下来插入关键字83,

  • 需要注意的是每一次新插入的元素一定是插入到最底层的终端结点中,用"查找"规则来确定插入位置(详情见"7.11.B树"),

从根结点出发,根结点包含的关键字为49,88,接下来检查该结点内的关键字,

第一个关键字49比83更小,所以会检查下一个关键字88,

第二个关键字88比83更大,所以83应插在49和88之间,

因此接下来要检查包含关键字60、80的结点,该结点内的关键字个数没有达到上限,所以可以继续插入关键字,接下来检查该结点内的关键字,

第一个关键字60比83更小,所以会检查下一个关键字80,

第二个关键字80比83更小,又因为80后已经没有关键字了,所以83应插在80的后面,

如下图:

如上图,

接下来插入关键字87,原理同上,

如下图:

如上图,

接下来插入关键字70,原理同上,

70应插在60和80之间,

如下图:

如上图,

  • 注:此时是5阶B树

当前处理的结点中包含了关键字60、70、80、83、87,关键字个数为5个,已经超出了上限,

所以要把中间位置⌈m/2⌉=⌈5/2⌉=3索引上的元素80提到其父结点中,然后把80左、右两个部分的元素分别放到两个不同的结点中,

现在要思考的问题是:元素80应该放到其父结点的哪个位置呢?显然,把元素80放到其父结点中应保证其父结点中的元素依然是有序排放的,所以80应放到49和88中间,

如下图:

如上图,

  • 总结:如果一个关键字因分裂需要把它提到父结点中,那么就应该把这个关键字放到指向它所属的结点的这条指针所对应的这个点的右边的位置。

接下来依次插入关键字92、93、94,

如下图:

如上图,

  • 注:此时是5阶B树

当前处理的结点中包含了关键字90、92、93、94、99,关键字个数为5个,已经超出了上限,

所以要把中间位置⌈m/2⌉=⌈5/2⌉=3索引上的元素93提到其父结点中,然后把93左、右两个部分的元素分别放到两个不同的结点中,

现在要思考的问题是:元素93应该放到其父结点的哪个位置呢?显然,把元素93放到其父结点中应保证其父结点中的元素依然是有序排放的,所以93应放到88的后面,

还需要把93右边的元素所组成的新结点连到93的右指针上

如下图:

如上图,

接下来依次插入关键字73、74、75,

如下图:

如上图,

  • 注:此时是5阶B树

当前处理的结点中包含了关键字60、70、73、74、75,关键字个数为5个,已经超出了上限,

所以要把中间位置⌈m/2⌉=⌈5/2⌉=3索引上的元素73提到其父结点中,然后把73左、右两个部分的元素分别放到两个不同的结点中,

现在要思考的问题是:元素73应该放到其父结点的哪个位置呢?显然,把元素73放到其父结点中应保证其父结点中的元素依然是有序排放的,所以73应放到49和80中间,

还需要把73右边的元素所组成的新结点连到73的右指针上

如下图:

如上图,

  • 注:此时是5阶B树

此时整棵树的根结点中包含了关键字49、73、80、88、93,关键字个数为5个,已经超出了上限,

整棵树的根结点不存在父结点,那么这种情况该如何处理呢?需要把整棵树的根结点继续向上分裂,这种情况下分裂的方法与之前类似,

所以要把中间位置⌈m/2⌉=⌈5/2⌉=3索引上的元素80提到其父结点中,由于现在整棵树的根结点没有父结点,所以会新建立一个结点作为根结点的父结点,然后把80放到该父结点中,最后把80的左、右两个部分连在父结点的左、右两边,

如下图:

如上图,

此时满足了5阶B树的特性与要求,根结点中允许只有1个关键字,除了根结点、叶子结点外的结点中关键字至少2个、至多4个。

2.总结:


二.B树的删除:

1.实例:

以上述图片的5阶B树为例,

首先删除关键字60,由于60位于终端结点,所以直接删除即可,

并且删除60之后,当前所操作的结点的关键字个数为3

并没有低于5阶B树所要求的下限2,

如下图:

如上图,

接下来删除关键字80,80位于非终端结点中,且80删除之后根结点就不存在了,

之后该怎么做呢?可以找到80的直接前驱或直接后继来顶替80的位置,

先看用80的直接前驱来顶替80的位置,80的直接前驱是77(找直接前驱的方法详情见"5.11.在线索二叉树中找前驱和后继"),

如下图:

如上图,

现在用关键字77顶替80的位置,

如下图:

如上图,

刚才的操作相当于把非终端结点的删除操作转换成对终端结点的删除操作,这是用直接前驱来替代。

接下来删除关键字77,77位于非终端结点中,且77删除之后根结点就不存在了,

因此可以用77的直接后继来顶替77的位置,77的直接后继是82(找直接前驱的方法详情见"5.11.在线索二叉树中找前驱和后继"),

如下图:

如上图,

现在用关键字82顶替77的位置,

关键字82移动后需要把82原来所在的结点里的关键字进行调整使其有序,

如下图:

如上图,

注:所找到的"直接前驱"或"直接后继"一定位于终端结点,所以对非终端结点的删除操作一定可以转化为对终端结点的删除操作,

如下图:

如上图,

接下来删除关键字38,

如下图:

如上图,

删除38之后,38原来所在的结点中只有1个关键字25,低于1个结点中关键字个数的下限2,

这时该如何处理呢?这个需要分多种情况来讨论->

情况一:"兄弟够借"->意思是当前结点的关键字个数如果低于下限,但是它的兄弟结点里的关键字个数是足够的,比如38原来所在的结点的右兄弟结点的关键字个数绰绰有余,所以可以把这个右兄弟结点中的关键字贡献出一个,比如把关键字70贡献出来,但这显然不对,因为在其上方的父结点中包含关键字49,49的左子树里的关键字都必须要小于49,所以如果把70贡献出来,就不满足B树的特性(同理其他的关键字也无法贡献出来),

如下图:

如上图,

那应该怎么解决呢?

  • 注:结点中关键字是递增排序的

只包含关键字25的结点中,25的后继就是指向25的结点的指针中右边的第一个关键字49,

25后继的后继即关键字49的后继,49的后继就是49右子树里最小的关键字70,

因此首先可以把关键字49放入包含关键字25的结点中,然后让关键字70顶替49的位置,这样就可以保证B树的特性,70的左子树中的所有关键字都比70小,70的右子树中的所有关键字都比70大,

所以刚才这种情况可以概述为"当右兄弟很宽裕时,会找到当前结点的后继结点、后继的后继结点来填补空缺",

如下图:

如上图,

接下来删除关键字90,

如下图:

如上图,

删除90之后,90原来所在的结点中只有1个关键字92,低于1个结点中关键字个数的下限2,

那应该怎么解决呢?

  • 注:结点中关键字是递增排序的

如果让只有1个关键字92的结点的右兄弟结点贡献一个关键字的话,该右兄弟结点中关键字的个数就会低于2,不符合5阶B树的特性,

但只有1个关键字92的结点的左兄弟结点显然关键字是绰绰有余的,所以可以向左兄弟借,

所以这种情况可以概述为"当左兄弟很宽裕时,会找到当前结点的前驱关键字、前驱的前驱关键字来填补空缺",

只包含关键字92的结点中,92的前驱就是指向92的结点指针左边的关键字88,

92前驱的前驱即关键字88的前驱,88的前驱就是88左子树中最大的关键字87,

接下来就是把88插入到92的前面,然后用87顶替88的位置,

此时就保证了5阶B树的特性,

如下图:

如上图,

总之,删除操作必须要始终保持B树的特性,

如下图:

如上图,

接下来删除关键字49,

如下图:

如上图,

删除49之后,49原来所在的结点中只有1个关键字25,低于1个结点中关键字个数的下限2,

那应该怎么解决呢?

  • 注:结点中关键字是递增排序的

如果让只有1个关键字25的结点的右兄弟结点贡献一个关键字的话,该右兄弟结点中关键字的个数就会低于2,不符合5阶B树的特性,而且只有1个关键字25的结点没有左兄弟结点->情况二:"兄弟不够借"

因此可以把只有1个关键字25的结点和其右兄弟结点合并,

除了要合并只有1个关键字25的结点和其右兄弟结点外,还需要把这两个结点中间的关键字70给一起合并到结点中,

如下图:

如上图,

由于刚才从包含关键字25的结点的父结点中借走了一个关键字70,此时只剩下关键字73,低于1个结点中关键字个数的下限2,也属于"兄弟不够借"的情况,

因此要把只包含关键字73的结点和其右兄弟结点(只包含关键字73的结点没有左兄弟结点)进行合并,

除了要合并只有1个关键字73的结点和其右兄弟结点外,还需要把这两个结点中间的关键字82给一起合并到结点中,

如下图:

如上图,

此时符合5阶B树的特性。

2.总结:


三.总结:


2025-09-01 09:36:04.864 13222-13285 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 4.0GB/5.7GB progress:7.03 2025-09-01 09:36:05.719 13222-13285 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 4.1GB/5.7GB progress:7.12 2025-09-01 09:36:06.212 13222-13222 VRI[MainAc...ty]@4024c0 com.kotei.overseas.navi I ViewPostIme pointer 0 2025-09-01 09:36:06.212 13222-13222 VRI[MainAc...ty]@4024c0 com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@4024c0 2025-09-01 09:36:06.299 13222-13222 VRI[MainAc...ty]@4024c0 com.kotei.overseas.navi I ViewPostIme pointer 1 2025-09-01 09:36:10.729 13222-13227 i.overseas.navi com.kotei.overseas.navi I Background young concurrent copying GC freed 19MB AllocSpace bytes, 0(0B) LOS objects, 5% free, 315MB/334MB, paused 235us,631us total 156.848ms 2025-09-01 09:36:11.276 13222-13592 USBOfflineUpdater com.kotei.overseas.navi E 备份过程中发生错误 java.io.FileNotFoundException: /storage/emulated/0/Android/data/com.kotei.overseas.navi/files/overseas/data/mapoffline/v1/1MCztmBt85mbwQr8hRKtgg/ocm-map/ARCHIVES/b5/Njc5MjM5NDk4Njc.dba: open failed: ENOENT (No such file or directory) at libcore.io.IoBridge.open(IoBridge.java:574) at java.io.FileInputStream.<init>(FileInputStream.java:179) at com.kotei.overseas.navi.update.singlecopydirectory.smallFileCopy(singlecopydirectory.java:95) at com.kotei.overseas.navi.update.singlecopydirectory.copyFileWithProgress(singlecopydirectory.java:79) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:65) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63) at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryWithProgress(singlecopydirectory.java:35) at com.kotei.overseas.navi.update.USBOfflineUpdater$UpdateTask.doInBackground(USBOfflineUpdater.java:655) at com.kotei.overseas.navi.update.USBOfflineUpdater$UpdateTask.doInBackground(USBOfflineUpdater.java:613) at android.os.AsyncTask$3.call(AsyncTask.java:394) at java.util.concurrent.FutureTask.run(FutureTask.java:264) at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305) at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145) at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644) at java.lang.Thread.run(Thread.java:1012) Caused by: android.system.ErrnoException: open failed: ENOENT (No such file or directory) at libcore.io.Linux.open(Native Method) at libcore.io.ForwardingOs.open(ForwardingOs.java:563) at libcore.io.BlockGuardOs.open(BlockGuardOs.java:274) at libcore.io.ForwardingOs.open(ForwardingOs.java:563) at android.app.ActivityThread$AndroidOs.open(ActivityThread.java:9499) at libcore.io.IoBridge.open(IoBridge.java:560) at java.io.FileInputStream.<init>(FileInputStream.java:179)  at com.kotei.overseas.navi.update.singlecopydirectory.smallFileCopy(singlecopydirectory.java:95)  at com.kotei.overseas.navi.update.singlecopydirectory.copyFileWithProgress(singlecopydirectory.java:79)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:65)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryInternal(singlecopydirectory.java:63)  at com.kotei.overseas.navi.update.singlecopydirectory.copyDirectoryWithProgress(singlecopydirectory.java:35)  at com.kotei.overseas.navi.update.USBOfflineUpdater$UpdateTask.doInBackground(USBOfflineUpdater.java:655)  at com.kotei.overseas.navi.update.USBOfflineUpdater$UpdateTask.doInBackground(USBOfflineUpdater.java:613)  at android.os.AsyncTask$3.call(AsyncTask.java:394)  at java.util.concurrent.FutureTask.run(FutureTask.java:264)  at android.os.AsyncTask$SerialExecutor$1.run(AsyncTask.java:305)  at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1145)  at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:644)  at java.lang.Thread.run(Thread.java:1012)  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; // 权重分配比例 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; // // 更新控制 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 long rollBackCopiedBytes = 0; private int currentPackageIndex = 0; // 当前处理的升级包索引 private int totalPackageCount = 0; // 总升级包数量 // 用于跟踪回滚状态 public boolean isRollingBack = false; private long rollbackTotalSize = 0; private long rollbackProcessedSize = 0; // 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(); Log.i(TAG, "初始化cacheDir目录为:" + cacheDir.getAbsolutePath());//ZWX storageDir = new File(sdkNativeEngine.getOptions().persistentMapStoragePath); Log.i(TAG, "初始化storageDir目录为:" + storageDir.getAbsolutePath());//ZWX // ✅ 初始化 backupDir backupDir = new File(cacheDir, "backup"); // ✅ 基于 cacheDir 固定路径 // 注册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 = true; 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(); } /** * 用户点击USB更新-点击查看-触发startUpdate-触发startUpdate重载(本方法)-调用UpdateTask */ public void startUpdate(UpdateListener listener) { this.updateListener = listener; 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, "检测到更新任务触发,开始进行地图更新"); notifyProgress("开始进行更新"); // 计算总工作量(新增) calculateTotalWorkload(); 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)); } // 获取更新包(新增) 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, "用户取消更新"); performRollback(backupDir);//zwx } private void cancelUpdate(int errorCode, String message) { isCancelled = true; lastErrorMessage = message; notifyListener(errorCode, message); } // 进度通知 private void notifyProgress(String message) { new Handler(Looper.getMainLooper()).post(() -> { if (updateListener != null) { // 计算当前总进度(修改) float 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) 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; // // break; case PHASE_ROLLBACK: // 增量:当前包拷贝进度 mProgress = rollBackCopiedBytes / (float) backupSize; 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("数据备份完成"); } 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: 处理升级包 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); File destFile = new File(cacheDir, packageName);//zwx修改 // 拷贝时更新进度(修改) 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(), storageDir.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) { notifyListener(SUCCESS, "更新成功,请重启车机"); currentPhase.set(PHASE_IDLE); currentTask = null; } else { // 只有备份完成时才进行回滚(场景2) if (backupDir != null && backupDir.exists()) { // 场景2:进入回滚流程 isRollingBack = true; currentPhase.set(PHASE_ROLLBACK); // 先发送回滚进度通知(99%) notifyProgress("更新失败,正在回滚数据..."); // 保存错误消息,因为回滚完成后还需要使用 final String errorMessage = lastErrorMessage; // 启动回滚线程 new Thread(new Runnable() { @Override public void run() { try { // 执行回滚 performRollback(backupDir); } finally { // 回滚完成后删除备份 // backupFile.delete(); if (backupDir.exists() && !FileUtils.deleteRecursive(backupDir)) { Log.w(TAG, "删除备份文件失败: " + backupDir.getAbsolutePath()); } if (!isCancelled) { // 回滚完成后发送最终结果 notifyListener(resultCode, getErrorMessage(resultCode)); } // 重置状态 currentPhase.set(PHASE_IDLE); currentTask = null; isRollingBack = false; } } }).start(); } else { // 场景1:没有备份文件,直接报告错误 notifyListener(resultCode, lastErrorMessage); currentPhase.set(PHASE_IDLE); currentTask = null; } } } } // ================== 启动时恢复检查 ================== private void checkRecoveryOnStartup() { // File backupFile = findLatestBackupFile(); Log.i(TAG, "=== 启动时初始化检查 ===" ); storageSize = FileUtils.getDirectorySize(storageDir); backupSize = FileUtils.getDirectorySize(backupDir); Log.i(TAG, "检测到备份数据大小为: " + backupSize); Log.i(TAG, "检测到storage数据大小为: " + storageSize); // 存在备份文件说明上次更新中断 if (backupDir != null && backupDir.exists()) { // long fileSize = backupFile.length(); // long fileSize = backupSize; //// long expectedSize = estimateBackupSize(); // long expectedSize = storageSize; // 场景3:备份未完成(文件大小小于预期大小的90%) if (backupSize < storageSize ) { isRollingBack = false; Log.i(TAG, "检测到未完成的备份,删除: " + backupDir.getName()); // backupFile.delete(); if (backupDir.exists() && !FileUtils.deleteRecursive(backupDir)) { Log.w(TAG, "删除备份文件失败: " + backupDir.getAbsolutePath()); } return; }else { // 场景4:备份已完成,启动回滚 Log.i(TAG, "检测到备份数据大小为: " + backupSize); Log.i(TAG, "检测到storage数据大小为: " + storageSize); Log.i(TAG, "检测到完整的备份,开始回滚: " + backupDir.getName()); currentPhase.set(PHASE_ROLLBACK); notifyProgress("检测到未完成更新,正在恢复数据..."); // 执行回滚 performRollback(backupDir); isRollingBack = true; // 删除备份文件 // backupFile.delete(); notifyProgress("数据恢复完成"); } } Log.i(TAG, "=== 初始化检查结束未发现备份 ===" ); isRollingBack = false; 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, "检测到低速存储设备,还原操作可能较慢"); } } // 新增回滚方法-删除storage-复制备份目录到storage-删除备份目录 private void performRollback(File backupDir) { currentPhase.set(PHASE_COPYING); try { // 1. 设置回滚进度为99% backupSize = FileUtils.getDirectorySize(backupDir); notifyProgress("开始删除更新目录"); // 2. 删除更新后的数据并且保留storageDir文件夹 FileUtils.deleteDirectoryContents(storageDir); notifyProgress("开始恢复备份目录"); try { // 执行目录复制 if (!singlecopydirectory.copyDirectoryWithProgress(backupDir,storageDir, (copied, total) -> { rollBackCopiedBytes = copied; notifyProgress("回滚中: " + formatSize(copied) + "/" + formatSize(total)); })) { throw new IOException("回滚失败: " + storageDir.getAbsolutePath() + " -> " + backupDir.getAbsolutePath()); } notifyProgress("恢复数据完成开始删除备份目录"); //deleteRecursive:删除整个目录 if (backupDir.exists() && !FileUtils.deleteRecursive(backupDir)) { Log.w(TAG, "删除备份文件失败: " + backupDir.getAbsolutePath()); notifyProgress("恢复数据删除失败"); } notifyProgress("回滚完成"); } catch (Exception e) { Log.e(TAG, "回滚过程中发生错误", e); // 可选:尝试二次备份或记录日志,这里直接抛出错误 notifyProgress("回滚过程中发生错误"); } } catch (Exception e) { Log.e(TAG, "回滚过程中发生错误", e); } } // 释放资源 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 -> "文件解密/验签失败"; 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); } } 备份中途点击取消,程序未响应
09-02
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值