Android Integrated Bugly Hot Update

Posted by radalin on Sat, 18 May 2019 13:32:47 +0200

Android Integrated Bugly Hot Update

@ Author GQ 18 December 2017

Recently, a new project will be opened. The leader said that he would not let the old app update, tens of megabytes at a time.

So integrated heat is the trend.~

By the way, use Google Dad Kt language!

Information check

Mainstream hot frameworks include:

  • Ali AndFix

  • Tencent Bugly

  • Robust, American Corps

Tinker is the official Android hotpatch solution for Wechat (recommended)

It supports dynamic downloading of code, So libraries and resources, enabling applications to update without reinstallation. Of course, you can also use Tinker to update your plug-in.

Frame comparison

Data from Guo Shen's subscription number: https://mp.weixin.qq.com/s/lMcCSKG54xvvCo9ZGI8ZlA

Select Bulgy

Create a new project

First run normally

Integrated Bugly

Be careful not to rush to build projects. When things are not loaded properly, they will report errors. Modify the configuration files in order first.

Before build ing, remember to register for Advanced Official Website, get appId and change it to project

In the build.gradle of the project
buildscript {
    ext.kotlin_version = '1.1.51'
    repositories {
        google()
        jcenter()
    }
    dependencies {
        classpath 'com.android.tools.build:gradle:3.0.1'
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"

        //Current version, you can use latest.release to introduce the latest version
        classpath "com.tencent.bugly:tinker-support:1.1.1" 
    }
}
In build.gradle under app

Note: You have to write the packaged jks in build.gradle. Don't fool around with moths. Because of this crap, I got stuck here for an hour.

The imported packages are all imported. Remember to import one more v4 package, which will be used.

apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'kotlin-android-extensions'
apply from: 'tinker-support.gradle'
android {
    compileSdkVersion rootProject.ext.android.compileSdkVersion
    buildToolsVersion "27.0.0"
    defaultConfig {
        applicationId rootProject.ext.android.applicationId
        minSdkVersion rootProject.ext.android.minSdkVersion
        targetSdkVersion rootProject.ext.android.targetSdkVersion
        versionCode rootProject.ext.android.versionCode
        versionName rootProject.ext.android.versionName
        testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
        vectorDrawables.useSupportLibrary = true
        // Open multidex
        multiDexEnabled true

    }
    dexOptions {
        // Supporting large-scale engineering model
        jumboMode = true
    }
    signingConfigs {
        release {
            keyAlias 'oakey'
            keyPassword '123456'
            //Write on your own path
            storeFile file('/Users/GUOQI/Android/workplace_git/jks/test.jks')
            //Set your own password
            storePassword '123456'
        }
    }

    buildTypes {
        release {
            minifyEnabled false
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
    }
}

dependencies {
    implementation fileTree(include: ['*.jar'], dir: 'libs')
    implementation "org.jetbrains.kotlin:kotlin-stdlib-jre7:$kotlin_version"
    implementation 'com.android.support:appcompat-v7:25.4.0'
    //V4 package
    implementation 'com.android.support:support-v4:25.4.0'
    implementation 'com.android.support:design:25.4.0'
    implementation 'com.android.support.constraint:constraint-layout:1.0.2'
    implementation 'com.android.support:support-vector-drawable:25.4.0'
    testImplementation 'junit:junit:4.12'
    androidTestImplementation 'com.android.support.test:runner:1.0.1'
    androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'

    // Multi dex configuration
    implementation 'com.android.support:multidex:1.0.2'
    // Remote Warehouse Integration (Recommendation)
    implementation 'com.tencent.bugly:crashreport_upgrade:1.3.4'
}
  • It uses something like rootProject.ext.android.xxx. Don't worry about it just for sharing.

Create a new config.gradle file under the project and directory

This gradle file is used to store the public items just mentioned.

ext {
    android = [compileSdkVersion: 25,
               applicationId    : "com.example.test",//Write your own name
               minSdkVersion    : 19,
               targetSdkVersion : 21,
               versionCode      : 1,
               versionName      : "1.0"]
}
Back in build.gradle, which was generated by default for the project
apply from: 'config.gradle'//Introduce the config.gradle file that I just wrote, and add this line.

buildscript {
    ...
}

allprojects {
   repositories {
        google()
        jcenter()
        maven { url 'https://jitpack.io' } //Introduce this warehouse
    }
}

task clean(type: Delete) {
    delete rootProject.buildDir
}
The most important new file, tinker-support.gradle

Note that in the app directory, don't build the wrong location, resulting in the error can not be found.

Then open editing the tinker-support.gradle content

apply plugin: 'com.tencent.bugly.tinker-support'

def bakPath = file("${buildDir}/bakApk/")

/**
 * Fill in the base package directory generated for each build here
 *
 * You just need to change myTinkeId here in the future.
 */
def baseApkDir = "app-1218-12-53-16" //When packing, the following numbers are automatically generated according to the timestamp, which can be ignored for the time being.
//Def myTinkerId = base-"+ rootProject. ext. android. versionName // / Used to generate benchmark packages (without modification)
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.1" // Used to generate patch packs (patch - ${version Name}. X. x is the best one to modify every time patch packs are generated)

/**
 * For detailed analysis of plug-in parameters, please refer to
 *
 * enable Attribute true
 * enableProxyApplication Attribute false
 * The above two attributes should not be mistakenly altered by other different information on the Internet
 */
tinkerSupport {

    // Open the tinker-support plug-in with the default value true
    enable = true

    // Whether the reinforcement mode is enabled or not, default is false.(tinker-spport 1.0.7 support)
    // isProtectedApp = true

    // Whether to turn on Reflective Application mode
    enableProxyApplication = false

    // Do you support the addition of non-export Activeness (Note: To modify the Android Manifest file, set true)
    supportHotplugComponent = true

    // Specify the archive directory, default value of current module subdirectory tinker
    autoBackupApkDir = "${bakPath}"

    // Whether to enable overlay tinkerPatch configuration function, default value false
    // The tinkerPatch configuration does not take effect after opening, that is, there is no need to add tinkerPatch
    overrideTinkerPatchConfiguration = true

    // When compiling patch packs, you must specify the baseline version of the apk, which defaults to null
    // If null, it means that the patch package is not compiled
    // @{link tinkerPatch.oldApk }
    baseApk = "${bakPath}/${baseApkDir}/app-release.apk"

    // applyMapping corresponding to tinker plug-in
    baseApkProguardMapping = "${bakPath}/${baseApkDir}/app-release-mapping.txt"

    // ApplyResource Mapping for the tinker plug-in
    baseApkResourceMapping = "${bakPath}/${baseApkDir}/app-release-R.txt"

    // Constructing benchmark and patch packs specifies different tinkerId and must guarantee uniqueness
    tinkerId = "${myTinkerId}"

    // Use when building multi-channel patches
    // buildAllFlavorsDir = "${bakPath}/${baseApkDir}"

}

/**
 * Generally speaking, we do not need to make any changes to the following parameters
 * For a detailed description of each parameter, please refer to:
 * https://github.com/Tencent/tinker/wiki/Tinker-%E6%8E%A5%E5%85%A5%E6%8C%87%E5%8D%97
 */
tinkerPatch {
    //oldApk ="${bakPath}/${appName}/app-release.apk"
    ignoreWarning = false
    useSign = true
    dex {
        dexMode = "jar"
        pattern = ["classes*.dex"]
        loader = []
    }
    lib {
        pattern = ["lib/*/*.so"]
    }

    res {
        pattern = ["res/*", "r/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
        ignoreChange = []
        largeModSize = 100
    }

    packageConfig {
    }
    sevenZip {
        zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
//        path = "/usr/local/bin/7za"
    }
    buildConfig {
        keepDexApply = false
        //tinkerId = "1.0.1-base"
        //applyMapping = "${bakPath}/${appName}/app-release-mapping.txt"// Optional. Set up the mapping file. It is recommended that the proguard confusion of the old apk be maintained.
        //applyResourceMapping = "${bakPath}/${appName}/app-release-R.txt"// Optional, set the R.txt file, and keep ResId allocation through the old apk file
    }
}
Create a new MyApplication Life to inherit Default Application Like

Initialize everything in order, direct cv

class MyApplicationLike(application: Application, tinkerFlags: Int,
                        tinkerLoadVerifyFlag: Boolean, applicationStartElapsedTime: Long,
                        applicationStartMillisTime: Long, tinkerResultIntent: Intent) : DefaultApplicationLike(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent) {
    private var mContext: Application? = null

    override fun onCreate() {
        super.onCreate()
        mContext = application
        //Configure Tinker
        configTinker()

       //Initialize the image loading, initialize the xxx itself used to write here
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    override fun onBaseContextAttached(base: Context?) {
        super.onBaseContextAttached(base)
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base)
        // Install tinker
        Beta.installTinker(this)
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    fun registerActivityLifecycleCallback(callbacks: Application.ActivityLifecycleCallbacks) {
        application.registerActivityLifecycleCallbacks(callbacks)
    }

    override fun onTerminate() {
        super.onTerminate()
        Beta.unInit()
    }

    companion object {
        val TAG = "Tinker.MyApplicationLike"
    }

    private fun configTinker() {
        // Set whether to turn on hot update capability by default to true
        Beta.enableHotfix = true
        // Set whether patches are automatically downloaded by default to true
        Beta.canAutoDownloadPatch = true
        // Sets whether patches are automatically synthesized by default to true
        Beta.canAutoPatch = true
        // Set whether to prompt the user to restart, default to false
        Beta.canNotifyUserRestart = true
        // Patch callback interface
        Beta.betaPatchListener = object : BetaPatchListener {
            override fun onPatchReceived(patchFile: String) {
                Toast.makeText(mContext, "Patch Download Address" + patchFile, Toast.LENGTH_SHORT).show()
            }

            override fun onDownloadReceived(savedLength: Long, totalLength: Long) {
                Toast.makeText(mContext,
                        String.format(Locale.getDefault(), "%s %d%%",
                                Beta.strNotificationDownloading,
                                (if (totalLength == 0L) 0 else savedLength * 100 / totalLength).toInt()),
                        Toast.LENGTH_SHORT).show()
            }

            override fun onDownloadSuccess(msg: String) {
                Toast.makeText(mContext, "Patch Download Successful", Toast.LENGTH_SHORT).show()
            }

            override fun onDownloadFailure(msg: String) {
                Toast.makeText(mContext, "Patch download failed", Toast.LENGTH_SHORT).show()
            }

            override fun onApplySuccess(msg: String) {
                Toast.makeText(mContext, "Successful patch application", Toast.LENGTH_SHORT).show()
            }

            override fun onApplyFailure(msg: String) {
                Toast.makeText(mContext, "Patch application failed", Toast.LENGTH_SHORT).show()
            }

            override fun onPatchRollback() {

            }
        }

        // Set up the development device, default is false, upload patch if the download scope is specified as "development device", you need to call this interface to identify the development device.
        Bugly.setIsDevelopmentDevice(mContext, false)
        // Multi-channel demand congestion
        // String channel = WalleChannelReader.getChannel(getApplication());
        // Bugly.setAppChannel(getApplication(), channel);
        // SDK initialization is implemented here, and appId is replaced by your appId applied for on the Bugly platform.
        Bugly.init(mContext, AppId.APP_ID_BUGLY, true)
    }

}
Test Application for a new project inherits from Tinker Application

Note that the second parameter fills in its corresponding MyApplication Life package name. Note when cv

class TestApplication : TinkerApplication(ShareConstants.TINKER_ENABLE_ALL, "com.xxx.MyApplicationLike", "com.tencent.tinker.loader.TinkerLoader", false) {
    //There's nothing to write inside.
}
Modify Manifest's name attribute

Write your new empty TestApplication

 <application
        android:name=".example.TestApplication"
        ...
  • Then Manifest must add the following permissions and configurations
<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"/>

//Note that I didn't write the application label and put it inside the application itself.

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

<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>
Execute build waiting for boot build

After the build is successful, click the right column gradle to execute assembleRelease

Waiting for the release package to be built successfully

You need to use the packaged jks configuration mentioned at the beginning, so you must write it in build.gradle.

After success, you can see it in the build under app

  • The newly generated file of bakApk represents the benchmark apk
  • app-release.apk is already an official package for signature generation and can be installed directly into mobile phones or simulators.

Making Patch Documents

Change textView on any page of app

Prove that this is a class or resource file that needs to be updated

Open the tinker-support.gradle file

Annotate the line of base, release the line of patch, generate patches many times and remember to change the number of. 0.1.

def baseApkDir = "app-1218-12-53-16"
//Def myTinkerId = base-"+ rootProject. ext. android. versionName // / Used to generate benchmark packages (without modification)
def myTinkerId = "patch-" + rootProject.ext.android.versionName + ".0.1" // Used to generate patch packs (patch - ${version Name}. X. x is the best one to modify every time patch packs are generated)
Then open the right sidebar build.gradle and execute build Tinker Patch Release

Waiting for the execution to complete successfully

Packed patch file in app/build/output/patch, the focus is xx7zip.apk, this is the patch file

Open Bugly's official website

Official website https://bugly.qq.com/v2/index

Registration - > Login - > Create Application - > Get AppId - > Modify to Project

Upload the above 7zip.apk to the project - > Application Upgrade - > Hot Update - > Publish New Upgrade

  • Show that it's in the process of posting, and you need to wait a little longer.
  • The apk installed on the phone or simulator before opening it again will prompt for hot updates and restart the application

Patched rendering

Topics: Android Gradle hot update Google