Android App version detection, download and install new version apk

Posted by callesson on Tue, 28 Dec 2021 15:38:47 +0100

background

Many Android applications have built-in new version detection and online update functions. This simple function mainly includes three links: detection, download and installation. The demonstration results are as follows:

After the download is completed, the apk will be opened automatically, and the installation interface will be skipped to the user for operation:

thinking

To realize the above functions, it is mainly divided into three steps:

  1. The App sends a network request to the server to obtain the latest version number of the App and compare it. If the version number returned by the server is greater than the version number of the current App, start the second step to download the new version of the App;
  2. When there is a new version of App, start the download, and give the download progress prompt on the interface to increase interactivity;
  3. When the download reaches 100%, open apk through code to realize installation.

realization

1. Version detection

Version detection is to send a network request to the server of the App and query the version number of the latest version of the App from the server. Generally speaking, the latest version number can be obtained by requesting static resources (manual configuration file, etc.) or dynamic interface.

  • For static resources, it is mainly to place an accessible configuration file on the server, which indicates the latest version number;
    Dynamic interface means that the server maintains an interface and can return the version number. The advantage is that it can be combined with the database to do more complex operations, such as maintaining version update records.

In this article, for simple expression, we use the first static resource method to place a text file version in JSON format on the server. Its access address is http://host/app/version , the content obtained after access is as follows:

{
	"versionCode": 1,
	"fileName" : "abc-20210806.apk"
}

Among them, versionCode is the versionCode (configuration attribute of Android application) of the latest version of App, and fileName is the file name of the latest version of App, which is used for file download.

App side detection version code:

RetrofitRequest.sendGetRequest(Constant.URL_APP_VERSION, new RetrofitRequest.ResultHandler(context) {
    ...
    @Override
    public void onResult(String response) {
        if (response == null || response.trim().length() == 0) {
            Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();
            LoadingDialog.close();
            return;
        }
        try {
            JSONObject jsonObject = new JSONObject(response);
            if (!jsonObject.has("versionCode") || !jsonObject.has("fileName")) {
                Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();
                LoadingDialog.close();
                return;
            }
            newVersionCode = jsonObject.getInt("versionCode");
            newFileName = jsonObject.getString("fileName");
            int versionCode = VersionUtil.getVersionCode(context);
            LoadingDialog.close();
            if (newVersionCode > versionCode) {
                showUpdateDialog(newFileName);
            } else {
                if (!isAutoCheck) {
                    Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();
                }
            }
        } catch (JSONException e) {
            Toast.makeText(context, R.string.layout_version_no_new, Toast.LENGTH_SHORT).show();
            LoadingDialog.close();
        }
    }
...
});

Where newversioncode > versioncode is the code that compares the version number of the latest server with the version of this App. According to the comparison results, if the current version is not the latest version, the update reminder dialog showUpdateDialog(newFileName) will be displayed.

private void showUpdateDialog(final String fileName) {
    ConfirmDialog dialog = new ConfirmDialog(context, new ConfirmDialog.OnClickListener() {
        @Override
        public void onConfirm() {
            showDownloadDialog(fileName);
        }
    });
    dialog.setTitle(R.string.note_confirm_title);
    dialog.setContent(R.string.layout_version_new);
    dialog.setConfirmText(R.string.layout_yes);
    dialog.setCancelText(R.string.layout_no);
    dialog.show();
}

2. Download the new version apk

When the user clicks "yes" in the update dialog box, it means that the latest version of apk needs to be downloaded. At this time, the download progress dialog box is displayed, and the download is started to refresh the download progress in real time:

private void showDownloadDialog(String fileName) {
    Builder builder = new Builder(context);

    View view = LayoutInflater.from(context).inflate(R.layout.dialog_download, null);
    proDownload = (ProgressBar) view.findViewById(R.id.pro_download);
    tvPercent = (TextView) view.findViewById(R.id.txt_percent);
    tvKbNow = (TextView) view.findViewById(R.id.txt_kb_now);
    tvKbAll = (TextView) view.findViewById(R.id.txt_kb_all);

    Button btnCancel = (Button) view.findViewById(R.id.btn_cancel);
    btnCancel.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            if (downloadDialog != null) {
                downloadDialog.dismiss();
            }
            cancelUpdate = true;
        }
    });

    downloadDialog = builder.create();
    downloadDialog.setCanceledOnTouchOutside(false);
    downloadDialog.show();
    downloadDialog.getWindow().setContentView(view);

    downloadApk(fileName);
}

Download background thread and front-end percentage update action:

