iOS 删除float小数点后多余的0,如:10.020000 修改成10.02

删除小数点后面多余的0,比如90.00100可以转换成90.001

新建一个NSString的分类removeFloatZero, 代码如下:

//删除小数点后面多余的0
-(NSString *)removeFloatUnuseZero {
    NSString *stringFloat = self;
    NSInteger length = [stringFloat length];
    if ([stringFloat containsString:@"."]) {
        
        for(NSInteger i = length - 1; i >= 0; i--) {
            NSString *subString = [stringFloat substringFromIndex:i];
            if(![subString isEqualToString:@"0"]) {
                if ([subString isEqualToString:@"."]) {
                    return [stringFloat substringToIndex:[stringFloat length] - 1];
                }else{
                    return stringFloat;
                }
            } else {
                stringFloat = [stringFloat substringToIndex:i];
            }
        }
    }
    return stringFloat;
}
2025-09-01 15:08:15.891 30801-30801 USBOfflineUpdater com.kotei.overseas.navi I USB mounted: /data/data/com.kotei.overseas.navi/files 2025-09-01 15:08:15.891 30801-31044 USBOfflineUpdater com.kotei.overseas.navi I === 启动时恢复检查 === 2025-09-01 15:08:15.906 30801-31044 USBOfflineUpdater com.kotei.overseas.navi I 检测到未完的备份,删除: /data/user/0/com.kotei.overseas.navi/cache/backup 2025-09-01 15:08:15.919 1360-10661 WindowManager system_server V Relayout Window{5a5234c u0 com.kotei.overseas.navi/com.kotei.overseas.navi.base.MainActivity}: viewVisibility=0 req=1920x1200 ty=1 d0 2025-09-01 15:08:15.919 30801-30801 BLASTBufferQueue_Java com.kotei.overseas.navi I update, w= 1920 h= 1200 mName = VRI[MainActivity]@84346fd mNativeObject= 0xb400007b462e1e00 sc.mNativeObject= 0xb400007bc39bf840 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-01 15:08:15.919 30801-30801 VRI[MainAc...y]@84346fd 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 0xb400007b45265000} ch=false seqId=0 2025-09-01 15:08:15.919 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I registerCallbackForPendingTransactions 2025-09-01 15:08:15.922 30801-30899 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I mWNT: t=0xb400007ac07f0680 mBlastBufferQueue=0xb400007b462e1e00 fn= 179 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-01 15:08:16.778 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 0 2025-09-01 15:08:16.925 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 1 2025-09-01 15:08:17.459 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 0 2025-09-01 15:08:17.578 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 1 2025-09-01 15:08:20.579 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@84346fd 2025-09-01 15:08:23.812 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2 2025-09-01 15:08:31.920 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 0 2025-09-01 15:08:31.921 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@84346fd 2025-09-01 15:08:31.950 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2 2025-09-01 15:08:32.051 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 1 2025-09-01 15:08:32.053 30801-30801 USBOfflineUpdater com.kotei.overseas.navi I 检测到更新任务触发,开始进行地图更新 2025-09-01 15:08:32.060 30801-30801 USBOfflineUpdater com.kotei.overseas.navi I 总工作量计算: 升级包数量=5, 升级包大小=13.6GB, 备份大小=10.0MB 2025-09-01 15:08:32.067 30801-31064 USBOfflineUpdater com.kotei.overseas.navi I 初始化备份目录为:/data/user/0/com.kotei.overseas.navi/cache/backup 2025-09-01 15:08:32.069 30801-31064 USBOfflineUpdater com.kotei.overseas.navi I 核对实际需要备份的数据大小 2025-09-01 15:08:32.076 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 开始进行更新 progress:0.0 2025-09-01 15:08:32.076 30801-31064 USBOfflineUpdater com.kotei.overseas.navi I 需要备份的数据大小:[10.0MB] 2025-09-01 15:08:32.076 30801-31064 USBOfflineUpdater com.kotei.overseas.navi I storage目录为:/storage/emulated/0/Android/data/com.kotei.overseas.navi/files/overseas/data/mapoffline 2025-09-01 15:08:32.077 30801-31064 USBOfflineUpdater com.kotei.overseas.navi I 开始进行数据备份 2025-09-01 15:08:32.078 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 开始备份数据... progress:0.0 2025-09-01 15:08:32.178 30801-31064 USBOfflineUpdater com.kotei.overseas.navi I 开始处理升级包【拷贝、解密验签、解压】 2025-09-01 15:08:32.179 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 备份中: 10.0MB/10.0MB progress:10.0 2025-09-01 15:08:32.179 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 数据备份完 progress:10.0 2025-09-01 15:08:32.222 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 10.0MB/3.0GB progress:10.02 2025-09-01 15:08:33.228 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 330.0MB/3.0GB progress:10.62 2025-09-01 15:08:34.236 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 630.0MB/3.0GB progress:11.19 2025-09-01 15:08:35.051 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@84346fd 2025-09-01 15:08:35.245 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 930.0MB/3.0GB progress:11.75 2025-09-01 15:08:35.356 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2 2025-09-01 15:08:35.428 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2 2025-09-01 15:08:36.262 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.2GB/3.0GB progress:12.23 2025-09-01 15:08:37.261 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I message: 拷贝 KVM_Navi_EU_148_1.zip: 1.4GB/3.0GB progress:12.77 2025-09-01 15:08:37.619 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 0 2025-09-01 15:08:37.620 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=high hint, reason=touch, vri=VRI[MainActivity]@84346fd 2025-09-01 15:08:37.753 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I ViewPostIme pointer 1 2025-09-01 15:08:37.757 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I resultCode: 8 message:用户取消更新 2025-09-01 15:08:37.757 30801-30832 USBOfflineUpdater com.kotei.overseas.navi I resultCode: 8 message:用户取消更新 2025-09-01 15:08:37.763 30801-30801 BLASTBufferQueue_Java com.kotei.overseas.navi I update, w= 1920 h= 1200 mName = VRI[MainActivity]@84346fd mNativeObject= 0xb400007b462e1e00 sc.mNativeObject= 0xb400007bc39bf840 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-01 15:08:37.763 1360-8154 WindowManager system_server V Relayout Window{5a5234c u0 com.kotei.overseas.navi/com.kotei.overseas.navi.base.MainActivity}: viewVisibility=0 req=1920x1200 ty=1 d0 2025-09-01 15:08:37.763 30801-30801 VRI[MainAc...y]@84346fd 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 0xb400007b45265000} ch=false seqId=0 2025-09-01 15:08:37.764 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I registerCallbackForPendingTransactions 2025-09-01 15:08:37.772 30801-30898 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I mWNT: t=0xb400007ac07f8d80 mBlastBufferQueue=0xb400007b462e1e00 fn= 191 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-01 15:08:40.755 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I call setFrameRateCategory for touch hint category=no preference, reason=boost timeout, vri=VRI[MainActivity]@84346fd 2025-09-01 15:08:42.002 30801-30801 VRI[MainAc...y]@84346fd com.kotei.overseas.navi I onDisplayChanged oldDisplayState=2 newDisplayState=2 目前点击取消后没有执行任何操作
09-02
wjs@wjs-desktop:~/Drone_Slam$ ros2 launch fishbot_grapher test_grapher_6.launch.py [INFO] [launch]: All log files can be found below /home/wjs/.ros/log/2025-07-30-09-53-24-432612-wjs-desktop-3720 [INFO] [launch]: Default logging verbosity is set to INFO [INFO] [sllidar_node-1]: process started with pid [3722] [INFO] [ahrs_driver_node-2]: process started with pid [3724] [INFO] [static_transform_publisher-3]: process started with pid [3726] [INFO] [robot_state_publisher-4]: process started with pid [3728] [INFO] [cartographer_node-5]: process started with pid [3730] [INFO] [cartographer_occupancy_grid_node-6]: process started with pid [3743] [INFO] [test01-7]: process started with pid [3755] [static_transform_publisher-3] [WARN] [1753840404.752631853] []: Old-style arguments are deprecated; see --help for new-style arguments [static_transform_publisher-3] [INFO] [1753840405.071306405] [imu_tf_publisher]: Spinning until stopped - publishing transform [static_transform_publisher-3] translation: ('0.000000', '0.000000', '0.000000') [static_transform_publisher-3] rotation: ('0.000000', '0.000000', '0.000000', '1.000000') [static_transform_publisher-3] from 'base_link' to 'imu_link' [ahrs_driver_node-2] [INFO] [1753840405.076138415] [ahrs_bringup]: Serial Port initialized [ahrs_driver_node-2] [INFO] [1753840405.076570660] [ahrs_bringup]: ahrsBringup::processLoop: start [robot_state_publisher-4] [INFO] [1753840405.077775505] [robot_state_publisher]: got segment base_link [robot_state_publisher-4] [INFO] [1753840405.078150434] [robot_state_publisher]: got segment imu_link [robot_state_publisher-4] [INFO] [1753840405.078241417] [robot_state_publisher]: got segment laser_link [sllidar_node-1] [INFO] [1753840405.116048820] [sllidar_node]: SLLidar running on ROS2 package SLLidar.ROS2 SDK Version:1.0.1, SLLIDAR SDK Version:2.0.0 [sllidar_node-1] [INFO] [1753840405.174082457] [sllidar_node]: SLLidar S/N: 7885EC95C1EA9ED1B2E49CF4FB594571 [sllidar_node-1] [INFO] [1753840405.174277570] [sllidar_node]: Firmware Ver: 1.01 [sllidar_node-1] [INFO] [1753840405.174331737] [sllidar_node]: Hardware Rev: 18 [cartographer_node-5] [INFO] [1753840405.201426611] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/home/wjs/Drone_Slam/install/fishbot_grapher/share/fishbot_grapher/config/test02.lua' for 'test02.lua'. [cartographer_node-5] [INFO] [1753840405.202830217] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/map_builder.lua' for 'map_builder.lua'. [cartographer_node-5] [INFO] [1753840405.203074645] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/map_builder.lua' for 'map_builder.lua'. [cartographer_node-5] [INFO] [1753840405.203383334] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/pose_graph.lua' for 'pose_graph.lua'. [cartographer_node-5] [INFO] [1753840405.203571724] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/pose_graph.lua' for 'pose_graph.lua'. [cartographer_node-5] [INFO] [1753840405.204422344] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder.lua' for 'trajectory_builder.lua'. [cartographer_node-5] [INFO] [1753840405.204688846] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder.lua' for 'trajectory_builder.lua'. [cartographer_node-5] [INFO] [1753840405.205060609] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_2d.lua' for 'trajectory_builder_2d.lua'. [cartographer_node-5] [INFO] [1753840405.205241315] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_2d.lua' for 'trajectory_builder_2d.lua'. [cartographer_node-5] [INFO] [1753840405.205915136] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_3d.lua' for 'trajectory_builder_3d.lua'. [cartographer_node-5] [INFO] [1753840405.206131286] [cartographer logger]: I0730 09:53:25.000000 3730 configuration_file_resolver.cc:41] Found '/opt/ros/humble/share/cartographer/configuration_files/trajectory_builder_3d.lua' for 'trajectory_builder_3d.lua'. [sllidar_node-1] [INFO] [1753840405.225742124] [sllidar_node]: SLLidar health status : 0 [sllidar_node-1] [INFO] [1753840405.225915700] [sllidar_node]: SLLidar health status : OK. [cartographer_node-5] [INFO] [1753840405.299036576] [cartographer logger]: I0730 09:53:25.000000 3730 map_builder_bridge.cpp:136] Added trajectory with ID '0'. [sllidar_node-1] [INFO] [1753840405.442538782] [sllidar_node]: current scan mode: DenseBoost, sample rate: 32 Khz, max_distance: 18.0 m, scan frequency:10.0 Hz, [cartographer_node-5] [INFO] [1753840407.497690847] [cartographer logger]: I0730 09:53:27.000000 3730 ordered_multi_queue.cc:172] All sensor data for trajectory 0 is available starting at '638894372074913209'. [cartographer_node-5] [INFO] [1753840407.498743172] [cartographer logger]: I0730 09:53:27.000000 3730 local_trajectory_builder_2d.cc:135] Extrapolator is still initializing. [cartographer_node-5] [INFO] [1753840407.587985983] [cartographer logger]: I0730 09:53:27.000000 3730 pose_graph_2d.cc:148] Inserted submap (0, 0). [cartographer_node-5] [INFO] [1753840420.301185648] [cartographer logger]: I0730 09:53:40.000000 3730 collated_trajectory_builder.cc:81] scan rate: 10.00 Hz 1.00e-01 s +/- 3.16e-03 s (pulsed at 99.97% real time) [test01-7] Traceback (most recent call last): [test01-7] File "/home/wjs/Drone_Slam/install/fishbot_grapher/lib/fishbot_grapher/test01", line 33, in <module> [test01-7] sys.exit(load_entry_point('fishbot-grapher==0.0.0', 'console_scripts', 'test01')()) [test01-7] File "/home/wjs/Drone_Slam/install/fishbot_grapher/lib/python3.10/site-packages/fishbot_grapher/test01.py", line 102, in main [test01-7] rclpy.spin(tf_subscriber) [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/__init__.py", line 226, in spin [test01-7] executor.spin_once() [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 751, in spin_once [test01-7] self._spin_once_impl(timeout_sec) [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 748, in _spin_once_impl [test01-7] raise handler.exception() [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/task.py", line 254, in __call__ [test01-7] self._handler.send(None) [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 447, in handler [test01-7] await call_coroutine(entity, arg) [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 361, in _execute_timer [test01-7] await await_or_execute(tmr.callback) [test01-7] File "/opt/ros/humble/local/lib/python3.10/dist-packages/rclpy/executors.py", line 107, in await_or_execute [test01-7] return callback(*args) [test01-7] File "/home/wjs/Drone_Slam/install/fishbot_grapher/lib/python3.10/site-packages/fishbot_grapher/test01.py", line 37, in timer_callback [test01-7] self.decoded_data = data.decode('utf-8') # 使用适当的编码解码数据 [test01-7] UnicodeDecodeError: 'utf-8' codec can't decode byte 0xf5 in position 38: invalid start byte [ERROR] [test01-7]: process has died [pid 3755, exit code 1, cmd '/home/wjs/Drone_Slam/install/fishbot_grapher/lib/fishbot_grapher/test01 --ros-args']. [cartographer_node-5] [INFO] [1753840435.382353868] [cartographer logger]: I0730 09:53:55.000000 3730 collated_trajectory_builder.cc:81] scan rate: 10.01 Hz 9.99e-02 s +/- 5.87e-04 s (pulsed at 100.01% real time) [cartographer_node-5] [INFO] [1753840440.885662344] [cartographer logger]: I0730 09:54:00.000000 3730 pose_graph_2d.cc:148] Inserted submap (0, 1). [cartographer_node-5] [INFO] [1753840440.889801774] [cartographer logger]: I0730 09:54:00.000000 3794 pose_graph_2d.cc:538] Remaining work items in queue: 0 [cartographer_node-5] [INFO] [1753840440.890449891] [cartographer logger]: I0730 09:54:00.000000 3794 constraint_builder_2d.cc:290] 0 computations resulted in 0 additional constraints. [cartographer_node-5] [INFO] [1753840440.890582300] [cartographer logger]: I0730 09:54:00.000000 3794 constraint_builder_2d.cc:292] Score histogram: [cartographer_node-5] Count: 0 [cartographer_node-5] [WARN] [1753840440.894951232] [cartographer logger]: W0730 09:54:00.000000 3794 preprocessor.cc:62] Specified options.num_threads: 7 exceeds maximum available from the threading model Ceres was compiled with: 4. Bounding to maximum number available. [cartographer_node-5] [INFO] [1753840450.400053557] [cartographer logger]: I0730 09:54:10.000000 3730 collated_trajectory_builder.cc:81] scan rate: 9.99 Hz 1.00e-01 s +/- 2.75e-03 s (pulsed at 100.00% real time) [cartographer_node-5] [INFO] [1753840457.594026617] [cartographer logger]: I0730 09:54:17.000000 3730 motion_filter.cc:42] Motion filter reduced the number of nodes to 8.6%. [cartographer_node-5] [INFO] [1753840465.483802323] [cartographer logger]: I0730 09:54:25.000000 3730 collated_trajectory_builder.cc:81] scan rate: 10.02 Hz 9.98e-02 s +/- 1.37e-03 s (pulsed at 99.99% real time) [cartographer_node-5] [INFO] [1753840480.566991084] [cartographer logger]: I0730 09:54:40.000000 3730 collated_trajectory_builder.cc:81] scan rate: 10.01 Hz 9.99e-02 s +/- 1.33e-03 s (pulsed at 100.00% real time) [cartographer_node-5] [INFO] [1753840495.656541170] [cartographer logger]: I0730 09:54:55.000000 3730 collated_trajectory_builder.cc:81] scan rate: 10.01 Hz 9.99e-02 s +/- 8.38e-04 s (pulsed at 99.98% real time) [cartographer_node-5] [INFO] [1753840503.457470350] [cartographer logger]: I0730 09:55:03.000000 3730 pose_graph_2d.cc:148] Inserted submap (0, 2). [cartographer_node-5] [INFO] [1753840503.467729546] [cartographer logger]: I0730 09:55:03.000000 3792 constraint_builder_2d.cc:275] Node (0, 70) with 111 points on submap (0, 0) differs by translation 0.01 rotation 0.003 with score 81.3%. ^C[WARNING] [launch]: user interrupted with ctrl-c (SIGINT) [cartographer_node-5] [INFO] [1753840503.649014539] [rclcpp]: signal_handler(signum=2) [robot_state_publisher-4] [INFO] [1753840503.649054612] [rclcpp]: signal_handler(signum=2) [cartographer_occupancy_grid_node-6] [INFO] [1753840503.649017298] [rclcpp]: signal_handler(signum=2) [static_transform_publisher-3] [INFO] [1753840503.649454010] [rclcpp]: signal_handler(signum=2) [ahrs_driver_node-2] [INFO] [1753840503.658569252] [rclcpp]: signal_handler(signum=2) [cartographer_node-5] [INFO] [1753840503.694378050] [cartographer logger]: I0730 09:55:03.000000 3730 node.cpp:569] Shutdown the subscriber of [scan] [cartographer_node-5] [INFO] [1753840503.694493603] [cartographer logger]: I0730 09:55:03.000000 3730 map_builder_bridge.cpp:152] Finishing trajectory with ID '0'... [cartographer_node-5] [WARN] [1753840503.694725912] [cartographer logger]: W0730 09:55:03.000000 3730 node.cpp:773] Can't run final optimization if there are one or more active trajectories. Trying to finish trajectory with ID 0 now. [cartographer_node-5] [INFO] [1753840503.694807725] [cartographer logger]: I0730 09:55:03.000000 3793 pose_graph_2d.cc:538] Remaining work items in queue: 0 [cartographer_node-5] [INFO] [1753840503.694885834] [cartographer logger]: I0730 09:55:03.000000 3730 node.cpp:551] Trajectory 0 already pending to finish. [cartographer_node-5] [INFO] [1753840503.694978147] [cartographer logger]: I0730 09:55:03.000000 3793 constraint_builder_2d.cc:290] 1 computations resulted in 1 additional constraints. [cartographer_node-5] [INFO] [1753840503.695035627] [cartographer logger]: I0730 09:55:03.000000 3730 map_builder_bridge.cpp:161] Running final trajectory optimization... [cartographer_node-5] [INFO] [1753840503.695091885] [cartographer logger]: I0730 09:55:03.000000 3793 constraint_builder_2d.cc:292] Score histogram: [cartographer_node-5] Count: 1 Min: 0.812666 Max: 0.812666 Mean: 0.812666 [cartographer_node-5] [WARN] [1753840503.698766648] [cartographer logger]: W0730 09:55:03.000000 3793 preprocessor.cc:62] Specified options.num_threads: 7 exceeds maximum available from the threading model Ceres was compiled with: 4. Bounding to maximum number available. [cartographer_node-5] [INFO] [1753840503.713479569] [cartographer logger]: I0730 09:55:03.000000 3793 pose_graph_2d.cc:538] Remaining work items in queue: 1 [cartographer_node-5] [INFO] [1753840503.716107246] [cartographer logger]: I0730 09:55:03.000000 3793 constraint_builder_2d.cc:290] 0 computations resulted in 0 additional constraints. [cartographer_node-5] [INFO] [1753840503.716216224] [cartographer logger]: I0730 09:55:03.000000 3793 constraint_builder_2d.cc:292] Score histogram: [cartographer_node-5] Count: 1 Min: 0.812666 Max: 0.812666 Mean: 0.812666 [cartographer_node-5] [WARN] [1753840503.717975663] [cartographer logger]: W0730 09:55:03.000000 3793 preprocessor.cc:62] Specified options.num_threads: 7 exceeds maximum available from the threading model Ceres was compiled with: 4. Bounding to maximum number available. [cartographer_node-5] [INFO] [1753840503.726926965] [cartographer logger]: I0730 09:55:03.000000 3794 constraint_builder_2d.cc:290] 0 computations resulted in 0 additional constraints. [cartographer_node-5] [INFO] [1753840503.727030240] [cartographer logger]: I0730 09:55:03.000000 3794 constraint_builder_2d.cc:292] Score histogram: [cartographer_node-5] Count: 1 Min: 0.812666 Max: 0.812666 Mean: 0.812666 Optimizing: Done. [sllidar_node-1] [INFO] [1753840503.849344663] [sllidar_node]: Stop motor [cartographer_node-5] [INFO] [1753840503.891603435] [cartographer logger]: I0730 09:55:03.000000 3791 constraint_builder_2d.cc:290] 0 computations resulted in 0 additional constraints. [cartographer_node-5] [INFO] [1753840503.891691710] [cartographer logger]: I0730 09:55:03.000000 3791 constraint_builder_2d.cc:292] Score histogram: [cartographer_node-5] Count: 1 Min: 0.812666 Max: 0.812666 Mean: 0.812666 Optimizing: Done. [INFO] [cartographer_occupancy_grid_node-6]: process has finished cleanly [pid 3743] [INFO] [static_transform_publisher-3]: process has finished cleanly [pid 3726] [INFO] [robot_state_publisher-4]: process has finished cleanly [pid 3728] [INFO] [ahrs_driver_node-2]: process has finished cleanly [pid 3724] [INFO] [cartographer_node-5]: process has finished cleanly [pid 3730] [ERROR] [sllidar_node-1]: process[sllidar_node-1] failed to terminate '5' seconds after receiving 'SIGINT', escalating to 'SIGTERM' [INFO] [sllidar_node-1]: sending signal 'SIGTERM' to process[sllidar_node-1] [ERROR] [sllidar_node-1]: process[sllidar_node-1] failed to terminate '10.0' seconds after receiving 'SIGTERM', escalating to 'SIGKILL' [INFO] [sllidar_node-1]: sending signal 'SIGKILL' to process[sllidar_node-1] [INFO] [sllidar_node-1]: process has finished cleanly [pid 3722] wjs@wjs-desktop:~/Drone_Slam$ 我该怎么实现让无人机实现定点飞行?
07-31
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 但是实际上却并没有执行
最新发布
09-03
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值