One, a brief introduction

In the previous article “Hot Fix — Tinker Integration and Use”, Tinker was integrated according to the official Tinker Wiki, but that was only local integration. There was one important problem that was not solved, which was that patches were delivered from the server to the user’s phone. If you had a good backend developer on your team, So you can do a patch management system, but I think there should not be many people willing to spend energy on the development of this background management system, and the development is sometimes in the bug, the ghost knows will dig out a big pit? For such a problem, as far as I know, there are three Tinker patch management systems on the market, as follows:

  • Bugly: Thermal repair
  • Making: tinker – manager
  • Tinkerpatch (Android Hot Update service)

“Bugly” and “Tinker-Manager” are free, “TinkerPatch” is charged, because “TinkerPatch” charges, so not considered for the time being. Bugly is developed and maintained by the Tencent team, so there is no guarantee of stability, while “Tinker-Manager” is developed and maintained by individual developers on GitHub, so there is no guarantee of stability (I do not mean to disparage developers, after all, there is strength in numbers.), so I think, Bugly is currently the best Tinker thermal repair solution. Before starting the Bugly integration, you can download the Demo and try it out.

2. Obtain the App ID

To use Bugly’s hotfix feature, you must first sign up and log in to Bugly, then click on the “Bugly Products page” or click on “My Products.”

My account has never created a product before, so there is nothing here, and then click “New Product”.

After filling in the necessary information, click “Save”.

Click “Product Settings” to select the newly created product (step 3 in the figure) and view the corresponding App ID of the product.

This App ID is very important. Record it first and use it later.

The App ID of Demo is 3062edb401. Don’t use mine, it’s useless to you, please use your own product’s App ID.

Add plugin dependencies

Build. Gradle for this project:

Dependencies {classpath 'com. Android. View the build: gradle: 3.0.0' / / tinkersupport plug-in (1.0.3 above no configuration tinker plug-in) classpath "Com. Tencent. Bugly: tinker - support: 1.1.1"}Copy the code

3. Integrate SDK

The build of the app. Gradle:

apply from: 'tinker-support.gradle' android { defaultConfig { ... // Enable multidex multiDexEnabled true} // Recommend dexOptions {jumboMode = true} // signingConfigs {release {try  { storeFile file("./keystore/release.keystore") storePassword "testres" keyAlias "testres" keyPassword "testres" } catch (ex) { throw new InvalidUserDataException(ex.toString()) } } debug { storeFile file("./keystore/debug.keystore") } } // Build type buildTypes {release {minifyEnabled true signingConfig SigningConfigs. release proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } debug { debuggable true minifyEnabled false signingConfig signingConfigs.debug } } sourceSets { main { jniLibs.srcDirs = ['libs'] } } } dependencies { ... Implementation "com. Android. Support: multidex:" 1.0.1 / / dex configuration implementation 'com. Tencent. Bugly: crashreport_upgrade: 1.3.4' / / remote warehouse integration method (recommended)}Copy the code

Please modify the signature configuration according to the actual situation of your project, for example:

4. Configure Tinker

Gradle = tinker-support.gradle = tinker-support.gradle = tinker-support.gradle = tinker-support.gradle = tinker-support.gradle

apply plugin: Def bakPath = file("${buildDir}/bakApk/") /** * def baseApkDir = "Tinker - bugly - 1211-16-01-34" def myTinkerId = "base -" + rootProject. Ext. Android versionName / / is used to generate the benchmark package (without modification) / / def MyTinkerId = "patch -" + rootProject. Ext. Android versionName + ". 0.0 "/ / used to generate the patches (every generation patches to change once, Patch -${versionName}.x.x) /** * For details about the parameters of tinkerSupport, please refer to */ tinkerSupport {// Enable tinker-support. Default value true enable = true // Whether to enable the hardening mode, The default value is false.(supported starting from Tinker-spport 1.0.7) // isProtectedApp = true // Whether to enable reflection Application mode enableProxyApplication = true // Whether new non-export activities are supported (note: AndroidManifest file supportHotplugComponent = true Default Value Subdirectory of the current Module tinker autoBackupApkDir = "${bakPath}" // Whether to enable the function of overwriting tinkerPatch configurations. The default value is false. Without adding tinkerPatch overrideTinkerPatchConfiguration = true / / compile patches, must specify the baseline version of the apk, the default value is empty if / / is empty, // @{link tinkerpatch. oldApk} baseApk = "${bakPath}/${baseApkDir}/app-release.apk" // ApplyMapping baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt" // ApplyResourceMapping baseApkResourceMapping = "${bakPath}/${baseApkDir}/ app-release-r.xt "// Both base and fix packages are built with different Tinkerids, TinkerId = "${myTinkerId}" // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"} ** * Generally speaking, we do not need to make any changes to the following parameters * Please refer to: * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97 */ tinkerPatch { ... }Copy the code

1, overrideTinkerPatchConfiguration

When overrideTinkerPatchConfiguration = true, tinkerPatch can omit don’t write, Bugly loads the default Tinker configuration. Please note that if your so files are not stored in the libs directory (the same as the SRC directory), or if your resource files are stored in your custom directory, then you should be careful that these files will not be detected during the process of making the patch package. This means that the so files and resource files will not be hot repaired. This situation requires the overrideTinkerPatchConfiguration = false, and set the tinkerPatch lib and res properties.

For other configurations and instructions, see Tinker- Access Guide.

2, baseApkDir

BaseApkDir is the directory of the base package (also called the base package). During patch production, you need to change the name of the base package folder in bakApk, for example, bakApk/ XXXX. When the patch package is generated, change the value of baseApkDir to XXXX. (XXXX is automatically generated by Tinker and named according to the timestamp).

3, tinkerId

TinkerId is the most important factor of Bugly hot fix. It is generally valued as git version number, versionName, etc. (I am used to versionName). It will generate the corresponding relationship between the patch package and the base package, assuming that the base package tinkerId is base-1.0. The relationship between the yapatch. MF file in the generated patch package is as follows:

Bugly requires that the tinkerId of the baseApk be different from the tinkerId of the patch pack. So, when generating the base pack, use the following tinkerId:

Def myTinkerId = "base -" + rootProject. Ext. Android versionName / / is used to generate the benchmark package (without modification)Copy the code

When generating a patch pack, use the following tinkerId:

Def myTinkerId = "patch -" + rootProject. Ext. Android versionName + ". 0.0 "/ / used to generate the patches (every generation patches to change once, Patch -${versionName}.x.x)Copy the code

For the same base pack, we may generate patch packs for several times and upload them to Bugly’s hotfix management background. At this time, the TinkerIDS of these patch packs are also different. Otherwise, when the App on the customer’s mobile phone obtains patches, it will be confused (for personal test, when the tinkerIDS of the patch packs of the same base pack are the same, Each time the App is restarted, it will obtain a different patch package, causing the same tinkerId patch package to be delivered in turn. So, patch “-” + rootProject. Ext android. VersionName + “0.0” in the “0.0” (called count) is to distinguish each generated patches, such as., 0.1, 0.2, etc., It is recommended that the count be reset when versionName is updated.

Gradle is not in the same file as your app’s build.gradle. So can’t through the android. DefaultConfig. Directly obtained versionName App versionName, here I use the config. Gradle to extract the common properties, RootProject. Ext. Android. VersionName access is config. Gradle versionName attribute, in details, please baidu.

4, patch old and new judgment

Def myTinkerId = "patch -" + rootProject. Ext. Android versionName + ". 0.0 "/ / used to generate the patches (every generation patches to change once, Patch -${versionName}.x.x)Copy the code

For a base pack, multiple patches can be released on Bugly (remember that tinkerID is different). This may lead you to think that a higher count means a newer patch. This is not true. The last patch uploaded is the latest patch. For example, I uploaded patch 1 with tinkerID “patch-1.0.0.9” yesterday and patch 2 with TinkerID “patch-1.0.0.1” today. Although the count of patch 2 is smaller than patch 1, patch 2 is transmitted later than patch 1. So patch 2 is the latest patch, meaning patches old and new have nothing to do with counting. Bugly will issue and apply the latest patch (patch 2), but it is recommended to count from small to large. This is just how Bugly determines whether a patch is old or new.

Initialize the SDK

Bugly initialization needs to be done in the Application, but for native Tinker, the default Application is not hot fix. For those of you who have read the official Tinker Wiki, Tinker offers developers two options for applications that cannot be hotfixed:

  • Use inherit TinkerApplication + DefaultApplicationLike.
  • Use the “DefaultLifeCycle Annotation + DefaultApplicationLike”.

Both of these two choices require modification of the custom Application, which is acceptable if there is not much custom Application code. However, some cases are “annoying” with these two choices. For this, Bugly provides two solutions as follows:

  • Using the original custom Application, Bugly dynamically generates new applications for the App through reflection.
  • Use inherit TinkerApplication + DefaultApplicationLike.

The DefaultLifeCycle annotation was neutered in Bugly.

Corresponding to the value of enableProxyApplication in the tinker-support.gradle file: true or false.

1, enableProxyApplication = true

Bugly will dynamically generate a new Application for the custom Application in the project by reflection. The following is the androidmanifest.xml in source code and the compiled Androidmanifest.xml in APK:

Now that the value of enableProxyApplication is set to true, it’s time to initialize the Bugly. Bugly needs to be configured in the custom Application onCreate() and installed in attachBaseContext() :

public class MyApplication extends Application { private Context mContext; @Override public void onCreate() { super.onCreate(); mContext = getApplicationContext(); // For debugging, change the third parameter to true configTinker(); // For debugging, change the third parameter to true configTinker(); } @Override protected void attachBaseContext(Context base) { super.attachBaseContext(base); // you must install multiDex whatever tinker is installed! MultiDex.install(mContext); // Install tinker. // This interface is only used to reflect Application access. Beta.installTinker(); }}Copy the code

Note:

  1. The Bugly installation must be in the attachBaseContext() method, otherwise the latest patches will not be available from the Bugly server.
  2. Open MultiDex tinker, need you, you need to configure the compile in the dependencies “com. Android. Support: MultiDex:” 1.0.1 MultiDex may only be used. The install method.

Finally, in the manifest file, declare to use our custom Application:

<application android:name="com.lqr.MyApplication" ... >Copy the code

EnableProxyApplication = false

This is the method recommended by Bugly, which is guaranteed to be stable (since the first method uses reflection and may be unstable). It requires modification of Application, first by inheriting TinkerApplication, and then in the default constructor, Change the second parameter to the fully qualified name of the ApplicationLike inherited class in your project:

public class SampleApplication extends TinkerApplication { public SampleApplication() { super(ShareConstants.TINKER_ENABLE_ALL, "com.lqr.SampleApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false); }}Copy the code

Note: This class integrates with the TinkerApplication class. No operations are done inside the TinkerApplication class. All Application code is placed in the ApplicationLike inherited class. TinkerFlags Indicates the types supported by Tinker: dex Only, Library Only or all suuport, default: TINKER_ENABLE_ALL parameter 2: delegateClassName Application Proxy 4: tinkerLoadVerifyFlag Whether to enable md5 authentication for dex or lib. The default value is false

Next, create the ApplicationLike inherited class:

public class SampleApplicationLike extends DefaultApplicationLike { public static final String TAG = "Tinker.SampleApplicationLike"; private Application mContext; public SampleApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) { super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent); } @Override public void onCreate() { super.onCreate(); mContext = getApplication(); configTinker(); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) @Override public void onBaseContextAttached(Context base) { super.onBaseContextAttached(base); // you must install multiDex whatever tinker is installed! MultiDex.install(base); // installTinker beta.installtinker (this); } @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH) public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) { getApplication().registerActivityLifecycleCallbacks(callbacks); } @Override public void onTerminate() { super.onTerminate(); Beta.unInit(); }}Copy the code

Note: The SampleApplicationLike class is the proxy class for Application. All previous implementations of Application must be copied to this class. Call beta.installtinker (this) in onBaseContextAttached.

Finally, in the manifest file, declare the modified Application (not ApplicationLike) :

<application android:name="com.lqr.SampleApplication" ... >Copy the code

3. Configure Bugly

This is the official configuration provided by Bugly, and the notes are very nice. Please take a look carefully, which is helpful for the function expansion and user experience of the project:

Private void configTinker() {// Sets whether to enable hot update. The default value is true beta. enableHotfix = true; / / Settings are automatically download patches, the default is true Beta. CanAutoDownloadPatch = true; CanAutoPatch = true; canAutoPatch = true; / / set whether to prompt the user to restart, the default is false Beta. CanNotifyUserRestart = true; BetaPatchListener = new betaPatchListener () {@override public void onPatchReceived(String patchFile) { Toast.maketext (mContext, "patch download address" + patchFile, toast.length_short).show(); } @Override public void onDownloadReceived(long savedLength, long totalLength) { Toast.makeText(mContext, String.format(Locale.getDefault(), "%s %d%%", Beta.strNotificationDownloading, (int) (totalLength == 0 ? 0 : savedLength * 100 / totalLength)), Toast.LENGTH_SHORT).show(); } @override public void onDownloadSuccess(String MSG) {toast.maketext (mContext, "download successful ", toast.length_short).show(); } @override public void onDownloadFailure(String MSG) {toast.maketext (mContext, "download failed ", toast.length_short).show(); } @override public void onApplySuccess(String MSG) {toast.maketext (mContext, "update successful ", toast.length_short).show(); } @override public void onApplyFailure(String MSG) {toast.maketext (mContext, "fix failed ", toast.length_short).show(); } @Override public void onPatchRollback() { } }; / / set up the development equipment, the default value is false, the upload patch if the range is specified for the issuance of the "development" equipment, you need to call this interface to identify development equipment Bugly. SetIsDevelopmentDevice (mContext, false); / / multi-channel requirement into / / the String channel. = WalleChannelReader getChannel (getApplication ()); // Bugly.setAppChannel(getApplication(), channel); Init (mContext, "e9d0b7f57f", true); }Copy the code

Here we use the App ID we got at the beginning, passing it to the second argument to the bugly.init () method, remember, using your own App ID.

The following two methods are important:

  • Bugly.setIsDevelopmentDevice()

Set whether the current device is a development device, depending on the “delivery range” selected by Bugly when uploading the patch package.

  • Bugly.init(context, appid, isDebug)

In addition to setting the App ID, this method can also set whether to output the Log, so that you can observe what Bugly does when the App starts.

Six, AndroidManifest. XML

1. Permission configuration

<uses-permission android:name="android.permission.READ_PHONE_STATE"/>
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE"/>
<uses-permission android:name="android.permission.READ_LOGS"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
Copy the code

2. Activity configuration

<activity
    android:name="com.tencent.bugly.beta.ui.BetaActivity"
    android:configChanges="keyboardHidden|orientation|screenSize|locale"
    android:theme="@android:style/Theme.Translucent"/>
Copy the code

3. Configure FileProvider

<provider
    android:name="android.support.v4.content.FileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"/>
</provider>
Copy the code

If you are using a third-party library with the same FileProvider configured, you can resolve merge conflicts by inheriting the FileProvider class, as shown in the following example:

<provider
    android:name=".utils.BuglyFileProvider"
    android:authorities="${applicationId}.fileProvider"
    android:exported="false"
    android:grantUriPermissions="true"
    tools:replace="name,authorities,exported,grantUriPermissions">
    <meta-data
        android:name="android.support.FILE_PROVIDER_PATHS"
        android:resource="@xml/provider_paths"
        tools:replace="name,resource"/>
</provider>
Copy the code

4. Upgrade the SDK download path configuration

Create a new XML folder in the RES directory and create the provider_Paths.xml file as follows:

<? The XML version = "1.0" encoding = "utf-8"? > <paths xmlns:android="http://schemas.android.com/apk/res/android"> <! -- /storage/emulated/0/Download/${applicationId}/.beta/apk--> <external-path name="beta_external_path" path="Download/"/> <! --/storage/emulated/0/Android/data/${applicationId}/files/apk/--> <external-path name="beta_external_files_path" path="Android/data/"/> </paths>Copy the code

Note: 1.3.1 and above, you can skip the above configuration, aar has been configured in AndroidManifest, and contains the corresponding resource file.

Seven, confused

** -keep public class com.tencent. Bugly.**{*; } # keep class android.support.**{*; }Copy the code

Now that the integration is complete, it’s time to create the base pack, patch pack, and upload the patch pack.

8. Make a base pack

After the app coding is completed and the test is completed, it is packaged and launched. The package played before the launch is the benchmark package. Here we will make the benchmark package, divided into 3 steps:

  1. Open the tinker-support.gradle file in app.
  2. Uncomment the tinkerId with “base” and comment out the tinkerId with “patch”.
  3. Double-click assembleRelease under build.

Usually the name of the main Module is “app”, but my Demo is “Tinker-bugly”, so when you go to step 3, find the main Module to make the base package based on your project.

When you run the assembleRelease command, you compile the base package. When you finish compiling, the base package folder is automatically generated in the build directory of the app, named with a timestamp. Each execution of the assembleRelease directive creates a different base package folder in the build directory.

All you need to do is save these 3 files to a cloud disk, Git server, etc., but don’t leave them there, because when you do clean Project, the build directory of your app will be deleted. The base pack and the Mapping and R files will be lost.

At this point, you can release it (base pack: Tinker-Bugly-release.apk) to the app market. Try the Demo:

Tip: Harden and multichannel packaging

This article does not cover specific reinforcement and multi-channel packaging.

1, the reinforcement

Gradle file: tinker-support.gradle file: tinker-support.gradle file: tinker-support.gradle file: Tinker-support. gradle file: Tinker-support. gradle file: isProtectedApp = true

See the “Bugly Hot Update Usage Example Documentation last: Hardened Packaging” section for details.

2. Multi-channel packaging

Gradle Options is a productFlavors solution and a Multi-channel Package (recommended).

See the “Bugly Hot Update Usage Sample Documentation: Multi-channel Packaging” section for more details.

Ix. Patch packs

Now we need to repair the App dynamically. For the code repair, so library repair, resource file repair, respectively corresponding to the Demo “say something”, “get String from. So “, “my avatar”, the repair process is nothing more than to change the code, replace the so file, replace the resource file, I will not demonstrate here. To make the patch, open the tinker-support.gradle file.

1. Base package naming

Ensure that the base package and related files are named the same as in the configuration file:

2. Modify baseApkDir and tinkerId

  1. Change the value of baseApkDir to the names of all folders in the base package.
  2. Comment out the tinkerId with “base” and uncomment the tinkerId with “patch” (when generating patches multiple times, remember to modify the “count” to distinguish between different patches).

3. Perform compilation and generate patches

Open the Gradle TAB on the side, locate the main Module of your project, and double-click the buildTinkerPatchRelease directive under Tinker-support to generate the patch package.

After compiling, the file “patch_singed_7zip.apk” will be found in the build/outputs/patch directory of the app, which is the patch package. Double-click to open it, you can see that there is a yapatch.mf in it. It records the tinkerId of the base pack and the patch pack (they must be different, if they are the same, there is a configuration problem).

10. Upload the patch package

1. Process diagram

First, click on the “Bugly Products page” or click on “My Products” to view my products.

After clicking the product you want to manage, click “Application upgrade” and “hot update” successively to check the patch distribution of the product (I have not uploaded the patch for this product, so it is blank).

To upload the patch package, follow the following sequence:

2. Upload failure analysis

It is possible that when you upload the patch pack, the page will prompt “No App version for the patch pack can be matched, please confirm whether the baseline version of the patch pack has been released”.

Calm down for a moment, but let’s get one thing straight: How does Bugly know if the baseline version has been released?

It’s not like the app market is going to tell Bugly that something has been released, is it? When the base pack is launched on the phone, the Bugly framework will let the App connect to the network to notify the Bugly server and upload the current App version number, tinkerId and other information. It does this for the following two purposes:

  • The base pack that marks a tinkerId has been installed for use on the phone (i.e., published).
  • Get the latest patch information of the tinkerId base package.

So when a “did not match to apply patches App version, please confirm whether the baseline version of the patch package has been released” such tips can be determined, the benchmark information such as the tinkerId package has not been uploaded to the Bugly server, to this, I will tread to sum up, the pit for their solution, in the following steps:

  • Check whether the App is connected to the Internet.
  • Check whether the App ID is correct.
  • Check whether the Application declared in androidmanifest.xml is written correctly, combining the value of enableProxyApplication.
  • Check whether the installation of Bugly is done in the attachBaseContext() or onBaseContextAttached() method.

I’ve made the mistake of declaring a TinkerApplication descendant class in androidmanifest. XML when you set enableProxyApplication = true in the tinker-support.gradle file.

So here we just need to declare our custom Application in androidmanifest.xml (MyApplication).

In addition to networking issues, there are several cases where the base pack needs to be regenerated. Here’s another quick way to determine if your App has uploaded version information:

3. The upload is successful

Verify the above method first. After I solved the problem, I installed the regenerated benchmark package on the mobile phone and opened it (at this time, the Bugly framework uploaded the version number and tinkerId of App to the server). Then I checked “version management”, and the version number was “1.0” (actually the versionName of App).

Going back to uploading patches, what would be different this time?

Yeah, success. Click “Deliver now” to see that the patch is now in the “Deliver” state:

Take a look at the reaction of the App (it may take a while for the patch to actually be delivered to the user’s phone, not immediately) :

Back to the patch delivery on the Bugly server:

Xi. Miscellaneous

1. Patch management

In addition to uploading and delivering patches, Bugly server can also manage patches:

  1. Stop delivery: The patch will not be delivered to the customer’s mobile phone (the patch can be restarted after being stopped).
  2. Backtrack: Remove a patch from the Bugly server, this operation is irreversible (it is not known if the patch successfully installed on the user’s phone will also be removed).
  3. Edit: you can modify the “distribution range” (development equipment, full equipment, remarks, etc.).
  4. History: View modification records.

2. Highlight some areas that need attention

  1. A base pack can have multiple patches. Bugly will issue the latest patches (the old patches will become “stopped” by default), and the old patches of the App on the customer’s phone will be overwritten by the new patches.
  2. To make the base package, use the tinkerId with “base” and the assembleRelease directive.
  3. After making the basic package, be sure to save baseApk, mapping.txt, and R.txt. Do not lose them.
  4. To create a patch package, change the value of baseApkDir to the names of all the folders in the base package, then enable tinkerId with “patch” and change the “count”, and execute the buildTinkerPatchRelease directive.
  5. After making the patch pack, finally open it and check the from and to information in the yapatch. MF file to check whether the tinkerId of the base pack corresponding to the patch pack is correct.
  6. Init (mContext, AppID, false); bugly.init (mContext, AppID, false);
  7. If is to test whether the patches with the effect, it is recommended that the setting for the development of equipment: Bugly. SetIsDevelopmentDevice (mContext, true);
  8. So files need to be manually. But first, let me call TinkerLoadLibrary installNavitveLibraryABI (this, CPU_ABI) method to take effect.

3. Official Documents of Bugly

  • Bugly Android Hot Update Guide
  • Bugly Android Hot Update details
  • Bugly Android Hot Update FAQ
  • Hot update API interface
  • Bugly multi-channel hot update solution

4. Links to this series of articles

  • Thermal repair — Simple principle and realization
  • Hotfix — Integration and use of Tinker
  • Hot Fix — Bugly makes hot fix so easy

Post the Demo link at the end

Github.com/GitLqr/HotF…