private void downloadApk(String fileName) {
    ExecutorService executorService = Executors.newFixedThreadPool(1);

    Retrofit retrofit = new Retrofit.Builder()
            .baseUrl(Constant.URL_CONTRACT_BASE)
            .callbackExecutor(executorService)
            .build();

    String url = String.format(Constant.URL_APP_DOWNLOAD, fileName);
    FileRequest fileRequest = retrofit.create(FileRequest.class);
    Call<ResponseBody> call = fileRequest.download(url);
    call.enqueue(new Callback<ResponseBody>() {
        @Override
        public void onResponse(Call<ResponseBody> call, Response<ResponseBody> response) {
            if (response.isSuccessful()) {
                if (writeResponseBodyToDisk(response.body())) {
                    downloadDialog.dismiss();
                } else {
                    mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
                }
            } else {
                mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
            }
        }

        @Override
        public void onFailure(Call<ResponseBody> call, Throwable t) {
            mHandler.sendEmptyMessage(DOWNLOAD_ERROR);
        }
    });
}

private boolean writeResponseBodyToDisk(ResponseBody body) {
    savePath = StorageUtil.getDownloadPath(context);
    File apkFile = new File(savePath, newFileName);
    InputStream inputStream = null;
    OutputStream outputStream = null;
    try {
        byte[] fileReader = new byte[4096];
        long fileSize = body.contentLength();
        long fileSizeDownloaded = 0;
        inputStream = body.byteStream();
        outputStream = new FileOutputStream(apkFile);

        BigDecimal bd1024 = new BigDecimal(1024);
        totalByte = new BigDecimal(fileSize).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();

        while (!cancelUpdate) {
            int read = inputStream.read(fileReader);
            if (read == -1) {
                mHandler.sendEmptyMessage(DOWNLOAD_FINISH);
                break;
            }
            outputStream.write(fileReader, 0, read);
            fileSizeDownloaded += read;
            progress = (int) (((float) (fileSizeDownloaded * 100.0 / fileSize)));
            downByte = new BigDecimal(fileSizeDownloaded).divide(bd1024, BigDecimal.ROUND_HALF_UP).setScale(0).intValue();
            mHandler.sendEmptyMessage(DOWNLOAD_ING);
        }
        outputStream.flush();
        return true;
    } catch (Exception e) {
        e.printStackTrace();
        return false;
    } finally {
        if (outputStream != null) {
            try {
                outputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
        if (inputStream != null) {
            try {
                inputStream.close();
            } catch (IOException e) {
                e.printStackTrace();
            }
        }
    }
}

private void showProgress() {
    proDownload.setProgress(progress);
    tvPercent.setText(progress + "%");
    tvKbAll.setText(totalByte + "Kb");
    tvKbNow.setText(downByte + "Kb");
}

3. Install apk

After the latest version of apk is downloaded, the installation code is invoked to perform the installation action. The installation methods of the old and new versions of Android SDK are slightly different. See the code for details:

private void installApk() {
    File apkFile = new File(savePath, newFileName);
    if (!apkFile.exists()) {
        return;
    }
    Intent intent = new Intent(Intent.ACTION_VIEW);
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
        intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION);
        Uri contentUri = FileProvider.getUriForFile(context, context.getPackageName() + ".fileprovider", apkFile);
        intent.setDataAndType(contentUri, "application/vnd.android.package-archive");
    } else {
        intent.setDataAndType(Uri.parse("file://" + apkFile.toString()), "application/vnd.android.package-archive");
    }
    context.startActivity(intent);
}

summary

The version update of Android is relatively free and is not limited by the app store. The idea of implementation is clear, and it is easy to step down each link step by step, but there are several points that developers should pay attention to:

  1. The version number of this version code corresponds to build The versionCode in gradle is not versionName, and the Android system determines whether the installed application is a new version according to the versionCode;
  2. If you want to accurately display the download progress in the progress bar, the App should be able to read the apk size when downloading. If the apk is provided in the form of static resources, it is more convenient. Generally, it can be read from the web server, such as the above code body contentLength(). If it is returned from the file stream interface on the server side, make sure that the file stream interface correctly returns the content length attribute of the Http request, otherwise the apk size cannot be read and the progress cannot be accurately expressed.
  3. The above demonstration operation is that the user updates actively. If you want to do automatic update without interaction in the background, you only need to modify one construction parameter and use new updatemanager (this, updatemanager. Check_auto) Checkupdate(), and the detection process will not have loading effect.
  4. Dynamic permission application and Dialog customization are not the focus of this article, but the source code is complete and available, including this part.
  5. The server version configuration file and download program code are placed in the versionConfig folder of the source code for reference only.

Source download

See: http://github.com/ahuyangdong/VersionDownload

Topics: Android apk