Android的在线热更新方案的实现

特别需要注意配置合适的Gradle版本
Robust官网:https://github.com/Meituan-Dianping/Robust
Robust问题讨论:https://github.com/Meituan-Dianping/Robust/issues/439


1、Android工程的Gradle配置:

01.Project的build.gradle:

buildscript {  
    //使用低版本的kotlin
    ext.kotlin_version = "1.3.72"
    repositories {
        google()
        jcenter()
    }
    dependencies {
        //只能使用低版本的Gradle
        classpath "com.android.tools.build:gradle:3.6.4"
        classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
        classpath 'com.meituan.robust:gradle-plugin:0.4.99'
        classpath 'com.meituan.robust:auto-patch-plugin:0.4.99'
        // NOTE: Do not place your application dependencies here; they belong
        // in the individual module build.gradle files
    }
}

allprojects {
    repositories {
        google()
        jcenter() // Warning: this repository is going to shut down soon
        //必须使用这个maven库
        maven { url 'https://jitpack.io' }
    }
}

2.app下的build.gradle(添加robust依赖):

dependencies {
    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    //下面的版本号都要改
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    .....
    //这是需要添加的依赖
    implementation 'com.meituan.robust:robust:0.4.99'
    implementation 'com.github.tbruyelle:rxpermissions:0.12'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.0.5'
    implementation 'com.android.support:multidex:1.0.3'

3.app下的build.gradle(针对Build Apk[命令:gradlew clean assembleRelease --stacktrace --no-daemon也可以buildApk]时可能的报错):

defaultConfig {
        applicationId "com.huye.robusttest3"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        //这里需要添加
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        //报错重复androidsupportmultidexversion.txt时添加
        packagingOptions {
            exclude 'androidsupportmultidexversion.txt'
        }
    }

4.app下的build.gradle(完整配置):

plugins {
    id 'com.android.application'
    //打补丁使用auto-patch-plugin
//    id 'auto-patch-plugin'
    //获取初始版本的Apk使用robust
    id 'robust'
    id 'kotlin-android'
}

android {
    compileSdkVersion 30
    buildToolsVersion "30.0.3"

    defaultConfig {
        applicationId "com.huye.robusttest3"
        minSdkVersion 16
        targetSdkVersion 30
        versionCode 1
        versionName "1.0"
        multiDexEnabled true
        testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
        packagingOptions {
            exclude 'androidsupportmultidexversion.txt'
        }
    }

    signingConfigs {
        debug {
            keyAlias "key0"
            keyPassword "123456"
            storeFile file('robustsign2')
            storePassword "123456"
        }
        release {
            keyAlias "key0"
            keyPassword "123456"
            storeFile file('robustsign2')
            storePassword "123456"
        }
    }

    buildTypes {
        release {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.release
        }
        debug {
            minifyEnabled true
            proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
            signingConfig signingConfigs.debug
        }
    }
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_8
        targetCompatibility JavaVersion.VERSION_1_8
    }
    kotlinOptions {
        jvmTarget = '1.8'
    }
}

dependencies {

    implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
    //这个版本号也要改
    implementation 'androidx.core:core-ktx:1.3.2'
    implementation 'androidx.appcompat:appcompat:1.2.0'
    implementation 'com.google.android.material:material:1.3.0'
    implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
    testImplementation 'junit:junit:4.+'
    androidTestImplementation 'androidx.test.ext:junit:1.1.2'
    androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0'

    implementation 'com.meituan.robust:robust:0.4.99'
    implementation 'com.github.tbruyelle:rxpermissions:0.12'
    implementation 'io.reactivex.rxjava3:rxandroid:3.0.0'
    implementation 'io.reactivex.rxjava3:rxjava:3.0.5'
    implementation 'com.android.support:multidex:1.0.3'
}

2、AndroidManifest.xml需要添加的动态权限:

<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    package="com.huye.robusttest3">
    <uses-permission android:name="android.permission.INTERNET" />
    <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
    <uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />

    <application
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:roundIcon="@mipmap/ic_launcher_round"
        android:supportsRtl="true"
        android:theme="@style/Theme.RobustTest3"
        tools:ignore="HardcodedDebugMode">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>

</manifest>

3、添加robust.xml(注意修改包名):

