Просмотр исходного кода

feat(app): 添加强制版本更新功能并优化下载管理

- 在 Login 活动中增加版本检查和强制更新逻辑
- 新增 UpdateInfoRequest 类用于版本更新请求
- 修改 ChinaTowerApiService接口,更新版本检查方法- 优化文件路径配置,支持更多存储位置
- 调整对话框布局,移除不必要的进度条属性
- 重构 NetworkRepository 类,优化网络请求结构- 修改 OsdData 类型,将 modeCode 改为 Integer 类型
- 更新 OsdDataBuilder,设置固定的 modeCode 值
mws 3 месяцев назад
Родитель
Сommit
e8e74e7430

+ 1 - 1
app/build.gradle

@@ -17,7 +17,7 @@ android {
         minSdk 24
         targetSdk 35
         versionCode 1
-        versionName "1.0"
+        versionName "1.0.0"
         manifestPlaceholders["API_KEY"] = project.AIRCRAFT_API_KEY
         manifestPlaceholders["GMAP_API_KEY"] = project.GMAP_API_KEY
         manifestPlaceholders["AMAP_API_KEY"] = project.AMAP_API_KEY

+ 364 - 10
app/src/main/java/com/paul/drone/activity/LoginActivity.java

@@ -1,16 +1,29 @@
 package com.paul.drone.activity;
 
+import android.annotation.SuppressLint;
+import android.app.DownloadManager;
+import android.content.BroadcastReceiver;
+import android.content.Context;
 import android.content.Intent;
+import android.content.IntentFilter;
+import android.database.Cursor;
+import android.net.Uri;
+import android.os.Build;
 import android.os.Bundle;
+import android.os.Handler;
+import android.os.Looper;
 import android.text.TextUtils;
 import android.util.Log;
+import android.view.LayoutInflater;
 import android.view.View;
 import android.widget.Button;
 import android.widget.EditText;
 import android.widget.LinearLayout;
+import android.widget.ProgressBar;
 import android.widget.TextView;
 import android.widget.Toast;
 
+import androidx.appcompat.app.AlertDialog;
 import androidx.appcompat.app.AppCompatActivity;
 
 import com.paul.drone.MainActivity;
@@ -21,6 +34,9 @@ import com.paul.drone.network.SessionManager;
 import com.paul.drone.repository.NetworkRepository;
 import com.paul.drone.util.VersionUpdateUtil;
 
+import java.io.File;
+import java.util.Objects;
+
 import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers;
 import io.reactivex.rxjava3.core.Single;
 import io.reactivex.rxjava3.disposables.CompositeDisposable;
@@ -39,6 +55,14 @@ public class LoginActivity extends AppCompatActivity {
     private Button buttonLogin;
     private CompositeDisposable disposables = new CompositeDisposable();
 
+    // 声明需要的成员变量
+    private long downloadId;
+    private ProgressBar progressBar;
+    private TextView progressText;
+    private TextView statusText;
+    private Button installButton;
+    private AlertDialog dialog;
+
     @Override
     protected void onCreate(Bundle savedInstanceState) {
         super.onCreate(savedInstanceState);
@@ -48,25 +72,35 @@ public class LoginActivity extends AppCompatActivity {
         SessionManager sessionManager = new SessionManager(this);
 
         updateUtil = VersionUpdateUtil.getInstance(this);
-        boolean isUpdate = false;
+
         String currentVersion = updateUtil.getCurrentVersion();
-        Single<UpdateInfoResponse> updateInfoResponseSingle = updateUtil.checkLatestVersion(currentVersion);
-        updateInfoResponseSingle.subscribeOn(Schedulers.io())
+        Single<UpdateInfoResponse> updateInfoResponseSingle = updateUtil.checkLatestVersion();
+
+        disposables.add(updateInfoResponseSingle.subscribeOn(Schedulers.io())
                 .observeOn(AndroidSchedulers.mainThread())
                 .subscribe(
                         updateInfoResponse -> {
-                            if (updateInfoResponse != null && updateInfoResponse.isForceUpdate()) {
-
+                            boolean newVersion = updateUtil.isNewVersion(currentVersion, updateInfoResponse.getData().getVersion());
+                            if (newVersion && updateInfoResponse.getData().isForce()) {
+                                // 处理强制更新逻辑 - 显示更新对话框
+                                showForceUpdateDialog(updateInfoResponse);
+                            } else {
+                                // 没有强制更新,继续检查登录状态
+                                checkLoginStatus(sessionManager);
                             }
                         },
                         throwable -> {
+                            // 处理错误 - 即使检查失败也允许用户登录
+                            Log.e(TAG, "版本检查失败: " + throwable.getMessage(), throwable);
+                            checkLoginStatus(sessionManager);
                         }
-                )
+                ));
+
+        // 注意:不要在这里添加其他逻辑,所有后续操作应该在subscribe回调中处理
+    }
+
+    private void checkLoginStatus(SessionManager sessionManager) {
         // 检查登录状态
-//        boolean isLoggedIn = networkRepository.isLoggedIn();
-//        String token = networkRepository.getUserToken();
-//        Log.i(TAG, "登录状态检查: isLoggedIn=" + isLoggedIn + ", token=" + (token != null ? token : "null"));
-        
         if (!sessionManager.isSessionExpired()) {
             Log.i(TAG, "用户已登录且token有效,直接进入MainActivity");
             Intent intent = new Intent(LoginActivity.this, MainActivity.class);
@@ -83,6 +117,7 @@ public class LoginActivity extends AppCompatActivity {
         setupListeners();
     }
 
+
     private void initViews() {
         editTextPhone = findViewById(R.id.et_phone_number);
         editTextSmsCode = findViewById(R.id.et_sms_code);
@@ -220,12 +255,331 @@ public class LoginActivity extends AppCompatActivity {
         }
     }
 
+    
+
+    private void showForceUpdateDialog(UpdateInfoResponse updateInfo) {
+        AlertDialog.Builder builder = new AlertDialog.Builder(this);
+        LayoutInflater inflater = LayoutInflater.from(this);
+        View dialogView = inflater.inflate(R.layout.dialog_update, null);
+        builder.setView(dialogView);
+
+        AlertDialog dialog = builder.create();
+        dialog.setCancelable(false);
+        dialog.setCanceledOnTouchOutside(false);
+
+        // 获取对话框中的控件
+        TextView currentVersionText = dialogView.findViewById(R.id.currentVersion);
+        TextView newVersionText = dialogView.findViewById(R.id.newVersion);
+        ProgressBar progressBar = dialogView.findViewById(R.id.dialogProgressBar);
+        TextView progressText = dialogView.findViewById(R.id.dialogProgressText);
+        TextView statusText = dialogView.findViewById(R.id.dialogStatusText);
+        Button cancelButton = dialogView.findViewById(R.id.dialogCancelButton);
+        Button installButton = dialogView.findViewById(R.id.dialogInstallButton);
+
+        // 设置版本信息
+        String currentVersion = updateUtil.getCurrentVersion();
+        currentVersionText.setText(currentVersion);
+        newVersionText.setText(updateInfo.getVersion());
+
+        // 设置状态文本
+        statusText.setText("检测到重要更新,请立即升级");
+
+        // 隐藏进度条和进度文本(初始状态)
+        progressBar.setVisibility(View.GONE);
+        progressText.setVisibility(View.GONE);
+
+        // 隐藏取消按钮,显示立即更新按钮
+        cancelButton.setVisibility(View.GONE);
+        installButton.setVisibility(View.VISIBLE);
+        installButton.setText("立即更新");
+
+        // 设置立即更新按钮点击事件
+        installButton.setOnClickListener(v -> {
+            // 开始下载APK
+            startDownloadApk(updateInfo, dialog, progressBar, progressText, statusText, installButton);
+        });
+
+        dialog.show();
+    }
+
+
+    // 修改 startDownloadApk 方法中的安装按钮点击事件
+    private final Handler progressHandler = new Handler(Looper.getMainLooper());
+    private Runnable progressRunnable;
+    private boolean isDownloading = false;
+
+    // 修改 startDownloadApk 方法
+    @SuppressLint("UnspecifiedRegisterReceiverFlag")
+    private void startDownloadApk(UpdateInfoResponse updateInfo, AlertDialog dialog,
+                                  ProgressBar progressBar, TextView progressText,
+                                  TextView statusText, Button installButton) {
+        // 保存引用到成员变量
+        this.dialog = dialog;
+        this.progressBar = progressBar;
+        this.progressText = progressText;
+        this.statusText = statusText;
+        this.installButton = installButton;
+
+        // 显示进度条
+        progressBar.setVisibility(View.VISIBLE);
+        progressText.setVisibility(View.VISIBLE);
+        statusText.setText("正在准备下载...");
+        installButton.setEnabled(false);
+        installButton.setText("下载中...");
+
+        try {
+            // 使用VersionUpdateUtil下载APK
+            downloadId = updateUtil.downloadApk(updateInfo.getUpgradeurl(), updateInfo.getVersion());
+
+            statusText.setText("开始下载,请勿关闭应用...");
+            progressText.setText("下载已启动...");
+            // 先检查是否已经有相同下载任务在进行或已完成
+            if (isDownloadAlreadyExists(updateInfo.getUpgradeurl())) {
+                Log.w(TAG, "检测到相同URL的下载任务已存在");
+            }
+        
+            // 注册下载完成广播接收器
+            IntentFilter filter = new IntentFilter(DownloadManager.ACTION_DOWNLOAD_COMPLETE);
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                registerReceiver(downloadReceiver, filter, Context.RECEIVER_EXPORTED);
+            } else {
+                registerReceiver(downloadReceiver, filter);
+                Log.d(TAG, "已注册广播接收器");
+            }
+
+            // 开始定期查询下载进度
+            startProgressTracking();
+
+        } catch (Exception e) {
+            statusText.setText("下载失败: " + e.getMessage());
+            installButton.setEnabled(true);
+            installButton.setText("重试");
+            installButton.setOnClickListener(v -> {
+                startDownloadApk(updateInfo, dialog, progressBar, progressText, statusText, installButton);
+            });
+        }
+    }
+    // 添加检查下载任务是否已存在的方法
+    private boolean isDownloadAlreadyExists(String downloadUrl) {
+        DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+        DownloadManager.Query query = new DownloadManager.Query();
+
+        Cursor cursor = downloadManager.query(query);
+        if (cursor != null) {
+            int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_URI);
+            while (cursor.moveToNext()) {
+                String uri = cursor.getString(uriIndex);
+                if (downloadUrl.equals(uri)) {
+                    cursor.close();
+                    return true;
+                }
+            }
+            cursor.close();
+        }
+        return false;
+    }
+
+    // 添加进度跟踪方法
+    private void startProgressTracking() {
+        isDownloading = true;
+        progressRunnable = new Runnable() {
+            @Override
+            public void run() {
+                if (isDownloading) {
+                    updateProgress();
+                    progressHandler.postDelayed(this, 1000); // 每秒更新一次
+                }
+            }
+        };
+        progressHandler.post(progressRunnable);
+    }
+
+    // 更新进度显示
+    private void updateProgress() {
+        DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+        DownloadManager.Query query = new DownloadManager.Query();
+        query.setFilterById(downloadId);
+
+        Cursor cursor = downloadManager.query(query);
+        if (cursor != null && cursor.moveToFirst()) {
+            int statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+            int status = cursor.getInt(statusIndex);
+
+            switch (status) {
+                case DownloadManager.STATUS_RUNNING:
+                    int bytesDownloadedIndex = cursor.getColumnIndex(DownloadManager.COLUMN_BYTES_DOWNLOADED_SO_FAR);
+                    int bytesTotalIndex = cursor.getColumnIndex(DownloadManager.COLUMN_TOTAL_SIZE_BYTES);
+
+                    long bytesDownloaded = cursor.getLong(bytesDownloadedIndex);
+                    long bytesTotal = cursor.getLong(bytesTotalIndex);
+
+                    if (bytesTotal > 0) {
+                        int progress = (int) ((bytesDownloaded * 100) / bytesTotal);
+                        progressBar.setProgress(progress);
+                        progressText.setText(String.format("下载中... %d%%", progress));
+                        statusText.setText("正在下载应用更新...");
+                    }
+                    break;
+
+                case DownloadManager.STATUS_PENDING:
+                    statusText.setText("等待下载开始...");
+                    progressText.setText("准备中...");
+                    break;
+
+                case DownloadManager.STATUS_PAUSED:
+                    statusText.setText("下载已暂停");
+                    progressText.setText("下载暂停");
+                    break;
+
+                case DownloadManager.STATUS_FAILED:
+                    statusText.setText("下载失败");
+                    progressText.setText("下载失败,请重试");
+                    stopProgressTracking();
+                    // 显示重试按钮
+                    installButton.setEnabled(true);
+                    installButton.setText("重试");
+                    installButton.setOnClickListener(v -> {
+                        // 重新开始下载逻辑
+                    });
+                    break;
+            }
+        }
+
+        if (cursor != null) {
+            cursor.close();
+        }
+    }
+
+    // 停止进度跟踪
+    private void stopProgressTracking() {
+        isDownloading = false;
+        if (progressRunnable != null) {
+            progressHandler.removeCallbacks(progressRunnable);
+        }
+    }
+
+
+    // 修改 downloadReceiver 添加额外验证
+    private final BroadcastReceiver downloadReceiver = new BroadcastReceiver() {
+        @Override
+        public void onReceive(Context context, Intent intent) {
+            Log.d(TAG, "收到广播: " + intent.getAction());
+            if (DownloadManager.ACTION_DOWNLOAD_COMPLETE.equals(intent.getAction())) {
+                long receivedDownloadId = intent.getLongExtra(DownloadManager.EXTRA_DOWNLOAD_ID, -1);
+                Log.d(TAG, "下载ID: " + receivedDownloadId + ", 期望ID: " + downloadId);
+
+                // 额外验证:确保是当前会话的下载任务
+                if (receivedDownloadId == downloadId && downloadId != 0) {
+                    Log.d(TAG, "下载完成,准备更新UI");
+                    // 停止进度跟踪
+                    stopProgressTracking();
+
+                    // 下载完成,更新UI
+                    runOnUiThread(() -> {
+                        if (!isFinishing() && progressText != null && statusText != null && installButton != null) {
+                            progressText.setText("");
+                            statusText.setText("下载已完成,准备安装");
+                            progressBar.setProgress(100);
+                            installButton.setEnabled(true);
+                            installButton.setText("立即安装");
+                            installButton.setVisibility(View.VISIBLE);
+
+                            // 设置安装按钮点击事件
+                            installButton.setOnClickListener(installView -> {
+                                installDownloadedApk(downloadId, dialog);
+                            });
+                            Log.d(TAG, "UI更新完成");
+                        } else {
+                            Log.w(TAG, "Activity已结束或UI组件为空");
+                        }
+                    });
+                } else {
+                    Log.d(TAG, "收到其他下载任务的完成广播或无效下载ID");
+                }
+            }
+        }
+    };
+
+    // 在 onDestroy 中停止进度跟踪
     @Override
     protected void onDestroy() {
         super.onDestroy();
+        // 停止进度跟踪
+        stopProgressTracking();
+
         // 清理资源
         if (disposables != null && !disposables.isDisposed()) {
             disposables.dispose();
         }
+
+        // 注销广播接收器
+        try {
+            unregisterReceiver(downloadReceiver);
+            Log.d(TAG, "广播接收器已注销");
+        } catch (IllegalArgumentException e) {
+            // 接收器未注册,忽略
+            Log.w(TAG, "广播接收器未注册或已注销: " + e.getMessage());
+        }
     }
+
+    private void installDownloadedApk(long downloadId, AlertDialog dialog) {
+        try {
+            // 通过DownloadManager查询下载的文件
+            DownloadManager downloadManager = (DownloadManager) getSystemService(DOWNLOAD_SERVICE);
+            DownloadManager.Query query = new DownloadManager.Query();
+            query.setFilterById(downloadId);
+
+            Cursor cursor = downloadManager.query(query);
+            if (cursor != null && cursor.moveToFirst()) {
+                int statusIndex = cursor.getColumnIndex(DownloadManager.COLUMN_STATUS);
+                if (DownloadManager.STATUS_SUCCESSFUL == cursor.getInt(statusIndex)) {
+                    // 获取下载文件的URI
+                    int uriIndex = cursor.getColumnIndex(DownloadManager.COLUMN_LOCAL_URI);
+                    String downloadedFileUriString = cursor.getString(uriIndex);
+
+                    if (downloadedFileUriString != null) {
+                        Uri apkUri = Uri.parse(downloadedFileUriString);
+
+                        // Android 7.0及以上版本使用FileProvider处理file:// URI
+                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && "file".equals(apkUri.getScheme())) {
+                            // 将 file:// URI 转换为 content:// URI
+                            File apkFile = new File(apkUri.getPath());
+                            if (apkFile.exists()) {
+                                Uri contentUri = androidx.core.content.FileProvider.getUriForFile(
+                                        this,
+                                        "com.paul.drone.fileprovider",
+                                        apkFile
+                                );
+                                // 授予临时权限给包管理器
+                                updateUtil.installApk(contentUri);
+                            } else {
+                                Toast.makeText(this, "APK文件不存在", Toast.LENGTH_LONG).show();
+                            }
+                        } else {
+                            // Android 7.0以下版本或已经是content:// URI,直接使用
+                            updateUtil.installApk(apkUri);
+                        }
+
+                        dialog.dismiss();
+                        finish();
+                    } else {
+                        Toast.makeText(this, "无法找到下载的文件", Toast.LENGTH_LONG).show();
+                    }
+                } else {
+                    Toast.makeText(this, "下载未完成", Toast.LENGTH_LONG).show();
+                }
+            } else {
+                Toast.makeText(this, "下载查询失败", Toast.LENGTH_LONG).show();
+            }
+
+            if (cursor != null) {
+                cursor.close();
+            }
+        } catch (Exception e) {
+            Log.e(TAG, "安装APK失败: " + e.getMessage(), e);
+            Toast.makeText(this, "安装失败: " + e.getMessage(), Toast.LENGTH_LONG).show();
+        }
+    }
+
+
 }

+ 34 - 0
app/src/main/java/com/paul/drone/data/UpdateInfoRequest.java

@@ -0,0 +1,34 @@
+package com.paul.drone.data;
+
+import com.google.gson.annotations.SerializedName;
+
+import lombok.AllArgsConstructor;
+import lombok.Data;
+import lombok.Getter;
+import lombok.NoArgsConstructor;
+import lombok.Setter;
+
+/**
+ * 更新信息响应模型
+ * 用于解析后端返回的版本更新信息
+ */
+@Data
+@Setter
+@Getter
+@AllArgsConstructor
+@NoArgsConstructor
+public class UpdateInfoRequest {
+
+    @SerializedName("deviceSn")
+    private String deviceSn;
+
+    @SerializedName("upgradeType")
+    private String upgradeType = "2";
+
+
+    @SerializedName("deviceModel")
+    private String deviceModel;
+
+
+
+}

+ 52 - 20
app/src/main/java/com/paul/drone/data/UpdateInfoResponse.java

@@ -8,38 +8,70 @@ import lombok.Getter;
 import lombok.NoArgsConstructor;
 import lombok.Setter;
 
-/**
- * 更新信息响应模型
- * 用于解析后端返回的版本更新信息
- */
 @Data
 @Setter
 @Getter
 @AllArgsConstructor
 @NoArgsConstructor
 public class UpdateInfoResponse {
-    @SerializedName("version")
-    private String version; // 新版本号
 
-    @SerializedName("downloadUrl")
-    private String downloadUrl; // 安装包下载链接
+    @SerializedName("code")
+    private int code;
 
-    @SerializedName("updateLog")
-    private String updateLog; // 更新日志
+    @SerializedName("msg")
+    private String msg;
 
-    @SerializedName("forceUpdate")
-    private boolean forceUpdate; // 是否强制更新
+    @SerializedName("data")
+    private UpdateData data;
 
-    @SerializedName("apkSize")
-    private long apkSize; // 安装包大小
+    // 内部数据类,匹配实际返回的data字段结构
+    @Data
+    @Setter
+    @Getter
+    @AllArgsConstructor
+    @NoArgsConstructor
+    public static class UpdateData {
+        @SerializedName("upgradeType")
+        private int upgradeType;
 
-    // 手动添加getVersion方法
+        @SerializedName("version")
+        private String version;
+
+        @SerializedName("upgradeurl")
+        private String upgradeurl;
+
+        @SerializedName("md5Sign")
+        private String md5Sign;
+
+        @SerializedName("fileSize")
+        private int fileSize;
+
+        @SerializedName("fileName")
+        private String fileName;
+
+        @SerializedName("isForce")
+        private int isForce;
+
+        // 提供便捷方法获取布尔值
+        public boolean isForce() {
+            return isForce == 1;
+        }
+    }
+
+    // 提供便捷方法直接访问data中的字段
     public String getVersion() {
-        return version;
+        return data != null ? data.getVersion() : null;
+    }
+
+    public String getUpgradeurl() {
+        return data != null ? data.getUpgradeurl() : null;
+    }
+
+    public boolean isForce() {
+        return data != null && data.isForce();
     }
 
-    // 手动添加setVersion方法
-    public void setVersion(String version) {
-        this.version = version;
+    public int getUpgradeType() {
+        return data != null ? data.getUpgradeType() : 0;
     }
-}
+}

+ 2 - 2
app/src/main/java/com/paul/drone/data/mqtt/OsdData.java

@@ -68,7 +68,7 @@ public class OsdData {
     private MaintainStatus maintainStatus; // 维护状态
     
     @SerializedName("mode_code")
-    private String modeCode; // 飞行模式代码
+    private Integer modeCode; // 飞行模式代码
     
     @SerializedName("obstacle_avoidance")
     private Object obstacleAvoidance; // 障碍物规避信息(如有,结构可扩展)
@@ -168,7 +168,7 @@ public class OsdData {
         this.maintainStatus = maintainStatus;
     }
     
-    public void setModeCode(String modeCode) {
+    public void setModeCode(Integer modeCode) {
         this.modeCode = modeCode;
     }
     

+ 1 - 1
app/src/main/java/com/paul/drone/data/mqtt/OsdDataBuilder.java

@@ -99,7 +99,7 @@ public class OsdDataBuilder {
         osdData.setHeight(getRelativeAltitude());
         
         // 飞行状态
-        osdData.setModeCode(getModeCode());
+        osdData.setModeCode(1);
         osdData.setHomeDistance(getDistanceToHome());
         osdData.setHorizontalSpeed(getHorizontalSpeed());
         osdData.setVerticalSpeed(getVerticalSpeed());

+ 3 - 2
app/src/main/java/com/paul/drone/network/ChinaTowerApiService.java

@@ -5,6 +5,7 @@ import com.paul.drone.data.RefreshTokenResponse;
 import com.paul.drone.data.RegisterRequest;
 import com.paul.drone.data.RegisterDeviceResponse;
 import com.paul.drone.data.SmsCodeResponse;
+import com.paul.drone.data.UpdateInfoRequest;
 import com.paul.drone.data.UpdateInfoResponse;
 
 import retrofit2.Call;
@@ -78,8 +79,8 @@ public interface ChinaTowerApiService {
     /**
      * 检查版本更新
      */
-    @GET("/api/app/update")
+    @POST("/hntt-system/system/upgrade/app")
     Call<UpdateInfoResponse> checkUpdate(
-            @Query("currentVersion") String currentVersion
+            @Body UpdateInfoRequest request
     );
 }

+ 186 - 180
app/src/main/java/com/paul/drone/repository/NetworkRepository.java

@@ -3,6 +3,9 @@ package com.paul.drone.repository;
 import android.content.Context;
 import android.util.Log;
 
+import androidx.annotation.OptIn;
+import androidx.media3.common.util.UnstableApi;
+
 import com.paul.drone.data.OAuth2TokenResponse;
 import com.paul.drone.data.RefreshTokenResponse;
 import com.paul.drone.data.RegisterRequest;
@@ -12,6 +15,7 @@ import com.paul.drone.network.ChinaTowerApiService;
 import com.paul.drone.network.SessionManager;
 import com.paul.drone.util.AuthUtil;
 import com.paul.drone.util.JsonUtil;
+import okio.Buffer;
 
 import java.io.IOException;
 import java.nio.charset.StandardCharsets;
@@ -43,6 +47,7 @@ public class NetworkRepository {
     private static final String BASE_URL = "http://111.22.178.69:53064";
     private static final int NETWORK_TIMEOUT = 30000; // 30秒超时
 
+
     private static volatile NetworkRepository instance;
     private final ChinaTowerApiService chinaTowerApiService;
     private final SessionManager sessionManager;
@@ -73,7 +78,7 @@ public class NetworkRepository {
     public static NetworkRepository getInstance() {
         if (instance == null) {
             throw new IllegalStateException(
-                "NetworkRepository 未初始化!请先在 Application.onCreate() 中调用 setInstance(context)"
+                    "NetworkRepository 未初始化!请先在 Application.onCreate() 中调用 setInstance(context)"
             );
         }
         return instance;
@@ -89,6 +94,7 @@ public class NetworkRepository {
     /**
      * 创建 API 服务
      */
+    @OptIn(markerClass = UnstableApi.class)
     private ChinaTowerApiService createApiService() {
         // 创建详细的日志拦截器
         HttpLoggingInterceptor loggingInterceptor = new HttpLoggingInterceptor(message -> {
@@ -109,25 +115,25 @@ public class NetworkRepository {
             request.headers().forEach(header -> {
                 Log.i(TAG, "  " + header.getFirst() + ": " + header.getSecond());
             });
-            
+
             // 打印请求体
+// 打印请求体
             if (request.body() != null) {
                 try {
                     RequestBody requestBody = request.body();
-                    if (requestBody != null) {
-                        okio.Buffer buffer = new okio.Buffer();
-                        requestBody.writeTo(buffer);
-                        String requestBodyString = buffer.readString(StandardCharsets.UTF_8);
-//                        Log.i(TAG, "请求体: " + requestBodyString);
-                    }
+                    Buffer buffer = new Buffer();
+                    requestBody.writeTo(buffer);
+                    String requestBodyString = buffer.readByteString().utf8();
+                    Log.i(TAG, "请求体: " + requestBodyString);
                 } catch (Exception e) {
                     Log.e(TAG, "打印请求体失败: " + e.getMessage());
                 }
             }
-            
+
+
             // 执行请求
             Response response = chain.proceed(request);
-            
+
             // 打印响应信息
 //            Log.i(TAG, "=== 网络响应 ===");
 //            Log.i(TAG, "响应码: " + response.code());
@@ -138,14 +144,14 @@ public class NetworkRepository {
             response.headers().forEach(header -> {
                 Log.i(TAG, "  " + header.getFirst() + ": " + header.getSecond());
             });
-            
+
             // 打印响应体
             ResponseBody responseBody = response.body();
             if (responseBody != null) {
                 try {
                     String responseBodyString = responseBody.string();
                     Log.i(TAG, "响应体: " + responseBodyString);
-                    
+
                     // 重新创建 ResponseBody,因为读取后需要重新创建
                     MediaType contentType = responseBody.contentType();
                     ResponseBody newResponseBody = ResponseBody.create(responseBodyString, contentType);
@@ -154,7 +160,7 @@ public class NetworkRepository {
                     Log.e(TAG, "打印响应体失败: " + e.getMessage());
                 }
             }
-            
+
             Log.i(TAG, "=== 网络请求结束 ===");
             return response;
         };
@@ -166,201 +172,201 @@ public class NetworkRepository {
                 .addInterceptor(customInterceptor)
                 .addInterceptor(loggingInterceptor)
                 .build();
-        
+
         Retrofit retrofit = new Retrofit.Builder()
                 .baseUrl(BASE_URL)
                 .client(okHttpClient)
                 .addConverterFactory(GsonConverterFactory.create())
                 .build();
-        
+
         return retrofit.create(ChinaTowerApiService.class);
     }
-    
+
     /**
      * 验证码登录
      */
     public Single<OAuth2TokenResponse> loginWithSmsCode(String phone, String code) {
         Log.i(TAG, "开始验证码登录: " + phone);
-        
+
         return Single.fromCallable(() -> {
-            try {
-                retrofit2.Response<OAuth2TokenResponse> response = chinaTowerApiService.getOAuth2Token(
-                        AuthUtil.generateDefaultBasicAuth(),
-                        phone,
-                        "mobile",
-                        "server",
-                        code
-                ).execute();
-                
-                if (response.isSuccessful() && response.body() != null && response.code() == 200) {
-                    OAuth2TokenResponse tokenResponse = response.body();
-                    Log.i(TAG,"loginWithSmsCode: "+ JsonUtil.toJson(tokenResponse));
-                    // 保存token
-                    sessionManager.saveAuthTokenWithExpiry(tokenResponse.getAccessToken(), tokenResponse.getExp());
-                    // 保存用户数据
-                    sessionManager.saveUserData(tokenResponse);
-                    // 保存刷新token
-                    sessionManager.saveRefreshToken(tokenResponse.getRefreshToken());
-                    
-                    Log.i(TAG, "验证码登录成功");
-                    return tokenResponse;
-                } else {
-                    Log.e(TAG, "验证码登录失败: " + response.message());
-                    throw new IOException("登录失败: " + response.message());
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "验证码登录异常: " + e.getMessage(), e);
-                throw new IOException("网络请求失败: " + e.getMessage());
-            }
-        }).subscribeOn(Schedulers.io())
-          .observeOn(AndroidSchedulers.mainThread());
+                    try {
+                        retrofit2.Response<OAuth2TokenResponse> response = chinaTowerApiService.getOAuth2Token(
+                                AuthUtil.generateDefaultBasicAuth(),
+                                phone,
+                                "mobile",
+                                "server",
+                                code
+                        ).execute();
+
+                        if (response.isSuccessful() && response.body() != null && response.code() == 200) {
+                            OAuth2TokenResponse tokenResponse = response.body();
+                            Log.i(TAG,"loginWithSmsCode: "+ JsonUtil.toJson(tokenResponse));
+                            // 保存token
+                            sessionManager.saveAuthTokenWithExpiry(tokenResponse.getAccessToken(), tokenResponse.getExp());
+                            // 保存用户数据
+                            sessionManager.saveUserData(tokenResponse);
+                            // 保存刷新token
+                            sessionManager.saveRefreshToken(tokenResponse.getRefreshToken());
+
+                            Log.i(TAG, "验证码登录成功");
+                            return tokenResponse;
+                        } else {
+                            Log.e(TAG, "验证码登录失败: " + response.message());
+                            throw new IOException("登录失败: " + response.message());
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "验证码登录异常: " + e.getMessage(), e);
+                        throw new IOException("网络请求失败: " + e.getMessage());
+                    }
+                }).subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread());
     }
-    
+
     /**
      * 密码登录
      */
     public Single<OAuth2TokenResponse> loginWithPassword(String username, String password) {
         Log.i(TAG, "开始密码登录: " + username);
-        
+
         return Single.fromCallable(() -> {
-            try {
-                retrofit2.Response<OAuth2TokenResponse> response = chinaTowerApiService.getOAuth2TokenByPassword(
-                        "Basic c2VydmVyOjEyMzQ1Ng==", // 默认Basic Auth
-                        username,
-                        password,
-                        "password",
-                        "server"
-                ).execute();
-                
-                if (response.isSuccessful() && response.body() != null) {
-                    OAuth2TokenResponse tokenResponse = response.body();
-                    // 保存token
-                    sessionManager.saveAuthTokenWithExpiry(tokenResponse.getAccessToken(), tokenResponse.getExp());
-                    // 保存用户数据
-                    sessionManager.saveUserData(tokenResponse);
-                    // 保存刷新token
-                    sessionManager.saveRefreshToken(tokenResponse.getRefreshToken());
-                    
-                    Log.i(TAG, "密码登录成功");
-                    return tokenResponse;
-                } else {
-                    Log.e(TAG, "密码登录失败: " + response.message());
-                    throw new IOException("登录失败: " + response.message());
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "密码登录异常: " + e.getMessage(), e);
-                throw new IOException("网络请求失败: " + e.getMessage());
-            }
-        }).subscribeOn(Schedulers.io())
-          .observeOn(AndroidSchedulers.mainThread());
+                    try {
+                        retrofit2.Response<OAuth2TokenResponse> response = chinaTowerApiService.getOAuth2TokenByPassword(
+                                "Basic c2VydmVyOjEyMzQ1Ng==", // 默认Basic Auth
+                                username,
+                                password,
+                                "password",
+                                "server"
+                        ).execute();
+
+                        if (response.isSuccessful() && response.body() != null) {
+                            OAuth2TokenResponse tokenResponse = response.body();
+                            // 保存token
+                            sessionManager.saveAuthTokenWithExpiry(tokenResponse.getAccessToken(), tokenResponse.getExp());
+                            // 保存用户数据
+                            sessionManager.saveUserData(tokenResponse);
+                            // 保存刷新token
+                            sessionManager.saveRefreshToken(tokenResponse.getRefreshToken());
+
+                            Log.i(TAG, "密码登录成功");
+                            return tokenResponse;
+                        } else {
+                            Log.e(TAG, "密码登录失败: " + response.message());
+                            throw new IOException("登录失败: " + response.message());
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "密码登录异常: " + e.getMessage(), e);
+                        throw new IOException("网络请求失败: " + e.getMessage());
+                    }
+                }).subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread());
     }
-    
+
     /**
      * 刷新token
      */
     public Single<RefreshTokenResponse> refreshToken() {
         Log.i(TAG, "开始刷新token");
-        
+
         return Single.fromCallable(() -> {
-            String refreshToken = sessionManager.fetchRefreshToken();
-            if (refreshToken == null) {
-                Log.e(TAG, "没有可用的刷新token");
-                throw new IOException("没有可用的刷新token");
-            }
-            
-            try {
-                retrofit2.Response<RefreshTokenResponse> response = chinaTowerApiService.refreshToken(
-                        "Basic c2VydmVyOjEyMzQ1Ng==",
-                        "refresh_token",
-                        refreshToken,
-                        "server"
-                ).execute();
-                
-                if (response.isSuccessful() && response.body() != null) {
-                    RefreshTokenResponse refreshResponse = response.body();
-                    // 更新token
-                    sessionManager.updateTokenWithExpiresIn(
-                            refreshResponse.getAccessToken(),
-                            refreshResponse.getExpiresIn()
-                    );
-                    
-                    Log.i(TAG, "token刷新成功");
-                    return refreshResponse;
-                } else {
-                    Log.e(TAG, "token刷新失败: " + response.message());
-                    throw new IOException("token刷新失败: " + response.message());
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "token刷新异常: " + e.getMessage(), e);
-                throw new IOException("网络请求失败: " + e.getMessage());
-            }
-        }).subscribeOn(Schedulers.io())
-          .observeOn(AndroidSchedulers.mainThread());
+                    String refreshToken = sessionManager.fetchRefreshToken();
+                    if (refreshToken == null) {
+                        Log.e(TAG, "没有可用的刷新token");
+                        throw new IOException("没有可用的刷新token");
+                    }
+
+                    try {
+                        retrofit2.Response<RefreshTokenResponse> response = chinaTowerApiService.refreshToken(
+                                "Basic c2VydmVyOjEyMzQ1Ng==",
+                                "refresh_token",
+                                refreshToken,
+                                "server"
+                        ).execute();
+
+                        if (response.isSuccessful() && response.body() != null) {
+                            RefreshTokenResponse refreshResponse = response.body();
+                            // 更新token
+                            sessionManager.updateTokenWithExpiresIn(
+                                    refreshResponse.getAccessToken(),
+                                    refreshResponse.getExpiresIn()
+                            );
+
+                            Log.i(TAG, "token刷新成功");
+                            return refreshResponse;
+                        } else {
+                            Log.e(TAG, "token刷新失败: " + response.message());
+                            throw new IOException("token刷新失败: " + response.message());
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "token刷新异常: " + e.getMessage(), e);
+                        throw new IOException("网络请求失败: " + e.getMessage());
+                    }
+                }).subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread());
     }
-    
+
     /**
      * 发送验证码
      */
     public Single<SmsCodeResponse> sendSmsCode(String phone) {
         Log.i(TAG, "发送验证码: " + phone);
-        
+
         return Single.fromCallable(() -> {
-            try {
-                retrofit2.Response<SmsCodeResponse> response = chinaTowerApiService.sendSmsCode(phone).execute();
-                
-                if (response.isSuccessful() && response.body() != null && response.code() == 200) {
-                    Log.i(TAG, "验证码发送成功");
-                    return response.body();
-                } else {
-                    Log.e(TAG, "验证码发送失败: " + response.message());
-                    throw new IOException("验证码发送失败: " + response.message());
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "验证码发送异常: " + e.getMessage(), e);
-                throw new IOException("网络请求失败: " + e.getMessage());
-            }
-        }).subscribeOn(Schedulers.io())
-          .observeOn(AndroidSchedulers.mainThread());
+                    try {
+                        retrofit2.Response<SmsCodeResponse> response = chinaTowerApiService.sendSmsCode(phone).execute();
+
+                        if (response.isSuccessful() && response.body() != null && response.code() == 200) {
+                            Log.i(TAG, "验证码发送成功");
+                            return response.body();
+                        } else {
+                            Log.e(TAG, "验证码发送失败: " + response.message());
+                            throw new IOException("验证码发送失败: " + response.message());
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "验证码发送异常: " + e.getMessage(), e);
+                        throw new IOException("网络请求失败: " + e.getMessage());
+                    }
+                }).subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread());
     }
-    
+
     /**
      * 设备注册
      */
     public Single<RegisterDeviceResponse> registerDevice(String deviceSn, String model, String longitude, String latitude) {
         Log.i(TAG, "注册设备: " + deviceSn);
-        
+
         return Single.fromCallable(() -> {
-            // 检查token是否有效
-            String token = getValidToken();
-            if (token == null) {
-                Log.e(TAG, "没有有效的token");
-                throw new IOException("没有有效的token,请先登录");
-            }
+                    // 检查token是否有效
+                    String token = getValidToken();
+                    if (token == null) {
+                        Log.e(TAG, "没有有效的token");
+                        throw new IOException("没有有效的token,请先登录");
+                    }
 
-            
-            try {
-                retrofit2.Response<RegisterDeviceResponse> response = chinaTowerApiService.registerDevice(
-                        "Bearer "+sessionManager.fetchAuthToken(),
-                        new RegisterRequest(deviceSn, model, longitude, latitude)
-                ).execute();
-                
-                if (response.isSuccessful() && response.body() != null && response.code() == 200) {
-                    // 保存MQTT信息
-                    sessionManager.saveMqttInfo(response.body().getData());
-                    Log.i(TAG, "设备注册成功");
-                    return response.body();
-                } else {
-                    Log.e(TAG, "设备注册失败: " + response.message());
-                    throw new IOException("设备注册失败: " + response.message());
-                }
-            } catch (Exception e) {
-                Log.e(TAG, "设备注册异常: " + e.getMessage(), e);
-                throw new IOException("网络请求失败: " + e.getMessage());
-            }
-        }).subscribeOn(Schedulers.io())
-          .observeOn(AndroidSchedulers.mainThread());
+
+                    try {
+                        retrofit2.Response<RegisterDeviceResponse> response = chinaTowerApiService.registerDevice(
+                                "Bearer "+sessionManager.fetchAuthToken(),
+                                new RegisterRequest(deviceSn, model, longitude, latitude)
+                        ).execute();
+
+                        if (response.isSuccessful() && response.body() != null && response.code() == 200) {
+                            // 保存MQTT信息
+                            sessionManager.saveMqttInfo(response.body().getData());
+                            Log.i(TAG, "设备注册成功");
+                            return response.body();
+                        } else {
+                            Log.e(TAG, "设备注册失败: " + response.message());
+                            throw new IOException("设备注册失败: " + response.message());
+                        }
+                    } catch (Exception e) {
+                        Log.e(TAG, "设备注册异常: " + e.getMessage(), e);
+                        throw new IOException("网络请求失败: " + e.getMessage());
+                    }
+                }).subscribeOn(Schedulers.io())
+                .observeOn(AndroidSchedulers.mainThread());
     }
