安卓开发APP内部更新,通过服务器进行简单的版本管理,以及小部分的nginx的配置

目录

基础配置

实现思路

代码部分

APPUtils

FileDownloadService

RetrofitClient

UpdateManager

DownloadProgressCallback

FileUtils

ApiResponse

GeneralObjectCallBack

MainActivity

JAVA后端SpringBoot

UpdateResponse

FileUtils

Result

Controller

Nginx

Nginx最终配置


基础配置

最近用到了APP内部更新,网上查的资料零零碎碎,完整的解决方案实际也是用不了,通过整合和自己写的代码也是实现了,励志做新年最全的实现方式,但是需要一点代码经验哦.

先给一下gradle的依赖用的是retrofit,直接用okhttp3我不太会原谅我是个菜鸟.

dependencies {
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    //后来引入
    implementation 'androidx.navigation:navigation-fragment:2.4.1'
    implementation 'androidx.navigation:navigation-ui:2.4.1'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'com.google.code.gson:gson:2.8.8'

    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// 如果需要使用 Gson 解析 JSON
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// 如果需要使用 Scalars 转换器(支持 String、int、boolean 等类型)
    implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'

    implementation 'commons-logging:commons-logging:1.2'
    implementation 'org.apache.commons:commons-lang3:3.12.0'
    implementation 'org.greenrobot:eventbus:3.2.0' // 添加 EventBus 依赖

}

这些都是很常用的依赖重点是得添加retrofit,retrofit并不是一个网络请求的框架,它是一个基于okHttp3的封装,之前自己封装过okhttp3做过基础的请求方法,但是过于难用了,避免后来者看到代码自取其辱,然后也是狠狠加入retrofit了,现在就是真香,兄弟们!!!

我们先做最简单的前置工作,也就是manifest的工作,这部分不需要代码能力只要会复制粘贴就好了.下面我上manifest

    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" android:maxSdkVersion="28" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32" />
    <uses-permission android:name="android.permission.READ_MEDIA_IMAGES" android:required="false" />
    <uses-permission android:name="android.permission.READ_MEDIA_VIDEO" android:required="false" />
    <uses-permission android:name="android.permission.READ_MEDIA_AUDIO" android:required="false" />
    <uses-permission android:name="android.permission.READ_MEDIA_FILES" android:required="false" />

上面这个是加基础的权限,但是部分需要代码的动态申请也是需要用户自己操作的,待会会上代码的.

    <application
中间是你的其他奇奇怪怪的设置总之你得把这个provider被application进行包裹        
        <provider
            android:name="androidx.core.content.FileProvider"
            android:authorities="com.ximin.fileprovider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/file_paths" />
        </provider>
    </application>

这里的@xml/file_paths需要你创建一个xml文件

那个network_security_config.xml的文件是安卓允许明文访问ip请求接口的配置,你配置请求的时候是得自己设置的,我的重点不是这个,所以要配置记得自己找别的博客弄哦.

<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 允许共享外部存储的下载目录 -->
    <external-path
        name="external_files"
        path="." />
    <!-- 如果需要,也可以配置更多路径 -->
</paths>

上面这个就是file_paths.xml的代码,直接复制粘贴过去就可以爽用了.

到目前位置,没有需要敲代码的地方,只要会复制粘贴就可以了,下面我们来需要一些理解了.

实现思路

1.首先得需要获取你的软件的版本号,然后将当前安装的版本号和服务器上你上传的apk进行版本对比,得出你是否需要对你的软件进行更新.

2.实现文件写入,然后申请安装.

代码部分

1.我们先来获取我们的版本号,这里有个打包小技巧,我也是从别的博客哪里学来的,实在是太好用了,后续的版本对比我直接就抛弃了output.json这个文件的主要原因,当然详细的怎么搞只需要你会文件的写入写出也能自己解决,我就写我自己的解决方案了.

plugins {
    id 'com.android.application'
}

