How does APK implement hot update feature?

2019-10-22

Keywords: APK installer through code, APK update, open APK installer, APK upgrade


p>

This article is an improved version based on the author’s another APK upgrade-related article (how to implement the APK upgrade function).

The main improvements are as follows:

1. Added the function of installing APK compatible with high and low versions of the system< /p>

2, improve the timing detection process;

3, improve the prompt process;

p>

4. Improved storage logic, compatible with multiple types of mobile phones, to avoid the problem of insufficient permissions.

5. Distinguish between normal upgrade and mandatory upgrade functions.

1. Overview

The so-called hot update is just a better way of saying that the APK is automatically upgraded. If an APK can get rid of the dependence of the mobile phone system application store and only rely on itself to achieve the upgrade, it is called a hot update.

The APK upgrade needs to be performed during the running of the program, and some even need to be performed when the program is running in the background. Therefore, the upgrade function is usually done on a background service sub-thread.

And my current concept of writing code advocates “specialized people and dedicated work”, which is “high cohesion” in professional terms. Therefore, I encapsulated the entire upgrade-related function code in the same Service code file. Although it is actually mixed with many different types of function codes, fortunately, the entire code file is not huge, and the management of upgrade functions is also very easy.

For APK upgrade, I personally have never referred to other people’s implementation methods before. My own understanding of the upgrade is that there is a background thread that constantly retrieves the upgrade information from the upgrade server during the running of the program, and when there is a new version, it will prompt the user to choose whether to download it or not. And I think the upgrade should set at least two upgrade strategies: one is ordinary upgrade. The second is mandatory upgrade. Needless to say about ordinary upgrades, mandatory upgrade means that when a mandatory upgrade package is detected, the update package is downloaded in the background immediately, and the software installation interface will pop up after the download is completed, and the mandatory upgrade detection time interval will be shortened. If the user cancels the installation of the mandatory upgrade package, the installation interface will pop up again within a short period of time. Through this “dead skin” method to force users to install a new version of the software. It may be possible to implement the function of installing software directly through the code, but I did not study it, so I will not discuss the mandatory upgrade in this way.

2. Upgrade service

The author here directly implements all upgrade functions through an UpgradeService.

share picture

Since a Service has to implement multiple functions, such as: start and stop detection, prompting, download, installation, etc., this Service defines multiple types, as shown below:

Share a picture

< /p>

The author’s Service only supports the start method, so it is required to set the type in the startup Intent so that the Service can distinguish different actions. The implementation of the onStartCommand() method is shown in the following figure:

share picture

The structure of the entire Service is simple, just doing different tasks according to different type parameters. Let’s analyze these types of “lives” one by one.

3. Upgrade detection

There are two types of startup in Serivce: 1. TYPE_START_CHECK

strong>; 2, TYPE_STOP_CHECK. The author’s opening strategy for upgrade detection is: stop upgrade detection when the application is not in the foreground, and restart upgrade detection when it returns to the foreground.

First of all, regarding the timing function, here is the implementation of android.os.CountDownTimer that comes with Android. Since this class is an abstract class, the author encapsulated a Timer inner class inside Service to implement CountDownTimer, and customized some functions according to business needs. The code of the entire Timer inner class is as follows:

private static  class Timer extends CountDownTimer {


private int timing;
private int interval;

private OnTimeOutCallback callback;

Timer(
int timingSeconds, int intervalSeconds, OnTimeOutCallback callback) {
super(timingSeconds * 1000, intervalSeconds * 1000);
timing
= timingSeconds;
interval
= intervalSeconds;
this.callback = callback;
}

@Override
public void onTick(long l) {
Logger.d(TAG,
"onTick:" + l + "," + this);
}

@Override
public void onFinish() {
boolean ret = false;
if(callback != null) {
ret
= callback.onTimeOut(timing, interval);
}

if(!ret) {
start();
// Restart timing...
}
}

interface OnTimeOutCallback {
/**@return true means that the life cycle of the Timer has been processed, and the Timer should not restart the counting thread. . */
boolean onTimeOut(int timingSeconds, int countDownIntervalSeconds);
}

}
// class Timer - end.< /pre>

In this Timer class, the timing completion event is notified through a callback, and by default, the timer will be restarted for second timing counting after the timing is over and the callback is over.

Then, an internal class is encapsulated inside the Service class, which is responsible for ordinary upgrade checks. This internal class only provides a start() method and a stop() method. At the same time, since it needs to rely on the Timer class mentioned above to count regularly, in order to receive the timing result, it is necessary to implement the interface in the Timer class. The source code framework is as follows:

private class< /span> NormalUpgradeThread implements Timer.OnTimeOutCallback {


Timer timer;

private void start(){
// Start the timer according to actual business needs.
}

private void stop(){
// Stop the timer according to actual business needs
}

@Override
public boolean onTimeOut(int timingSeconds, int countDownIntervalSeconds) {
// Processing logic
// Connect to the server to check for upgrade

return false / true;
}
}

So far, Service only needs to hold this NormalUpgradeThread class object to control the background upgrade sub-thread, or start and stop the timer counter.

As for the mandatory upgrade mentioned earlier, its framework is basically the same as this one.

4. Prompt and trigger upgrade

In the function of connecting to the server to detect and upgrade, I use OkHttp directly here Open source framework to achieve.