-    
+
     /**
      * 检查用户是否已登录且token有效
      * 会自动尝试刷新即将过期的token
@@ -368,7 +374,7 @@ public class NetworkRepository {
     public boolean isLoggedIn() {
         boolean isSessionValid = sessionManager.isLoggedIn();
         Log.i(TAG, "检查登录状态: " + isSessionValid);
-        
+
         // 如果session有效但token即将过期,尝试刷新
         if (isSessionValid && sessionManager.getSessionRemainingTime() < 5 * 60 * 1000) {
             Log.i(TAG, "Token即将过期,尝试刷新");
@@ -380,17 +386,17 @@ public class NetworkRepository {
                 isSessionValid = false;
             }
         }
-        
+
         return isSessionValid;
     }
-    
+
     /**
      * 获取用户Token
      */
     public String getUserToken() {
         return sessionManager.fetchAuthToken();
     }
-    
+
     /**
      * 获取用户ID
      */
@@ -423,36 +429,36 @@ public class NetworkRepository {
         }
         return token;
     }
-    
+
     /**
      * 获取用户名
      */
     public String getUsername() {
         return sessionManager.fetchUsername();
     }
-    
+
     /**
      * 获取昵称
      */
     public String getNickName() {
         return sessionManager.fetchNickName();
     }