android {
    namespace 这里写你们的包名格式是单引号例如 'com.yonglu'
    compileSdk 33

    defaultConfig {
        applicationId "com.ximin"
        minSdk 26
        targetSdk 33
        versionCode 1
        versionName "${releaseTime()}"
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            android.applicationVariants.all { variant ->
                variant.outputs.all {
                    outputFileName = "my_${releaseTime()}.apk"
                }
            }


        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
}

dependencies {
    implementation 'androidx.appcompat:appcompat:1.4.1'
    implementation 'com.google.android.material:material:1.1.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.1.3'
    testImplementation 'junit:junit:4.13.2'
    androidTestImplementation 'androidx.test.ext:junit:1.1.3'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0'
    //后来引入

    implementation 'androidx.navigation:navigation-fragment:2.4.1'
    implementation 'androidx.navigation:navigation-ui:2.4.1'
    implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0'
    implementation 'com.squareup.okhttp3:okhttp:4.9.3'
    implementation 'com.google.code.gson:gson:2.8.8'
    // 在项目的 build.gradle 文件中
    implementation 'com.squareup.retrofit2:retrofit:2.9.0'
// 如果需要使用 Gson 解析 JSON
    implementation 'com.squareup.retrofit2:converter-gson:2.9.0'
// 如果需要使用 Scalars 转换器(支持 String、int、boolean 等类型)
    implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
    implementation 'com.squareup.retrofit2:converter-moshi:2.9.0'
    implementation 'com.squareup.retrofit2:adapter-rxjava3:2.9.0'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.2'

    implementation 'commons-logging:commons-logging:1.2'
    implementation 'org.apache.commons:commons-lang3:3.12.0'
    implementation 'org.greenrobot:eventbus:3.2.0' // 添加 EventBus 依赖

}

def releaseTime() {
    return new Date().format("yyyyMMddHHmmss", TimeZone.getTimeZone("UTC"))
}

细节是这里的outputFileName这里根据了时间戳生成安装包名称,实际版本会是你的时间戳哦!!!

2.我们先创建一个utils的包,用来存放我们的工具类

我知道我废话有点多了,但是想让很多人能看懂,这里我们需要AppUtils的实现.

APPUtils

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;

/**
 * 为了获取软件名称做的工具类
 */
public class AppUtils {
    public static String getAppVersion(Context context) {
        try {
            // 获取当前应用的 PackageInfo
            PackageInfo packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), 0);

            // 获取版本号(数字形式)
            int versionCode = packageInfo.versionCode;

            // 获取版本名(字符串形式)
            String versionName = packageInfo.versionName;

            // 输出版本号和版本名
            return versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        return null;
    }

}

这里复制过去就可以使用.

下面是存放接口的service

FileDownloadService

import com.ximin.pojo.ApiResponse;
import com.ximin.pojo.UpdateResponse;

import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.Query;
import retrofit2.http.Streaming;
import retrofit2.http.Url;

public interface FileDownloadService {
    @GET
    @Streaming
    Call<ResponseBody> downloadFile(@Url String fileUrl);

    @GET("/checkUpdate")
    Call<ApiResponse<UpdateResponse>> checkUpdate(@Query("currentVersion")String currentVersion);
}

上面的downloadFile直接传递一个url就能下载了,下面的checkUpdate也是很好理解就是查询更新

创建一个retrofit的实例方便获取和直接使用

RetrofitClient

import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;

/**
 * 下载安装的client
 */
public class DownloadRetrofitClient {
    private static Retrofit retrofit;

    public static Retrofit getClient(String baseUrl) {
        if (retrofit == null) {
            retrofit = new Retrofit.Builder()
                    .baseUrl(baseUrl)
                    .addConverterFactory(GsonConverterFactory.create())
                    .build();
        }
        return retrofit;
    }

    public static FileDownloadService getDownloadService(String baseUrl) {
        return getClient(baseUrl).create(FileDownloadService.class);
    }


}

写代码时直接获取下面这个FileDownloadService就行.

下面这个也是放进utils,是网络请求的封装

UpdateManager

import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.provider.Settings;
import android.util.Log;
import android.widget.Toast;
import androidx.core.content.FileProvider;
import com.ximin.call.DownloadProgressCallback;
import com.ximin.call.GeneralObjectCallBack;
import com.ximin.client.DownloadRetrofitClient;
import com.ximin.inface.ProgressListener;
import com.ximin.pojo.ApiResponse;
import com.ximin.pojo.UpdateResponse;
import com.ximin.service.FileDownloadService;
import java.io.File;
import okhttp3.ResponseBody;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;


public class UpdateManager {
    private final static String TAG = "UpdateManager";

