Mobile Development 37 min read

Implementing Android App Update with SpringBoot Backend and Vue Frontend

The article walks through a complete Android app update workflow where an administrator uploads an APK via a Vue front‑end to a SpringBoot service, the backend stores version data, and the Android client periodically checks for newer versions, downloads the APK using a Service and OkHttp, and installs it automatically.

The Dominant Programmer
The Dominant Programmer
The Dominant Programmer
Implementing Android App Update with SpringBoot Backend and Vue Frontend

Scenario

An administrator logs into a web back‑end, uploads an APK file together with version number and release notes via a Vue front‑end, and the back‑end provides CRUD APIs for managing app versions and a download endpoint for the latest package.

Android implementation

Project setup and dependencies

Create a new Android project named AppUpdateDemo and add the following Gradle dependencies:

implementation 'com.google.code.gson:gson:2.8.6'
implementation 'com.squareup.okhttp3:logging-interceptor:3.4.1'

Required permissions

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<!-- Permission for showing system dialogs from a Service -->
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />

FileProvider configuration (Android 7.0+)

Define a FileProvider in the manifest and create res/xml/file_path.xml:

<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <external-path name="install_path" path="."/>
    <root-path name="root_path" path="."/>
</paths>

Update configuration constants

Create UpdateConfig to define status codes used by the update logic:

public class UpdateConfig {
    public static final int FILE_EXIST_INTACT = 1;
    public static final int FILE_NOT_EXIST = 2;
    public static final int FILE_NOT_INTACT = 3;
    public static final int FILE_DELETE_ERROR = 4;
    public static final int CLIENT_INFO_ERROR = 5;
    public static final int DIALOG_MUST = 6;
    public static final int DIALOG_CAN = 7;
    public static final int DOWNLOAD_ERROR = 8;
    public static final int INSTALL_ERROR = 9;
}

Network utilities – UpdateCheck

Provides methods to verify network availability, request the latest version information from the back‑end, and parse the JSON response using Gson.

public class UpdateCheck {
    public static boolean isNetWorkAvailable(Context ctx) { /* ... */ }
    public static boolean isNetWorkConnected(Context ctx) { /* ... */ }
    public static void checkVersion(OkHttpClient client, String url, String versionName, String packageName, CheckVersionResult result) {
        if (TextUtils.isEmpty(url)) { result.fail(-1); return; }
        Request request = new Request.Builder().url(url).build();
        client.newCall(request).enqueue(new Callback() {
            @Override public void onFailure(Call call, IOException e) { result.error(e); }
            @Override public void onResponse(Call call, Response response) throws IOException {
                if (response.isSuccessful()) {
                    UpdateBean info = new Gson().fromJson(response.body().string(), UpdateBean.class);
                    String oldUrl = info.getData().getDownloadLink();
                    String newUrl = "http://<your‑server>:8888/system/file/download?fileName=" + oldUrl;
                    info.getData().setDownloadLink(newUrl);
                    result.success(info);
                } else { result.fail(response.code()); }
            }
        });
    }
    public interface CheckVersionResult { void success(UpdateBean info); void fail(int code); void error(Throwable t); }
}

Update dialogs – UpdateDialog

Utility class that creates three types of dialogs: forced update, optional update, and error notification.

public class UpdateDialog {
    public static Dialog mustUpdate(Context ctx, String msg, DialogInterface.OnClickListener listener) {
        AlertDialog.Builder b = new AlertDialog.Builder(ctx);
        b.setTitle("Version Update");
        b.setMessage(msg);
        b.setCancelable(false);
        b.setPositiveButton("Update", listener);
        return b.create();
    }
    public static Dialog canUpdate(Context ctx, String msg, DialogInterface.OnClickListener listener, DialogInterface.OnCancelListener cancel) {
        AlertDialog.Builder b = new AlertDialog.Builder(ctx);
        b.setTitle("Version Update");
        b.setMessage(msg);
        b.setCancelable(false);
        b.setPositiveButton("Update", listener);
        b.setNegativeButton("Later", listener);
        b.setOnCancelListener(cancel);
        return b.create();
    }
    public static Dialog errorDialog(Context ctx, String msg, DialogInterface.OnClickListener listener) {
        AlertDialog.Builder b = new AlertDialog.Builder(ctx);
        b.setTitle("Version Update");
        b.setMessage(msg);
        b.setCancelable(false);
        b.setNegativeButton("OK", listener);
        return b.create();
    }
    public static ProgressDialog durationDialog(Context ctx) {
        ProgressDialog d = new ProgressDialog(ctx);
        d.setTitle("Version Update");
        d.setProgressStyle(ProgressDialog.STYLE_HORIZONTAL);
        d.setCancelable(false);
        return d;
    }
    public static void setType(Dialog d) {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            d.getWindow().setType(WindowManager.LayoutParams.TYPE_APPLICATION_OVERLAY);
        } else {
            d.getWindow().setType(WindowManager.LayoutParams.TYPE_SYSTEM_ALERT);
        }
    }
}