-    
+
     /**
      * 获取MQTT信息
      */
     public String getMqttUsername() {
         return sessionManager.fetchMqttUsername();
     }
-    
+
     public String getMqttPassword() {
         return sessionManager.fetchMqttPassword();
     }
-    
+
     public String getMqttAddr() {
         return sessionManager.fetchMqttAddr();
     }
-    
+
     /**
      * 清除登录信息
      */
@@ -460,7 +466,7 @@ public class NetworkRepository {
         sessionManager.clearLoginInfo();
         Log.i(TAG, "登录信息已清除");
     }
-    
+
     /**
      * 清除MQTT信息
      */
@@ -468,7 +474,7 @@ public class NetworkRepository {
         sessionManager.clearMqttInfo();
         Log.i(TAG, "MQTT信息已清除");
     }
-    
+
     /**
      * 延长会话时间
      */
@@ -476,14 +482,14 @@ public class NetworkRepository {
         sessionManager.extendSession();
         Log.i(TAG, "会话时间已延长");
     }
-    
+
     /**
      * 获取会话剩余时间
      */
     public long getSessionRemainingTime() {
         return sessionManager.getSessionRemainingTime();
     }
-    
+
     /**
      * 统一错误处理
      */
@@ -509,7 +515,7 @@ public class NetworkRepository {
             return "未知错误: " + throwable.getMessage();
         }
     }