    // 直接传入完整的 downloadUrl,而不再需要固定的 baseUrl
    public void downloadApkWithProgress(Context context, String apkUrl, String apkFileName, DownloadProgressCallback callback) {
        Log.d(TAG, "downloadApkWithProgress: 我被调用了");
        FileDownloadService downloadService = DownloadRetrofitClient.getDownloadService(""); // 不需要 baseUrl因为我是直接让服务器返回了apk在服务器中的下载链接,要是你有别的需求是可以改的哦,我就不提供别的实现了,反正就是这么个事
        downloadService.downloadFile(apkUrl).enqueue(new Callback<ResponseBody>() {
            @Override
            public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
                if (response.isSuccessful()) {
                    try {
                        long totalBytes = response.body().contentLength();  // 获取文件的总长度
                        // 使用异步任务或线程来处理文件保存和进度更新
                        new Thread(() -> {
                            File file = null;
                            try {
                                file = FileUtils.saveFileFromResponseWithProgress(context, response.body(), apkFileName, totalBytes, new ProgressListener() {
                                    @Override
                                    public void update(long bytesRead, long contentLength, boolean done) {
                                        // 进度更新时切换到主线程
                                        final int progress = (int) ((bytesRead * 100) / contentLength);
                                        // 使用 handler 或 runOnUiThread 来更新 UI
                                        new Handler(Looper.getMainLooper()).post(() -> callback.onProgress(progress));
                                    }
                                });
                            } catch (Exception e) {
                                throw new RuntimeException(e);
                            }

                            // 下载完成后回调
                            File finalFile = file;
                            new Handler(Looper.getMainLooper()).post(() -> callback.onComplete(finalFile));

                        }).start();

                    } catch (Exception e) {
                        e.printStackTrace();
                        new Handler(Looper.getMainLooper()).post(() -> callback.onError("保存文件失败"));
                    }
                } else {
                    new Handler(Looper.getMainLooper()).post(() -> callback.onError("下载失败"));
                }
            }

            @Override
            public void onFailure(Call<ResponseBody> call, Throwable t) {
                t.printStackTrace();
                new Handler(Looper.getMainLooper()).post(() -> callback.onError("下载失败: " + t.getMessage()));
            }
        });
    }


    //向服务器发起请求获取软件更新的url地址
    public void checkUpdate(String currentApkName, GeneralObjectCallBack<UpdateResponse> callBack) {
        FileDownloadService downloadService = DownloadRetrofitClient.getDownloadService("这里需要写你的服务器请求的地址哦比如http://loacalhost:8080 localhost换成你服务器ip这里的端口当然是你接口放的端口了啦");
        downloadService.checkUpdate(currentApkName).enqueue(new Callback<ApiResponse<UpdateResponse>>() {
            @Override
            public void onResponse(Call<ApiResponse<UpdateResponse>> call, Response<ApiResponse<UpdateResponse>> response) {
                ApiResponse<UpdateResponse> apiResponse = response.body();
                if (apiResponse != null && apiResponse.getCode() != 0) {
                    UpdateResponse updateResponse = apiResponse.getData();
                    callBack.onSuccess(updateResponse);
                } else {
                    Log.d(TAG, "onResponse: 返回为空");
                }
            }

            @Override
            public void onFailure(Call<ApiResponse<UpdateResponse>> call, Throwable t) {
                Log.e(TAG, "onFailure: 请求失败", t);
                callBack.onFailure("请求失败" + t.getMessage());
            }
        });
    }

}

下面是回调的接口

DownloadProgressCallback

import java.io.File;

public interface DownloadProgressCallback {
    void onProgress(int progress);   // 下载进度回调
    void onComplete(File file);      // 下载完成回调
    void onError(String errorMessage);  // 错误回调
}

然后是FileUtils

FileUtils

import android.content.Context;

import com.ximin.inface.ProgressListener;

import java.io.File;
import java.io.FileOutputStream;
import java.io.InputStream;
import java.io.OutputStream;

import okhttp3.ResponseBody;

public class FileUtils {

    public static File saveFileFromResponseWithProgress(Context context, ResponseBody body, String fileName, long totalBytes, ProgressListener listener) throws Exception {
        File file = new File(context.getExternalFilesDir(null), fileName);
        InputStream inputStream = null;
        OutputStream outputStream = null;

        try {
            inputStream = body.byteStream();
            outputStream = new FileOutputStream(file);
            byte[] buffer = new byte[1024];
            long totalRead = 0;
            int bytesRead;

            // 读取文件并写入
            while ((bytesRead = inputStream.read(buffer)) != -1) {
                totalRead += bytesRead;
                outputStream.write(buffer, 0, bytesRead);

                // 回调更新下载进度
                listener.update(totalRead, totalBytes, totalRead == totalBytes);
            }

            outputStream.flush();
        } finally {
            if (inputStream != null) inputStream.close();
            if (outputStream != null) outputStream.close();
        }

        return file;
    }
}