File handling – UpdateFile

Provides asynchronous verification of existing files, download of the APK, and installation via FileProvider.

public class UpdateFile {
    public static class CheckFile extends AsyncTask<String, Integer, Void> { /* verifies file integrity */ }
    public static void checkFile(File f, String url, Handler h) { /* same logic as CheckFile */ }
    private static boolean verifyFile(String url, File file) { /* compare local length with server length */ }
    private static long getFileLength(String url) { /* HEAD request to obtain content length */ }
    public static void installApp(File file, Context ctx, Handler h) { /* launch installer intent */ }
    public static class DownloadAsync extends AsyncTask<String,Integer,Integer> {
        protected Integer doInBackground(String... params) {
            String downloadUrl = params[0];
            long total = 0;
            Request request = new Request.Builder().url(downloadUrl).build();
            Response response = new OkHttpClient().newCall(request).execute();
            if (response.code() == 200) {
                InputStream in = response.body().byteStream();
                FileOutputStream out = new FileOutputStream(file);
                byte[] buf = new byte[1024];
                int len;
                while ((len = in.read(buf)) != -1) {
                    total += len;
                    out.write(buf, 0, len);
                    int progress = (int) (total * 100f / getFileLength(downloadUrl));
                    publishProgress(progress);
                }
                out.flush();
                return 1; // success
            }
            return 2; // fail
        }
        protected void onProgressUpdate(Integer... values) { listener.onProgress(values[0]); }
        protected void onPostExecute(Integer result) { if (result == 1) listener.onSuccess(); else listener.onFail(); }
    }
    public interface DownloadListener { void onProgress(int p); void onSuccess(); void onFail(); }
}

Update service – UpdateService

A foreground service that periodically contacts the back‑end, determines whether a newer version exists, and triggers the appropriate UI flow.

public class UpdateService extends Service {
    public static boolean isRunning = false;
    public static String checkUrl = "http://your-server:8888/fzyscontrol/sys/version/getLastestVersion";
    private int versionCode = -1;
    private String updateUrl = "";
    private String description = "";
    private File targetFile;
    private Handler handler = new Handler() {
        public void handleMessage(Message msg) {
            switch (msg.what) {
                case UpdateConfig.FILE_EXIST_INTACT:
                    UpdateFile.installApp(targetFile, getApplicationContext(), this);
                    break;
                case UpdateConfig.FILE_NOT_EXIST:
                case UpdateConfig.FILE_NOT_INTACT:
                    mProgressDialog = UpdateDialog.durationDialog(getApplicationContext());
                    UpdateDialog.setType(mProgressDialog);
                    mProgressDialog.show();
                    new UpdateFile.DownloadAsync(targetFile, downloadListener).execute(updateUrl);
                    break;
                case UpdateConfig.DIALOG_MUST:
                    showDialog(DialogType.MUST);
                    break;
                case UpdateConfig.DIALOG_CAN:
                    showDialog(DialogType.CAN);
                    break;
                case UpdateConfig.CLIENT_INFO_ERROR:
                case UpdateConfig.DOWNLOAD_ERROR:
                case UpdateConfig.INSTALL_ERROR:
                    error_msg = "Error occurred";
                    showDialog(DialogType.ERROR);
                    break;
            }
        }
    };
    public void onCreate() {
        filePath = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator + "MyApp/";
    }
    public int onStartCommand(Intent intent, int flags, int startId) {
        if (!isRunning) {
            isRunning = true;
            versionCode = getVersionCode(this);
            String versionName = getVersionName(this);
            if (UpdateCheck.isNetWorkConnected(this) && UpdateCheck.isNetWorkAvailable(this)) {
                UpdateCheck.checkVersion(client, checkUrl, versionName, getPackageName(), versionResult);
            } else {
                handler.sendEmptyMessage(UpdateConfig.CLIENT_INFO_ERROR);
            }
        }
        return super.onStartCommand(intent, flags, startId);
    }
    // Helper methods for version retrieval, dialog display, and download listener omitted for brevity
}

Main activity integration

In MainActivity, check for the floating‑window permission, start UpdateService, and handle the permission result.

public class MainActivity extends AppCompatActivity {
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        if (UpdateCheck.checkFloatPermission(this)) {
            startService(new Intent(this, UpdateService.class));
        } else {
            Intent i = new Intent(Settings.ACTION_MANAGE_OVERLAY_PERMISSION);
            if (Build.VERSION.SDK_INT > Build.VERSION_CODES.M) {
                i.setData(Uri.parse("package:" + getPackageName()));
            }
            startActivityForResult(i, 212);
        }
    }
    protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        if (requestCode == 212 && UpdateCheck.checkFloatPermission(this)) {
            startService(new Intent(this, UpdateService.class));
        } else {
            Toast.makeText(this, "Please grant overlay permission", Toast.LENGTH_SHORT).show();
        }
    }
}

