Project introduction

XUpdate is a lightweight, highly available Android full version update framework.

XUpdate is a library for unified full version updates of Android across different project teams and platforms. It has the characteristics of lightweight, flexible, low coupling, high availability and so on, and can easily customize their own version updates.

The design was originally

Before XUpdate, Android version updates are basically implemented by writing various version update tool classes. What’s worse, sometimes the version updates are completely different between different project groups or platforms. As a result, countless version update tool classes will be written. And every time change a project team or platform will need to rewrite again, very troublesome. At that time, I wondered if it was possible to design a common base library, independent of business or platform, for version updates, which are basically available in Android applications and relatively stable in content.

Design ideas

Before writing XUpdate, I did a special search on Github for Android updates and found that AppUpdate had the most stars. However, when I looked at the source code, I found that it was not elegantly designed, and the internal coupling was very serious, but the advantage was that the Android version of the new features are mostly covered. So I redesigned it according to its features, combined with my understanding of the version update. If you are interested, please click to view the UML design of the framework.

To solve the pain points

  • Simple to use, just one line of code to complete the version update function.
  • Powerful, compatible with Android6.0, 7.0, 8.0, 9.0 and 10.0, support silent update and automatic update, support internationalization.
  • Strong scalability, you can customize request API interface, prompt popup, download service, file encryption, etc.
  • Simple setup, only need to provide JSON content to support version update.
  • By default, it provides background services, management interfaces, and plug-ins.

The project address

For your convenience, XUpdate provides a full version update solution.

  • Android Base library: github.com/xuexiangjys…
  • Version update background service: github.com/xuexiangjys…
  • Version update management system: github.com/xuexiangjys…
  • Flutter plugin: github.com/xuexiangjys…
  • React-native plugin: github.com/xuexiangjys…

Project presentations

Client-side effects

  • Default version Update

  • The background update

  • Forced version update

  • Version updates can be ignored

  • Custom prompt pop-up theme

  • Use the system popup prompt

Background Management Interface

  • The login page

  • Background Management Homepage

  • Adding application Versions

  • Application Version Change


The integration guide

Add Gradle dependencies

1. Add build.gradle repositories in the project root directory:

allprojects {
     repositories {
        ...
        maven { url "https://jitpack.io" }
    }
}
Copy the code

2. Add to dependencies:

Below is the version description, just choose one.

  • Androidx version: 2.0.0 and above
dependencies { ... / / androidx version implementation 'com. Making. Xuexiangjys: XUpdate: 2.0.2'}Copy the code
  • Support version: 1.1.6 or later
dependencies { ... / / support version implementation 'com. Making. Xuexiangjys: XUpdate: 1.1.6'}Copy the code

To initialize the SDK

Initial configuration in Application:

Note that IUpdateHttpService must be set, otherwise the framework will not work properly! IUpdateHttpService can be implemented in the Demo

Xupdate.get ().debug(true).isWifiOnly(true) // Default is not automatic mode, Can be used depending on the configuration. The param (" versionCode ", UpdateUtils getVersionCode (this)) / / set the default public request parameters. The param (" appKey ", GetPackageName ()). SetOnUpdateFailureListener (new OnUpdateFailureListener () {/ / set the version update error listening in @ Override public void onFailure(UpdateError error) { if (error.getCode() ! Toastutils.toast (error.tostring ())); SetIUpdateHttpService (new OKHttpUpdateHttpService()) // This must be set! Realize network request function. .init(this);Copy the code

[Note] : If any problem occurs, enable the debug mode to trace the problem. If you also need to log to disk, you can implement the following interfaces