剩下的代码就是你打开app的主界面了哦,也就是mainActivity了啦要用到的声明变量

ApiResponse

public class ApiResponse<T> {

    private int code;
    private String msg;
    private T data;

    public int getCode() {
        return code;
    }

    public void setCode(int code) {
        this.code = code;
    }

    public String getMsg() {
        return msg;
    }

    public void setMsg(String msg) {
        this.msg = msg;
    }

    public T getData() {
        return data;
    }

    public void setData(T data) {
        this.data = data;
    }

    public ApiResponse() {
    }

    public ApiResponse(int code, String msg, T data) {
        this.code = code;
        this.msg = msg;
        this.data = data;
    }
}

GeneralObjectCallBack

/**
 * 直接传回对象时用的通用接收类
 */
public interface GeneralObjectCallBack<T> {
    void onSuccess(T data);
    void onFailure(String errorMessage);
}

/**
 * 更新用的url接收类
 */
public class UpdateResponse {
    private boolean updateRequired;
    private String latestVersion;
    private String downloadUrl;

    // Getters and Setters
    public boolean isUpdateRequired() {
        return updateRequired;
    }

    public void setUpdateRequired(boolean updateRequired) {
        this.updateRequired = updateRequired;
    }

    public String getLatestVersion() {
        return latestVersion;
    }

    public void setLatestVersion(String latestVersion) {
        this.latestVersion = latestVersion;
    }

    public String getDownloadUrl() {
        return downloadUrl;
    }

    public void setDownloadUrl(String downloadUrl) {
        this.downloadUrl = downloadUrl;
    }
}

MainActivity

    private static final int INSTALL_PACKAGES_REQUEST_CODE = 1001;
    private UpdateManager updateManager;
    private Context context;
    private ProgressBar progressBar;  // 使用 ProgressBar 替代 ProgressDialog
    private AlertDialog progressDialog; // 使用 AlertDialog 包裹进度条
    private String apkVersion;//apk版本

然后是要在oncreat中实现的方法,给你们完整的看看怎么写的,然后你们可以自己复制粘贴到你自己代码的位置



import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.content.FileProvider;

import android.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Color;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
import android.widget.ProgressBar;
import android.widget.Toast;


import java.io.File;

public class MainActivity extends AppCompatActivity {
    private static final String TAG = "MainActivity";