As for the parsing and error handling of the communication transmission participation result, you have to deal with it yourself.

As for the function of prompting the new upgrade package, the author here uses the traditional way of prompting with a small red dot in the appropriate place.

When the user clicks on the corresponding small red dot, a prompt dialog box will pop up. After the user confirms it, the function of downloading a new upgrade package or directly opening the system installation program will be triggered.

As for whether to download or open the installation interface, it depends on whether the upgrade package software has been successfully downloaded in the designated download directory.

5. Download and installation

About the save location of the downloaded new upgrade package file, the author here uses The cache directory under the APK application installation directory, namely

context.getCacheDir();

In In Android, there are mainly the following five locations for APK storage files:

1, the cache directory under the program installation directory, that is, the /data directory corresponds to the package The cache directory under the name;

2, the files directory under the program installation directory is also the files directory under the corresponding package name in the /data directory;

3, sdcard directory;

4, the cache directory under the exclusive directory corresponding to the package name under the sdcard directory ;

5. The files directory under the exclusive directory corresponding to the package name in the sdcard directory;

The selection is the directory shown in item 1 above.

The reason for choosing this directory is to avoid the problem of being unable to read or write due to the different default policies of the mobile phone ROM's access to the sdcard permission of the APK. The above items 1 and 2 all have read and write permissions by default, and using them is the most compatible.

When the download and verification pass, the program will be automatically opened to install the application for installation. This function also has different implementation codes due to different system versions. The core code is as follows:

Intent intent = new< /span> Intent(Intent.ACTION_VIEW);

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|Intent.FLAG_ACTIVITY_NEW_TASK);
Uri uri
= FileProvider.getUriForFile(this, "com.unionman.locator.fileprovider", apk);
intent.setDataAndType(uri,
"application/vnd.android.package-archive");
}
else{
intent.setDataAndType(Uri.fromFile(apk),
"application/vnd.android.package-archive");
}
try {
startActivity(intent);
}
catch(Exception e){
e.printStackTrace();
}

For Android7.0 and above systems, it also requires some additional configuration. For these configurations, please refer to another blog post by the author: Android How to install APK via Java code?

6. Ordinary upgrade and mandatory upgrade

The procedures described above are all ordinary upgrade procedures.

But the so-called mandatory upgrade is basically the same as the normal upgrade. The only difference is that when a new version of the application is detected, it will be automatically downloaded in the background. Of course, you can actually choose whether to download automatically according to the network used by the user, for example, only when the user connects to the Internet via WIFI, to automatically download to avoid generating additional traffic charges for the user.

When the new version of the application is downloaded, the detection cycle will be shortened. When each detection cycle arrives, since it can detect that the currently downloaded installation package is detected, the application installation will be forced to open The program is for users to confirm the installation.

The purpose of shortening the detection cycle is to prevent users from refusing to install. When the user refuses to install, it will return to the application to continue using it. If the original detection cycle is still installed at this time, this mandatory upgrade can be considered as having no "mandatory effect". As long as the user does not want to upgrade, the user only needs to return every time the installation interface pops up to continue using the old version of the software. Therefore, we must shorten the detection cycle, and shorten it very short. For example, once every 5 seconds, the user will not be able to use it if they do not upgrade, and the purpose of mandatory upgrade can be achieved to a certain extent.

As for the mandatory upgrade code, we will not post it. Knowing the principle, it is very simple to implement.

private static class Timer extends CountDownTimer {


private int timing;
private int interval;

private OnTimeOutCallback callback;

Timer(
int timingSeconds, int intervalSeconds, OnTimeOutCallback callback) {
super(timingSeconds * 1000, intervalSeconds * 1000);
timing
= timingSeconds;
interval
= intervalSeconds;
this.callback = callback;
}

@Override
public void onTick(long l) {
Logger.d(TAG,
"onTick:" + l + "," + this);
}

@Override
public void onFinish() {
boolean ret = false;
if(callback != null) {
ret
= callback.onTimeOut(timing, interval);
}

if(!ret) {
start();
// Restart timing...
}
}

interface OnTimeOutCallback {
/**@return true means that the life cycle of the Timer has been processed, and the Timer should not restart the counting thread. . */
boolean onTimeOut(int timingSeconds, int countDownIntervalSeconds);
}

}
// class Timer - end.< /pre>

private class NormalUpgradeThread implements Timer.OnTimeOutCallback {


Timer timer;

private void start(){
// Start the timer according to actual business needs.
}

private void stop(){
// Stop the timer according to actual business needs
}

@Override
public boolean onTimeOut(int timingSeconds, int countDownIntervalSeconds) {
// Processing logic
// Connect to the server to check for upgrade

return false / true;
}
}

context.getCacheDir();

Intent intent = new Intent(Intent.ACTION_VIEW);

intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.N ){
intent.setFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION
|Intent.FLAG_ACTIVITY_NEW_TASK);
Uri uri
= FileProvider.getUriForFile(this, "com.unionman.locator.fileprovider", apk);
intent.setDataAndType(uri,
"application/vnd.android.package-archive");
}
else{
intent.setDataAndType(Uri.fromFile(apk),
"application/vnd.android.package-archive");
}
try {
startActivity(intent);
}
catch(Exception e){
e.printStackTrace();
}

Leave a Comment

Your email address will not be published.