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 agent
IUpdateProxy
: 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 installation
Update 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 listener
OnInstallListener
Interface 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 themIUpdateHttpService
What 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]