Backend – SpringBoot

File upload/download controller

@RestController
@RequestMapping("file")
public class FileController {
    @PostMapping("upload")
    public AjaxResult upload(@RequestParam("file") MultipartFile file) {
        String path = "D://fzys/file/" + DateUtils.datePath() + "/";
        FileUtils.check_folder(path);
        String fileName = UploadUtil.save_file(file, path);
        return AjaxResult.success().put("fileName", path + fileName);
    }
    @GetMapping("download")
    public void download(String fileName, HttpServletResponse response) throws IOException {
        File file = new File(fileName);
        response.reset();
        response.addHeader("Content-Disposition", "attachment;filename=" + new String(fileName.getBytes("gbk"), "iso-8859-1"));
        response.addHeader("Content-Length", "" + file.length());
        try (InputStream in = new BufferedInputStream(new FileInputStream(fileName));
             OutputStream out = new BufferedOutputStream(response.getOutputStream())) {
            byte[] buf = new byte[in.available()];
            in.read(buf);
            out.write(buf);
        }
    }
}

Utility classes

public class UploadUtil {
    public static String save_file(MultipartFile file, String path) throws IOException {
        String suffix = file.getOriginalFilename().substring(file.getOriginalFilename().lastIndexOf('.'));
        String fileName = UUID.randomUUID().toString() + suffix;
        File dest = new File(path, fileName);
        dest.getParentFile().mkdirs();
        file.transferTo(dest);
        return fileName;
    }
}
public class FileUtils {
    public static void check_folder(String path) {
        File dir = new File(path);
        if (!dir.isDirectory()) dir.mkdirs();
    }
}
public class DateUtils {
    public static final String datePath() {
        return new SimpleDateFormat("yyyy/MM/dd").format(new Date());
    }
}

Version management

Database table stores app version information (id, appName, versionNum, downloadLink, updateInstructions, updateTime). The generated controller provides CRUD endpoints and a getLastestVersion endpoint that returns the record with the highest versionNum.

@RestController
@RequestMapping("/sys/version")
public class SysAppVersionController {
    @GetMapping("/getLastestVersion")
    public AjaxResult getLastestVersion(){
        SysAppVersion v = sysAppVersionMapper.getLast();
        return AjaxResult.success(v);
    }
    // other CRUD methods omitted for brevity
}

MyBatis mapper snippet:

<select id="getLast" resultMap="SysAppVersionResult">
    SELECT * FROM sys_app_version ORDER BY version_num DESC LIMIT 1
</select>

Frontend – Vue (RuoYi Admin)

The admin UI provides a page to manage version records, upload APK files via el‑upload, and invoke the back‑end APIs.

export default {
    data() {
        return {
            tableData: [],
            queryParams: { pageNum: 1, pageSize: 10 },
            form: { id: null, appName: null, downloadLink: null, updateInstructions: null, versionNum: null },
            fileList: [],
            headers: { Authorization: "Bearer " + getToken() },
            url: process.env.VUE_APP_BASE_API + '/system/file/upload'
        };
    },
    methods: {
        listData() { getList(this.queryParams).then(r => { this.tableData = r.rows; this.total = r.total; }); },
        handleAdd() { this.title = 'Add'; this.open = true; this.$refs['form'].resetFields(); this.fileList = []; },
        uploadSuccess(res, file, fileList) { this.form.downloadLink = res.fileName; this.fileList = fileList; },
        submitForm() { this.$refs['form'].validate(valid => { if (valid) { add(this.form).then(r => { this.$message(r.msg); this.open = false; this.listData(); }); } }); },
        // edit, delete, and other UI logic omitted for brevity
    },
    created() { this.listData(); }
};

End‑to‑End flow

The admin uploads an APK and version metadata via the Vue UI; the file is saved on the server and the record is stored in the database.

The Android app starts UpdateService, which calls /sys/version/getLastestVersion to obtain the latest version info.

If the server version is newer than the installed version, the service shows a forced or optional update dialog.

When the user confirms, UpdateFile.CheckFile verifies whether the APK already exists locally; if not, UpdateFile.DownloadAsync downloads it using OkHttp.

After download, UpdateFile.installApp launches the installer via a FileProvider URI, handling Android 7.0+ restrictions.

Successful installation completes the update cycle.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

AndroidVueSpringBootREST APIFileProviderOkHttpApp Update
The Dominant Programmer
Written by

The Dominant Programmer

Resources and tutorials for programmers' advanced learning journey. Advanced tracks in Java, Python, and C#. Blog: https://blog.csdn.net/badao_liumang_qizhi

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.