<?xml version="1.0" encoding="utf-8"?>
<resources>

    <switch>
        <!--true代表打开Robust,请注意即使这个值为true,Robust也默认只在Release模式下开启-->
        <!--false代表关闭Robust,无论是Debug还是Release模式都不会运行robust-->
        <turnOnRobust>true</turnOnRobust>
        <!--<turnOnRobust>false</turnOnRobust>-->

        <!--是否开启手动模式,手动模式会去寻找配置项patchPackname包名下的所有类,自动的处理混淆,然后把patchPackname包名下的所有类制作成补丁-->
        <!--这个开关只是把配置项patchPackname包名下的所有类制作成补丁,适用于特殊情况,一般不会遇到-->
        <!--<manual>true</manual>-->
        <manual>false</manual>

        <!--是否强制插入插入代码,Robust默认在debug模式下是关闭的,开启这个选项为true会在debug下插入代码-->
        <!--但是当配置项turnOnRobust是false时,这个配置项不会生效-->
        <!--<forceInsert>true</forceInsert>-->
        <forceInsert>false</forceInsert>

        <!--是否捕获补丁中所有异常,建议上线的时候这个开关的值为true,测试的时候为false-->
        <catchReflectException>true</catchReflectException>
        <!--<catchReflectException>false</catchReflectException>-->

        <!--是否在补丁加上log,建议上线的时候这个开关的值为false,测试的时候为true-->
        <!--<patchLog>true</patchLog>-->
        <patchLog>false</patchLog>

        <!--项目是否支持progaurd-->
        <proguard>true</proguard>
        <!--<proguard>false</proguard>-->

        <!--项目是否支持ASM进行插桩,默认使用ASM,推荐使用ASM,Javaassist在容易和其他字节码工具相互干扰-->
        <useAsm>true</useAsm>
        <!--<useAsm>false</useAsm>-->

        <!--针对Java8级别的Lambda表达式,编译为private级别的javac函数,此时由开发者决定是否进行插桩处理-->
        <forceInsertLambda>true</forceInsertLambda>
        <!--        <forceInsertLambda>false</forceInsertLambda>-->
    </switch>

    <!--需要热补的包名或者类名,这些包名下的所有类都被会插入代码-->
    <!--这个配置项是各个APP需要自行配置,就是你们App里面你们自己代码的包名,
    这些包名下的类会被Robust插入代码,没有被Robust插入代码的类Robust是无法修复的-->
    <packname name="hotfixPackage">
        <name>com.huye.robusttest3</name>
    </packname>

    <!--不需要Robust插入代码的包名,Robust库不需要插入代码,如下的配置项请保留,还可以根据各个APP的情况执行添加-->
    <exceptPackname name="exceptPackage">
    </exceptPackname>

    <!--补丁的包名,请保持和类PatchManipulateImp中fetchPatchList方法中设置的补丁类名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
    各个App可以独立定制,需要确保的是setPatchesInfoImplClassFullName设置的包名是如下的配置项,类名必须是:PatchesInfoImpl-->
    <patchPackname name="patchPackname">
        <name>com.huye.robusttest3</name>
    </patchPackname>

    <!--自动化补丁中,不需要反射处理的类,这个配置项慎重选择-->
    <noNeedReflectClass name="classes no need to reflect">

    </noNeedReflectClass>
</resources>

4、创建MainActivity:

class MainActivity : AppCompatActivity() {
    lateinit var textView: TextView

    override
    fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)
        textView = findViewById(R.id.content_tv)

        val file =
            File(externalCacheDir?.absolutePath + File.separator + "robust" + File.separator + "patch ")
        if (!file.exists()) {
            file.mkdirs()
        }
        RxPermissions(this)
            .request(
                Manifest.permission.READ_EXTERNAL_STORAGE,
                Manifest.permission.WRITE_EXTERNAL_STORAGE
            )
            .subscribe { granted ->
            }
    }

    fun getString(): String {
        return "这是个Bug"
    }

    fun loadPatch(view: View) {
        PatchExecutor(this, PatchManipulateImp(), object : RobustCallBack {
            override fun onPatchListFetched(
                result: Boolean,
                isNet: Boolean,
                patches: MutableList<Patch>?
            ) {
            }

            override fun onPatchFetched(result: Boolean, isNet: Boolean, patch: Patch?) {
                Log.d("swt", "onPatchFetched")
            }

            override fun onPatchApplied(result: Boolean, patch: Patch?) {
                Log.d("swt", "onPatchApplied")

            }

            override fun logNotify(log: String?, where: String?) {
                Log.d("swt", "logNotify")

            }

            override fun exceptionNotify(throwable: Throwable?, where: String?) {
                Log.d("swt", "exceptionNotify")

            }

        }).start()
    }

    @Modify
    fun setTV(view: View) {
        textView.text = getString()
    }

    @Add
    fun getString1(): String {
        Toast.makeText(this, "终于成功了", Toast.LENGTH_SHORT).show()
        return "哈哈"
    }
}

5、创建PatchManipulateImp补丁执行类:

class PatchManipulateImp : PatchManipulate() {
    override fun fetchPatchList(context: Context): MutableList<Patch> {
        val patch = Patch()
        patch.name = "test"
        patch.tempPath =
            context.externalCacheDir?.absolutePath + File.separator + "robust" + File.separator + "patch"
        patch.localPath =
            context.externalCacheDir?.absolutePath + File.separator + "robust" + File.separator + "patch"
        patch.patchesInfoImplClassFullName = "com.huye.robusttest3.PatchesInfoImpl"
        val list = arrayListOf<Patch>()
        list.add(patch)
        return list
    }

    override fun verifyPatch(context: Context?, patch: Patch?): Boolean {
        return true
    }

    override fun ensurePatchExist(patch: Patch?): Boolean {
        return true
    }
}

6、制作patch补丁的步骤:

01.获取初始版本的Apk,将生成的mapping.text、methodsMap.robust、robust.apkhash放到app下的robust文件夹下面

android 热更 android 热更方案_android

02.切换build.gradle的插件,开始制作补丁

android 热更 android 热更方案_包名_02

03.将patch.jar利用adb push放到指定的位置

adb push C:\Users\huye5\Desktop\test\patch.jar /sdcard/Android/data/com.huye.robusttest3/cache/robust/patch_temp.jar

04.加载补丁文件,观看Log日志输入是否正常,出现"apply result true"就是成功了

android 热更 android 热更方案_包名_03