It’s been almost a year since the first Sophix was introduced, but there aren’t many practical applications. Here are some reasons:

1. Due to my carelessness at the beginning, there was a bug in the logic I wrote, so that only the first startup could request the hot repair loading interface, so I missed a rare opportunity to experience;

2. In the past half year, the demand for introducing hot repair projects is very small, so we need to apply it to projects with more frequent demand;

3. In the beginning, the logic is very simple, that is, the interface request is based on the time limit when starting. The actual use may require the user to kill the process for many times, so the effect is difficult to reflect;

Logic optimization

1. Original time limit -> Time limit based on version, version update, time reset recalculation; (One of the charging standards of Alige repair is the number of interface requests, so it must be limited)

2. The original trigger condition is only [startup] -> The new trigger condition is [Cut to the background and the current page of APP is the home page]; (Ensure that the current page of the APP is the home page) to avoid the user’s ongoing operations being interrupted)

3. Automatic killing logic is added. When the download of the new patch package is complete, the application is cut to the background and the current page of the APP is the home page, the process will be killed, and the patch package will be loaded when the user starts next time, and the bug will be fixed;

4. In addition to [BuilType], add [Android system version], [mobile phone manufacturer], [mobile phone model] and other three tags, convenient management.

The technical details

Get the DEBUG status from ApplicationInfo

When we initialize the hotfix, we need to tag it with different tags so that we can do grayscale publishing flexibly,

However, initialized classes are not allowed to reference any non-Android SDK classes, and yes, even BuildConfig is not allowed,

You have to maintain an additional DEBUG variable, which is not safe;

Androidmanifest.xml has a different configuration under the Application TAB in the Release and debug versions.

There is another way to get DEBUG status directly from applicationInfo:

boolean isDebug = getApplicationInfo() ! = null && (getApplicationInfo().flags & ApplicationInfo.FLAG_DEBUGGABLE) ! = 0;Copy the code
<! < Application Android: Theme ="@ref/0x7f0f000f"
        android:label="@ref/0x7f0e0071"
        android:icon="@ref/0x7f0c00bb"
        android:name="cn.com.bluemoon.wash.SophixStubApplication"
        android:debuggable="true"
        android:allowBackup="false"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@ref/0x7f110002"
        android:appComponentFactory="android.support.v4.app.CoreComponentFactory"> <! < Application Android: Theme ="@ref/0x7f0f000f"
        android:label="@ref/0x7f0e0071"
        android:icon="@ref/0x7f0c00bb"
        android:name="cn.com.bluemoon.wash.SophixStubApplication"
        android:allowBackup="false"
        android:supportsRtl="true"
        android:usesCleartextTraffic="true"
        android:networkSecurityConfig="@ref/0x7f110002"
        android:appComponentFactory="android.support.v4.app.CoreComponentFactory">
Copy the code

Version-based time limit

fun queryAndLoadNewPatchExamine(context: Context) { val perf = createSharedPreferences(SOPHIX_SHAREDPREFERENCES_NAME, Context) val versionKey = buildconfig. VERSION_NAME// Use the version name as the key to store the time of the last requested interface val currentTime = System.currentTimeMillis() val lastTime = getLong(perf, versionKey)if(lastTime = = 0 l | | currentTime - lastTime > TIME_INTERVAL) {/ / more than 4.2 and above system to support the SystemClock elapsedRealtimeNanos (), System.currenttimemillis () putLong(perf, versionKey, currentTime) logutils.d ()"****test****"."SophixManager.getInstance().queryAndLoadNewPatch()");
            SophixManager.getInstance().queryAndLoadNewPatch()
        }
    }
Copy the code

Three states of hot repair

When we release a patch pack, we look at three states — notification successful, download successful, load successful,

We abstracted two states from it, which we called “to be requested” before the download was successful.

In this state we will try to ask if there is a patch pack,

When the download is successful but not loaded, we call it “to be loaded”.

For practical engineering purposes we only need to maintain these two abstract states.

State switch between To be Requested and to be loaded

By default (initialized when the APP starts), the state is “to be requested”,

Only when the patch pack is downloaded will it switch to “waiting to load” and try to kill the application,

And since there are only two states, I’m actually maintaining it with a Boolean.

// Callback to download completion public voidloadRelaunch() {
        isLoadRelaunch = true; // Switch stateif(activityCount = = 0) {/ / try to kill the application handler. SendEmptyMessageDelayed (WHAT TIME); }}Copy the code

What does continuous 5S mean in the background?

We kill one of the conditions of the application when continuous 5s is in the background, but what about the implementation?

As a matter of fact, I use Handle to delay message processing, with 500ms as a count for 10 times in total, which is equal to 5s in total.

In fact, this time is not accurate, but it does not affect our logic, the actual engineering implementation is not a big problem.

@Override public void handleMessage(Message msg) { super.handleMessage(msg); // The process will be killed if it is still in the background after 5sif(ActivityManager.getInstance().getLastActivity() ! = null && ActivityManager.getInstance().getLastActivity() instanceof MainTabActivity && SophixActivityLifecycleImpl.getInstance().activityCount == 0) {if(num >= MAX_TIME) {if (num >= MAX_TIME) {if (num >= MAX_TIME) {if (num >= MAX_TIME) { Front desk number is 0, the Activity/process/kill ActivityManager getInstance (). FinishAllActivity (); SophixManager.getInstance().killProcessSafely(); }elseNum += TIME; sendEmptyMessageDelayed(WHAT, TIME); }}else{ num = 0; }}Copy the code

Test integration pain points

One of the most annoying aspects of hot fix work is to simulate hot fix scenarios:

We need to simulate the real scene to add bugs to the code, type multiple packages successively, and make different packages for testing.

This process is cumbersome and unreliable, requiring multiple compilations and packaging.

Hotfix there are some things that can’t be changed between two packages, like the SophixStubApplication of the entry, like AndroidManifest,

If these are accidentally modified, the patch pack generation may fail, or at worst, other bugs may interfere with testing.

Multi-source set building

BuilType can build both debug and Release packages on Gradle. In fact, we can build multiple APKs on another dimension with productFlavors:

flavorDimensions 'hotfix'

    productFlavors {
        before {
            dimension 'hotfix'
        }

        current {
            dimension 'hotfix'
        }

        after {
            dimension 'hotfix'}}Copy the code

Then configure different resources, or different code logic in multiple source sets, so that three different packages are produced at once,

It ensures that the areas that need to stay the same are consistent, and it can be flexibly configured to simulate various bug situations, so it can kill multiple birds with one stone.

This is also the most suitable application scenario I have encountered since learning multi-source-set Construction;

conclusion

That’s the hotfix logic for this optimization, and it’s a mature solution so far.