XUpdate.get().setILogger(new ILogger() { @Override public void log(int priority, String tag, String message, Throwable t) {// Implement log function}});Copy the code

Confuse configuration

-keep class com.xuexiang.xupdate.entity.** { *; } // If you are using a custom Api parser, you will need to configure the obfuscation rule for your custom Api entities.  -keep class com.xuexiang.xupdatedemo.entity.** { *; }Copy the code

Based on using

Default version Update

Directly call the following code to complete the version update operation:

XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl)
        .update();
Copy the code

Note that with the default version update, the json format returned by the request server should include the following:

{"Code": 0, //0 means the request succeeded, non-0 means the request failed "Msg": "", // request error message "UpdateStatus": 1, //0 indicates no update, 1 indicates that the version is updated and the upgrade is not required.2 indicates that the version is updated and the upgrade is required. "VersionCode": 3, "VersionName": "1.0.2", "ModifyContent": "1. \r\n2, add use demo demo. \r\n3. Added custom update service API interface. \r\n4, optimize the update prompt interface. , "DownloadUrl" : "https://raw.githubusercontent.com/xuexiangjys/XUpdate/master/apk/xupdate_demo_1.0.2.apk", "ApkSize" : 2048 "ApkMd5": "..." // If there is no MD5 value, there is no guarantee that the APK is complete. }Copy the code

Automatic version update

Automatic version update: automatic version check + automatic APK download + automatic APK installation (silent installation). You only need to set isAutoMode(true), but if the device does not have root permission, it will not be able to do the full automatic update (because silent installation requires root permission). In addition, custom installation listeners may be required for some special devices to implement silent installation.

Xupdate.newbuild (getActivity()).updateurl (mUpdateUrl).isautomode (true)Copy the code

Support background update

After enabling background update, users can enter background update after clicking the “background update” button, without waiting in the update interface.

XUpdate.newBuild(getActivity())
        .updateUrl(mUpdateUrl)
        .supportBackgroundUpdate(true)
        .update();
Copy the code

Custom versions update theme styles

Customize theme styles by setting updated top image, theme color, button text color, width to height ratio, etc.

  • PromptThemeColor: Sets the theme color
  • PromptButtonTextColor: Sets the text color of the button
  • PromptTopResId: Sets the top background image
  • PromptWidthRatio: Specifies the ratio of screen width to screen width of the version update prompter. The default value is -1 and this parameter is not restricted
  • PromptHeightRatio: Specifies the ratio of the height of the version update prompter to the screen. The default value is -1 and this parameter is not restricted
XUpdate.newBuild(getActivity()) .updateUrl(mUpdateUrl) .promptThemeColor(ResUtils.getColor(R.color.update_theme_color)) PromptButtonTextColor (Color. WHITE). PromptTopResId (R.m ipmap. Bg_update_top). PromptWidthRatio (0.7 F), update ();Copy the code

Forced version update

If the user does not update, the application will not work properly.

  • If you are using the default version update return API, you only need the server to return the UpdateStatus field as 2.

  • If you customize the request return API, just set the mIsForce field of UpdateEntity to true.


Use the advanced

Version Updates the information entity

UpdateEntity acts as the communication medium for the interfaces in each part of the framework, and understanding their role is critical to customizing the interfaces that follow.

  • UpdateEntity Field property
The field name type The default value note
mHasUpdate boolean false Is there a new version
mIsForce boolean false Mandatory installation or not: App cannot be used without mandatory installation
mIsIgnorable boolean false Whether this version can be ignored
mVersionCode int 0 Latest version code
mVersionName String unknown_version Latest Version Name
mUpdateContent String “” Update the content
mDownloadEntity DownloadEntity / Download information entity
mIsSilent boolean false Silent download or Not: Download a new version without prompting
mIsAutoInstall boolean true Whether to automatically install the software after the download is complete
  • DownloadEntity field property
The field name type The default value note
mDownloadUrl String “” Download address
mCacheDir String “” Directory to download files
mMd5 String “” Md5 value of the downloaded file, used for verification, to prevent the downloaded APK file from being replaced (the latest demo has a tool to calculate the MD5 value)
mSize long 0 The size of the downloaded file
mIsShowNotification boolean false Whether to display the download progress on the notification bar
  • PromptEntity field attribute
The field name type The default value note
mThemeColor int R.color.xupdate_default_theme_color Theme colors (background colors for progress bars and buttons)
mTopResId int R.drawable.xupdate_bg_app_top Top background image resource ID
mButtonTextColor int 0 Button text color
mSupportBackgroundUpdate boolean false Whether background updates are supported
mWidthRatio float -1 (No constraint) The ratio of the width of the version update cue to the screen
mHeightRatio float -1 (No constraint) Version update tip height as a percentage of the screen

The structure

Now that we know the structure of the version update and the functionality of each part, we can customize it according to our actual needs. Here is the structure of the version update:

  • IUpdateChecker: Checks whether the latest version is available.

  • Version update IUpdateParser: Parses data returned by the server.

  • IUpdatePrompter: Displays the latest version information.

  • IUpdateDownloader: Downloads the latest APK installation package.

  • Network Request Service Interface IUpdateHttpService: Defines the interface for making network requests.

In addition, there are two listeners:

  • Version update failed listener OnUpdateFailureListener.

  • Version update apK installed listener OnInstallListener.

Update scheduling core:

  • Version Updates the business agentIUpdateProxy: Responsible for control of the version update process, call UPDATE to start the version update process.

Theoretically, all of the above components open up a custom API, and we just need to implement the corresponding interface according to our needs to complete the customization.

Custom versions update the parser

If you don’t want to update the returned interface data with the default version, you can customize the parser by implementing the IUpdateParser interface, as shown in the following example:

Xupdate.newbuild (getActivity()).updateurl (mUpdateUrl3).updateparser (new CustomUpdateParser()) // sets a custom version updateParser .update(); public class CustomUpdateParser implements IUpdateParser { @Override public UpdateEntity parseJson(String json) throws Exception { CustomResult result = JsonUtil.fromJson(json, CustomResult.class); if (result ! = null) { return new UpdateEntity() .setHasUpdate(result.hasUpdate) .setIsIgnorable(result.isIgnorable) .setVersionCode(result.versionCode) .setVersionName(result.versionName) .setUpdateContent(result.updateLog) .setDownloadUrl(result.apkUrl) .setSize(result.apkSize); } return null; }}Copy the code

Custom version update inspector + version update parser + version update hint

  • The IUpdateChecker interface can be used to customize the checker.

  • The IUpdateParser interface can be used to customize the parser.

  • You can customize the prompter by implementing the IUpdatePrompter interface.

XUpdate.newBuild(getActivity()) .updateUrl(mUpdateUrl3) .updateChecker(new DefaultUpdateChecker() { @Override public void onBeforeCheck() { super.onBeforeCheck(); CProgressDialogUtils. ShowProgressDialog (getActivity (), "in the query..." ); } @Override public void onAfterCheck() { super.onAfterCheck(); CProgressDialogUtils.cancelProgressDialog(getActivity()); } }) .updateParser(new CustomUpdateParser()) .updatePrompter(new CustomUpdatePrompter(getActivity())) .update(); public class CustomUpdatePrompter implements IUpdatePrompter { private Context mContext; public CustomUpdatePrompter(Context context) { mContext = context; } @Override public void showPrompt(@NonNull UpdateEntity updateEntity, @NonNull IUpdateProxy updateProxy, @NonNull PromptEntity promptEntity) { showUpdatePrompt(updateEntity, updateProxy); } /** * Display custom prompt ** @param updateEntity * @param updateProxy */ private void showUpdatePrompt(final @nonNULL) UpdateEntity updateEntity, final @NonNull IUpdateProxy updateProxy) { String updateInfo = UpdateUtils.getDisplayUpdateInfo(mContext, updateEntity);  New Alertdialog.builder (mContext).setTitle(string. format(" Upgrade to %s version? , updateEntity getVersionName ())). SetMessage (updateInfo). SetPositiveButton (" upgrade ", new DialogInterface.OnClickListener() { @Override public void onClick(DialogInterface dialog, int which) { updateProxy.startDownload(updateEntity, new OnFileDownloadListener() { @Override public void onStart() { HProgressDialogUtils. ShowHorizontalProgressDialog (mContext, "progress", false); } @Override public void onProgress(float progress, long total) { HProgressDialogUtils.setProgress(Math.round(progress * 100)); } @Override public boolean onCompleted(File file) { HProgressDialogUtils.cancel(); return true; } @Override public void onError(Throwable throwable) { HProgressDialogUtils.cancel(); }}); }}).setnegativeButton (" Not yet upgraded ", null).setCancelable(false).create().show(); }Copy the code

Custom file encryption verifier

If you do not want to use MD5 encryption, you can also customize IFileEncryptor. The following is an implementation of the MD5 file encryptor:

/** * public class DefaultFileEncryptor implements MD5 encryption ** @author xuexiang * @since 2019-09-06 14:21 */ IFileEncryptor {/** * encryptor ** @param file * @return */ @override public String encryptFile(file file) {return Md5Utils.getFileMD5(file); } /** ** verify that the file is valid (encryption is consistent) ** @param encrypt encrypted value, if encrypt is empty, * @param file File to be verified * @return File Whether the file is valid */ @Override public Boolean isFileValid(String encrypt, File file) { return TextUtils.isEmpty(encrypt) || encrypt.equalsIgnoreCase(encryptFile(file)); }}Copy the code

Finally, call xupdate.get ().setiFileencryptor to set the encryptor.

Use only XUpdate’s downloader functionality for apK downloads

XUpdate. NewBuild (getActivity ()). ApkCacheDir (PathUtils. GetExtDownloadsPath ()) / / set the root directory of the download cache. The build () .download(mDownloadUrl, New OnFileDownloadListener() {// Set the download address and download listener @override public void onStart() { HProgressDialogUtils. ShowHorizontalProgressDialog (getContext (), "progress", false); } @Override public void onProgress(float progress, long total) { HProgressDialogUtils.setProgress(Math.round(progress * 100)); } @Override public boolean onCompleted(File file) { HProgressDialogUtils.cancel(); Toastutils.toast ("apk completed, file path: "+ file.getPath()); return false; } @Override public void onError(Throwable throwable) { HProgressDialogUtils.cancel(); }});Copy the code

Use XUpdate’s APK installation only

_XUpdate.startInstallApk(getContext(), FileUtils.getFileByPath(PathUtils.getFilePathByUri(getContext(), data.getData()))); // Enter the file pathCopy the code

If your APK installation is different, you can implement your own APK installer. You only need to implement OnInstallListener interfaces, and through the XUpdate. SetOnInstallListener set to take effect.


Q&A

Access problems

1. Q: Why do I keep reporting errors when I first access the systemupdateHttpService == null?

A: You need to read the access documentation carefully, you must initialize XUpdate as required in the Application, and the IUpdateHttpService must be set, unless you customize the version checker and version update downloader, the framework will not work properly!

2. Q: Why can I display the latest version prompt when I am developing and debugging, but the typed package does not respond?

A: This problem is generally less obfuscated configuration. If you are using a custom version update parser, obfuscate your interface entities.

3. Q: Why can THE file be downloaded after I click download, but the progress bar is not updated, or the value of the progress bar is -1?

Answer: this kind of circumstance can be checked from two aspects.

  • If you print a progress bar with a value of -1, it is likely that the download service provided by the server itself does not support progress. Because if you are asking the server to download the file, the server does not return the data Length in the request header, that is, contentLength (content-Length is not set, is unknown, then it is impossible to progress. You can grab packets to see if “Content-Length” is set in the response header.

  • If you are using a server that is already confirmed to support progress. You may need to consider whether there is a problem with the download interface of your IUpdateHttpService. You must ensure that the onProgress method of the DownloadCallback interface executes properly.

4. Q: Why do I execute the version update method, but it keeps telling me that there is no latest version or version update is in progress?

A: The first thing you need to know about this question is what you’re using to determine if you have the latest version. Whether to use VersionCode or VersionName depends on the scenario you are actually using. Once you know this, you can use the logs to determine whether there is a front-end problem or a back-end problem.

5. Q: I have already downloaded the latest version, but have not installed it. Why should I download it again the next time I check for a version update?

A: This only means that your backend does not return the MD5 value of the latest version of the file when it returns version information, or that you did not set it. If you set the MD5 value, then is the MD5 value you set and file calculated MD5 value do not match, this kind of circumstance, you of the APK is highly may have been tampered with (in this case, of course, you also can’t normal installation), or are you the before and after the MD5 value calculation algorithms (usually there is no this kind of situation).

6. Q: Why did my latest app download but clickThe installationUpdate failed after the button?

A: There are many different situations in which this can happen.

  • First, you need to make sure that you can find the latest APK. If you set the MD5 value, you also need to check whether the MD5 value calculated by the latest APK is consistent with the MD5 value returned by the background interface (there is a corresponding method in Demo for calculating the MD5 value of the file).
  • Secondly, you need to manually install APK to ensure that the APK file is ok (consistent signature, complete file) and can be installed normally.
  • Finally, try it on multiple devices to make sure it’s not the device itself.
  • If none of this solves the problem, unfortunately, you’ll have to customize the install listenerOnInstallListenerInterface to achieve the correct installation of APK method.

7. Q: An error occurs during the version update. How do I troubleshoot it?

A: The best solution is of course to break the point of investigation one by one! Call xupdate.get ().debug(true) to enable the debug mode and print relevant logs to identify the error location. In this way, the problem can be solved faster.

8. Q: Why does the version update popup not pop upSystem.err: at com.xuexiang.xupdate.widget.BaseDialog.init(BaseDialog.java:72) The mistake?

A: The best way to do this is to pass in a context that uses AppCompatActivity, not an Activity or FragmentActivity! If you must use Activity or FragmentActivity, set its Theme to Theme of type.AppCompat.

Custom questions

There is often feedback from users that they do not know how to customize the interface (facing a bunch of interfaces, do not know how to start), and carry out personalized customization to meet the requirements of the version update implementation. I will list the problems and solutions one by one below.

1. Q: I use retrofit custom interfaces and don’t want to use themIUpdateHttpServiceWhat should I do with the set of generic requests to query the latest version?

A: You can customize the IUpdateChecker version checker, which is responsible for querying the existence of the latest version. Refer to the custom version update checker provided by the framework by default.

2. Q: I do not want to use the json format that the framework default requests the server to return, because the company’s back end has its own data return format. What should I do?

A: You can customize the IUpdateParser, which parses the data returned by the server and builds UpdateEntity. For details, see the custom version update parser or the version update parser provided by the framework by default.

3. Q: I think the default version update prompt provided by the framework does not fit our company’s UI style. Can I customize my own version update prompt?

A: You can customize IUpdatePrompter, which displays the latest version information. For details, you can refer to the custom version update hint or the version update hint provided by the framework by default.

4. Q: I always feel that the latest version of APK download service provided in the framework is not fast, I want to implement my own download service, and make relevant download progress prompt, ok?

A: you can customize the IUpdateDownloader, which is responsible for downloading the latest APK installation package. You can customize the version update loader provided by the framework by default.

5. Q: There is something special about my application and common application, and I cannot use the system’s installation API to install the program. What should I do?

A: If your APK installation is different, you can implement your own APK installer. You only need to implement OnInstallListener interfaces, and through the XUpdate. SetOnInstallListener set to take effect.

[Note] The above implementation of the custom interface, XUpdate can be global and local Settings.

Error code

Error code note
2000 Query update failed
2001 There is no wifi
2002 There is no Internet
2003 Version update in progress
2004 None Latest version
2005 Version check returns null
2006 The version check returned JSON parsing failure
2007 The version that has been ignored
2008 The cache directory for application downloads is empty
3000 Version prompt exception error
3001 The Activity page where the version cue is located is destroyed
4000 Failed to download the new application installation package. Procedure
4001 Failed to apply for read and write permission. Procedure
5000 Apk installation failed. Procedure
5100 An unknown error

Links to resources

  • Android Base library: github.com/xuexiangjys…
  • Version update background service: github.com/xuexiangjys…
  • Version update management system: github.com/xuexiangjys…
  • Flutter plugin: github.com/xuexiangjys…
  • React-native plugin: github.com/xuexiangjys…

Wechat official account

For more information, please scan and follow my personal wechat public number: [My Android open Source Journey]