-    
+
     /**
      * 释放资源
      */

+ 30 - 50
app/src/main/java/com/paul/drone/util/VersionUpdateUtil.java

@@ -11,6 +11,8 @@ import android.os.Environment;
 import android.util.Log;
 import android.widget.Toast;
 
+import com.paul.drone.data.RegisterRequest;
+import com.paul.drone.data.UpdateInfoRequest;
 import com.paul.drone.repository.NetworkRepository;
 import com.paul.drone.data.UpdateInfoResponse;
 
@@ -28,7 +30,7 @@ import io.reactivex.rxjava3.schedulers.Schedulers;
  */
 public class VersionUpdateUtil {
     private static final String TAG = "VersionUpdateUtil";
-    private static final String UPDATE_API_URL = "http://111.22.178.69:53064/api/app/update";
+
     private static volatile VersionUpdateUtil instance;
     private final Context context;
     private final NetworkRepository networkRepository;
@@ -66,62 +68,19 @@ public class VersionUpdateUtil {
         }
     }
 
-    /**
-     * 检查版本更新
-     */
-    public void checkUpdate(UpdateCheckListener listener) {
-        String currentVersion = getCurrentVersion();
-        Log.i(TAG, "当前版本: " + currentVersion);
-
-        // 调用后端接口检查更新
-        checkLatestVersion(currentVersion)
-                .subscribeOn(Schedulers.io())
-                .observeOn(AndroidSchedulers.mainThread())
-                .subscribe(
-                        updateInfo -> {
-                            if (updateInfo != null && isNewVersion(currentVersion, updateInfo.getVersion())) {
-                                Log.i(TAG, "发现新版本: " + updateInfo.getVersion());
-                                if (listener != null) {
-                                    listener.onUpdateAvailable(updateInfo);
-                                }
-                            } else {
-                                Log.i(TAG, "当前已是最新版本");
-                                if (listener != null) {
-                                    listener.onNoUpdateAvailable();
-                                }
-                            }
-                        },
-                        error -> {
-                            Log.e(TAG, "检查更新失败: " + error.getMessage());
-                            if (listener != null) {
-                                listener.onUpdateFailed(error.getMessage());
-                            }
-                        }
-                );
-    }
-
     /**
      * 调用后端接口检查最新版本
      */