    private static final int INSTALL_PACKAGES_REQUEST_CODE = 1001;
    private UpdateManager updateManager;
    private Context context;
    private ProgressBar progressBar;  // 使用 ProgressBar 替代 ProgressDialog
    private AlertDialog progressDialog; // 使用 AlertDialog 包裹进度条
    private String apkVersion;//apk版本

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        initView();
        requestPermissions();
        apkVersion = AppUtils.getAppVersion(this);
        Log.d("APKFileName", "当前应用的 APK 文件名:" + apkVersion);
        UpdateApp(apkVersion);

        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
            getWindow().getDecorView().setSystemUiVisibility(
                    View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_STABLE);
            getWindow().setStatusBarColor(Color.TRANSPARENT);
        }

    }

    // 显示进度条
    public void showDownloadProgress(int progress) {
        runOnUiThread(() -> {
            if (progressBar != null) {
                progressBar.setProgress(progress);  // 更新进度
            }
        });
    }

    // 显示进度对话框
    private void showProgressDialog() {
        // 创建一个 AlertDialog 来显示进度条
        progressDialog = new AlertDialog.Builder(this)
                .setTitle("下载中...")
                .setMessage("正在下载更新,请稍等...")
                .setCancelable(false)
                .create();

        // 使用 ProgressBar 来显示进度
        progressBar = new ProgressBar(this);
        progressBar.setIndeterminate(false); // 设置为不确定模式
        progressBar.setMax(100);  // 设置最大值为 100

        progressDialog.setView(progressBar);
        progressDialog.show();
    }

    // 下载完成后的回调
    public void downloadCompleted(File file) {
        runOnUiThread(() -> {
            if (progressDialog != null && progressDialog.isShowing()) {
                progressDialog.dismiss();  // 下载完成后关闭进度条
            }
            Toast.makeText(MainActivity.this, "下载完成", Toast.LENGTH_SHORT).show();
            installApk(file);  // 启动 APK 安装
        });
    }

    // 启动 APK 安装
    private void installApk(File file) {
        Uri apkUri = FileProvider.getUriForFile(this, getPackageName() + ".fileprovider", file);
        Intent intent = new Intent(Intent.ACTION_VIEW);
        intent.setDataAndType(apkUri, "application/vnd.android.package-archive");
        intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_GRANT_READ_URI_PERMISSION);
        startActivity(intent);
    }

    // 检查并更新应用
    private void UpdateApp(String apkFileName) {
        updateManager.checkUpdate(apkFileName, new GeneralObjectCallBack<UpdateResponse>() {
            @Override
            public void onSuccess(UpdateResponse data) {
                String downloadUrl = data.getDownloadUrl();
                String version = data.getLatestVersion();
                boolean updateRequired = data.isUpdateRequired();
                Log.d(TAG, "onSuccess: 下载的链接是" + downloadUrl);
                Log.d(TAG, "onSuccess: 版本是" + version);
                if (updateRequired) {
                    showUpdateDialog(downloadUrl, version);  // 显示更新提示
                }
            }

            @Override
            public void onFailure(String errorMessage) {
                Toast.makeText(context, errorMessage, Toast.LENGTH_SHORT).show();
            }
        });
    }

    // 显示更新对话框
    private void showUpdateDialog(String downloadUrl, String version) {
        new AlertDialog.Builder(MainActivity.this)
                .setTitle("发现新版本")
                .setMessage("当前版本是"+apkVersion+"是否更新到最新版本 " + version + " ?")
                .setPositiveButton("更新", (dialog, which) -> {
                    showProgressDialog();  // 显示下载进度条
                    updateManager.downloadApkWithProgress(context, downloadUrl, version, new DownloadProgressCallback() {
                        @Override
                        public void onProgress(int progress) {
                            showDownloadProgress(progress);  // 更新进度

                        }

                        @Override
                        public void onComplete(File file) {
                            downloadCompleted(file);  // 下载完成,安装 APK
                        }

                        @Override
                        public void onError(String errorMessage) {

                        }
                    });
                })
                .setNegativeButton("取消", null)
                .show();
    }

    // 初始化视图
    private void initView() {
        updateManager = new UpdateManager();
        context = MainActivity.this;
    }

    // 动态申请权限
    private void requestPermissions() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
            String[] permissions = {
                    Manifest.permission.READ_MEDIA_IMAGES,
                    Manifest.permission.READ_MEDIA_VIDEO,
                    Manifest.permission.READ_MEDIA_AUDIO,
                    Manifest.permission.REQUEST_INSTALL_PACKAGES
            };
            ActivityCompat.requestPermissions(this, permissions, INSTALL_PACKAGES_REQUEST_CODE);
        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
            String[] permissions = {
                    Manifest.permission.WRITE_EXTERNAL_STORAGE,
                    Manifest.permission.READ_EXTERNAL_STORAGE,
                    Manifest.permission.REQUEST_INSTALL_PACKAGES
            };
            ActivityCompat.requestPermissions(this, permissions, INSTALL_PACKAGES_REQUEST_CODE);
        }
    }

    // 权限结果回调
    @Override
    public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
        if (requestCode == INSTALL_PACKAGES_REQUEST_CODE) {
            for (int result : grantResults) {
                if (result != PackageManager.PERMISSION_GRANTED) {
//                    Toast.makeText(this, "需要权限才能继续更新", Toast.LENGTH_SHORT).show();
                    return;
                }
            }
        }
    }
}

把下面这些代码复制进你的项目,只需要在oncreat中直接调用就能实现自动更新了,然后就是改改url.

安卓部分的代码就这么多了,接下来是安卓java后端的代码哦!

JAVA后端SpringBoot

一样的UpdateResponse

UpdateResponse

public class UpdateResponse {
    private boolean updateRequired;
    private String latestVersion;
    private String downloadUrl;

    // Getters and Setters
    public boolean isUpdateRequired() {
        return updateRequired;
    }

    public void setUpdateRequired(boolean updateRequired) {
        this.updateRequired = updateRequired;
    }

    public String getLatestVersion() {
        return latestVersion;
    }