-    public Single<UpdateInfoResponse> checkLatestVersion(String currentVersion) {
+    public Single<UpdateInfoResponse> checkLatestVersion() {
         return Single.fromCallable(() -> {
             try {
-                if (networkRepository == null || !networkRepository.isInitialized()) {
+                if (networkRepository == null || !NetworkRepository.isInitialized()) {
                     throw new IllegalStateException("NetworkRepository not initialized");
                 }
 
-                // 获取有效的token
-                String token = networkRepository.getValidToken();
-                if (token == null) {
-                    throw new IllegalStateException("Failed to get valid token");
-                }
-
-                // 构建请求头
-                String authorization = "Bearer " + token;
-
                 // 调用API接口
                 retrofit2.Response<UpdateInfoResponse> response = networkRepository.getChinaTowerApiService()
-                        .checkUpdate(currentVersion)
+                        .checkUpdate( new UpdateInfoRequest())
                         .execute();
 
                 if (response.isSuccessful() && response.body() != null) {
@@ -144,7 +103,7 @@ public class VersionUpdateUtil {
      * @param newVersion 新版本
      * @return 如果newVersion大于currentVersion则返回true
      */
-    private boolean isNewVersion(String currentVersion, String newVersion) {
+    public boolean isNewVersion(String currentVersion, String newVersion) {
         if (currentVersion == null || newVersion == null) {
             return false;
         }
@@ -180,9 +139,22 @@ public class VersionUpdateUtil {
     /**
      * 下载安装包
      */
-    public long downloadApk(String downloadUrl, String version) {
+    /**
+     * 下载安装包
+     */
+    public long downloadApk(String downloadUrl, String version) throws Exception {
         Log.i(TAG, "开始下载安装包: " + downloadUrl);
 
+        // 验证下载链接
+        if (downloadUrl == null || downloadUrl.isEmpty()) {
+            throw new IllegalArgumentException("下载链接不能为空");
+        }
+
+        // 简单验证URL格式
+        if (!downloadUrl.startsWith("http://") && !downloadUrl.startsWith("https://")) {
+            throw new IllegalArgumentException("无效的下载链接格式");
+        }
+
         // 创建下载请求
         DownloadManager.Request request = new DownloadManager.Request(Uri.parse(downloadUrl));
 
@@ -192,7 +164,7 @@ public class VersionUpdateUtil {
 
         // 设置下载路径
         String fileName = "steeltower_update_v" + version + ".apk";
-        
+
         // 适配Android Q及以上版本的存储变更
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
             // Android Q及以上,使用DownloadManager的默认目录
@@ -221,6 +193,13 @@ public class VersionUpdateUtil {
         return downloadId;
     }
 
+
+    /**
+     * 安装APK
+     */
+    /**
+     * 安装APK
+     */
     /**
      * 安装APK
      */
@@ -244,6 +223,7 @@ public class VersionUpdateUtil {
         }
     }
 
+
     /**
      * 版本更新检查监听器
      */

+ 3 - 5
app/src/main/res/layout/dialog_update.xml

@@ -77,13 +77,11 @@
 
     <!-- 进度条 -->
     <ProgressBar
-        android:id="@+id/dialogProgressBar"
-        style="?android:attr/progressBarStyleHorizontal"
+        android:id="@+id/dialogProgressBar"    style="?android:attr/progressBarStyleHorizontal"
         android:layout_width="match_parent"
-        android:layout_height="6dp"
+        android:layout_height="wrap_content"
         android:max="100"
-        android:progress="0"
-        android:layout_marginBottom="8dp" />
+        android:progress="0" />
 
     <!-- 进度文本 -->
     <TextView

+ 12 - 7
app/src/main/res/xml/file_paths.xml

@@ -1,9 +1,14 @@
 <?xml version="1.0" encoding="utf-8"?>
 <paths xmlns:android="http://schemas.android.com/apk/res/android">
-    <external-path
-        name="downloads"
-        path="Download" />
-    <external-files-path
-        name="apk_files"
-        path="Download" />
-</paths>
+    <!-- 公共下载目录 -->
+    <external-path name="external_files" path="."/>
+
+    <!-- 明确指定Download目录 -->
+    <external-path name="download" path="Download/"/>
+
+    <!-- 其他可能需要的路径 -->
+    <external-cache-path name="external_cache" path="." />
+    <external-files-path name="external_files_path" path="." />
+    <cache-path name="cache" path="." />
+    <files-path name="files" path="." />
+</paths>