    public void setLatestVersion(String latestVersion) {
        this.latestVersion = latestVersion;
    }

    public String getDownloadUrl() {
        return downloadUrl;
    }

    public void setDownloadUrl(String downloadUrl) {
        this.downloadUrl = downloadUrl;
    }
}

FileUtils

import java.io.File;
import java.util.Arrays;
import java.util.Comparator;

public class FileUtils {
    // 获取目录下最新的 APK 文件名
    public static String getLatestApkFileName(String directoryPath) {
        File dir = new File(directoryPath);
        if (!dir.exists() || !dir.isDirectory()) {
            throw new IllegalArgumentException("目录不存在或不是有效目录:" + directoryPath);
        }

        // 列出所有以 .apk 结尾的文件
        File[] apkFiles = dir.listFiles((dir1, name) -> name.endsWith(".apk"));
        if (apkFiles == null || apkFiles.length == 0) {
            throw new IllegalStateException("目录中没有找到 APK 文件:" + directoryPath);
        }

        // 按文件名排序,确保最新的文件在最后(假设文件名带时间戳递增)
        Arrays.sort(apkFiles, Comparator.comparing(File::getName));
        return apkFiles[apkFiles.length - 1].getName(); // 返回最新文件名
    }
}

Result

import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;

@Data
@NoArgsConstructor
@AllArgsConstructor
public class Result {
    private Integer code;//响应码,1 代表成功; 0 代表失败
    private String msg;  //响应信息 描述字符串
    private Object data; //返回的数据

    //增删改 成功响应
    public static Result success(){
        return new Result(1,"success",null);
    }
    //查询 成功响应
    public static Result success(Object data){
        return new Result(1,"success",data);
    }
    //失败响应
    public static Result error(String msg){
        return new Result(0,msg,null);
    }
    public static Result exist(String msg){return new Result(2,msg,null);}
}

Controller

import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;

@RestController
public class UpdateController {

    private static final String APK_DIRECTORY = "/www/uploads/app"; // 当前最新版本(带时间戳)这里是你服务器中apk的存放路径哦

    @GetMapping("/checkUpdate")
    public Result checkUpdate(@RequestParam("currentVersion") String currentVersion) {
        UpdateResponse response = new UpdateResponse();
        System.out.println("传递的版本号是" + currentVersion);
        try {
            // 动态获取最新版本号
            String latestApkFileName = FileUtils.getLatestApkFileName(APK_DIRECTORY);

            // 提取时间戳
            String currentTimestamp = extractTimestampFromFilename(currentVersion);
            String latestTimestamp = extractTimestampFromFilename(latestApkFileName);

            System.out.println("裁切后的current: " + currentTimestamp);
            System.out.println("裁切后的latest: " + latestTimestamp);

            // 比较版本
            if (currentTimestamp.compareTo(latestTimestamp) < 0) {
                response.setUpdateRequired(true);
                response.setLatestVersion(latestApkFileName);
                response.setDownloadUrl("这里是放的是你用nginx映射的地址哦比如后面这个http://x.xx.xxx.xxx:8000/download/" + latestApkFileName); // 动态设置下载地址
            } else {
                response.setUpdateRequired(false);
            }
        } catch (Exception e) {
            e.printStackTrace();
            response.setUpdateRequired(false);
        }

        return Result.success(response);
    }

    // 提取文件名中的时间戳(假设格式为 my_yyyyMMddHHmmss.apk 或 智慧营养水肥机_yyyyMMddHHmmss.apk)
    private String extractTimestampFromFilename(String filename) {
        // 使用正则表达式提取时间戳部分
        String regex = "(\\d{14})"; // 14位时间戳,匹配 yyyyMMddHHmmss 格式
        java.util.regex.Pattern pattern = java.util.regex.Pattern.compile(regex);
        java.util.regex.Matcher matcher = pattern.matcher(filename);
        if (matcher.find()) {
            return matcher.group(1); // 返回第一个匹配的时间戳
        }
        throw new IllegalArgumentException("文件名中未找到有效的时间戳: " + filename);
    }
}

启动类我这里就不放了.

这里贴一个服务器返回的json数据

{
    "code": 1,
    "msg": "success",
    "data": {
        "updateRequired": false,
        "latestVersion": null,
        "downloadUrl": null
    }
}

接下来是Nginx和一些服务器上的配置

Nginx

配置分析:

server {
    listen 8000;
    server_name xxx.xx.xxx.xx;这里是填你的服务器ip也可以是域名
    index index.html index.htm index.php;
    root /www/server/phpmyadmin;

    #error_page   404   /404.html;
    include enable-php.conf;

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 30d;
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
    }

    location ~ /\.. {
        deny all;
    }

    location /download/ {
        alias /www/uploads/app/;这里就是你的apk存放路径
        autoindex on;
    }

    access_log /www/wwwlogs/access.log;
}
 1. server_name xx.xx.xx.xx;`
  这里指定了服务器的 IP 地址 ` xx.xx.xx.xx`,表示只有当客户端请求的 `Host` 头部为这个 IP 地址时,Nginx 才会处理该请求。如果你通过浏览器直接访问 `http://xx.xx.xx.xx:8000` 就可以使用这个配置。
注意: 如果你希望在浏览器中通过域名访问(如 `http://example.com:8000`),则需要将 `server_name` 设置为对应的域名,而不是 IP 地址。

 2. `root /www/server/phpmyadmin;
这条配置是指定了网站根目录的位置(即 `/www/server/phpmyadmin`)。

在 `/download/` 路径下使用了 `alias`,这是合理的做法,所以这条配置不会影响到 `/download/` 的访问。
 如果你不需要访问 PHPMyAdmin,你可以将 `root` 改为更合适的路径,或者去掉它(只保留 `alias`)。

 3. alias /www/uploads/app/;
 这里使用 `alias` 来映射 `/download/` 路径到 `/www/uploads/app/` 目录,这是正确的做法。通过 `http://xx.xx.xx.xx:8000/download/` 访问时,Nginx 会映射到 `/www/uploads/app/` 目录。
注意:alias 后面的路径必须以 `/` 结尾,确保路径的正确映射。

 4. autoindex on;
  这条指令启用了目录列表,如果用户访问的是 `http://xx.xxx.xxx.xx:8000/download/`,并且目录下没有默认的首页文件(如 `index.html`),Nginx 会列出 `/www/uploads/app/` 目录下的文件。

5. `access_log /www/wwwlogs/access.log;`
这是记录访问日志的路径,确保日志文件存在并且有写入权限,方便你查看服务器的访问记录。
如果你希望通过 IP 地址访问你的服务器,可以直接使用 `server_name xx.xx.xx.xx;`。
端口号 需要通过 `listen` 指令来指定,已经是 `8000` 端口,不需要在 `server_name` 中添加端口。
确保你配置的路径(如 `/www/uploads/app/` 和 `/www/server/phpmyadmin`)是正确且存在的。
`autoindex on` 会启用目录列表,用户可以查看文件。

Nginx最终配置

server {
    listen 8000;
    server_name x.xx.xx.xx;  # 这里是 IP 地址
    index index.html index.htm index.php;
    root /www/server/phpmyadmin;  # 这个配置暂时无影响,但可以调整为更合适的目录

    # error_page   404   /404.html;  # 如果需要自定义 404 错误页面可以启用

    include enable-php.conf;  # 包含 PHP 配置(如果有 PHP 文件需要处理)

    location ~ .*\.(gif|jpg|jpeg|png|bmp|swf)$ {
        expires 30d;
    }

    location ~ .*\.(js|css)?$ {
        expires 12h;
    }

    location ~ /\.. {
        deny all;  # 禁止访问隐藏文件(例如 .git、.env 等)
    }

    # 下载目录配置
    location /download/ {
        alias /www/uploads/app/;
        autoindex on;  # 启用目录列表,方便浏览文件
    }

    access_log /www/wwwlogs/access.log;  # 访问日志路径
}

现在,你应该能够通过 `http://xx.xx.xx.xx:8000/download/` 访问并下载文件。但是实际拼接的可下载url是http://xx.xx.xx.xx:8000/download/yourApkname.apk,所以你要根据自己实际apk在的位置配置你的nginx,我的话是放在了服务器/www/uploads/app/这个目录下了,最后启动你的nginx就可以了,这时候你就可以通过http://xx.xx.xx.xx:8000/download/yourApkname.apk这个格式的url,来测试你是否成功配置了nginx,记得设置防火墙放行和安全组开放端口允许所有ip访问哦!!!!

实现方法已经完全给出了,我觉得确实有点难弄呢,如果有问题可以评论区问哦,如果很急也可以私信.

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值