教你如何在SDK开发使用美团Robust进行热更新

RobustForSdk演示如何在SDK开发中使用美团的Robust进行代码的热更新 一开始要做SDK的热更新,我的内心是拒绝的-_-。看了大名鼎鼎的Tinker、Sophix、Robust之后,基于SDK的应用场景和时效性,我选择了Robust,以下介绍SDK接入Robust的整个流程~接入流程1.Robust无法直接应用在SDK项目的解决方式首先参考Robust接入指南完成配置,这里不再赘
摘要由CSDN通过智能技术生成

目录:

一开始要做SDK的热更新,我的内心是拒绝的-_-。看了大名鼎鼎的Tinker、Sophix、Robust之后,基于SDK的应用场景和时效性,我选择了Robust,以下介绍SDK接入Robust的整个流程~

接入流程

1.Robust无法直接应用在SDK项目的解决方式

首先参考Robust接入指南完成配置,这里不再赘述

里面写到Module是需要应用程序的形式

    apply plugin: 'com.android.application'
    //制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application
    //apply plugin: 'auto-patch-plugin'
    apply plugin: 'robust'

很明显SDK的开发是apply plugin: 'com.android.library',如果编译的话会报类似如下错误

Failed to notify project evaluation listener.
Transforms with scopes ‘[SUB_PROJECTS, SUB_PROJECTS_LOCAL_DEPS, EXTERNAL_LIBRARIES]’ cannot be applied to library projects.
Could not find property ‘applicationVariants’ on com.android.build.gradle.LibraryExtension_Decorated@6a5c2d2d.

因此我们需要思考如何能打出Robust处理过的jar包呢?

我们不妨将SDK的Module配置成application形式先,然后打个develop渠道的apk包,可以看到\sdk\build\outputs\apk\develop\release目录下生成了apk包,
QQ截图20171026151701.png

既然如此,那么apk在打包过程中编译生产的资源文件、代码文件应该会生成在\sdk\build的某个目录下,因此我们通过*.jar搜索找到了apk对应的代码jar文件,gradle-3.0.0 & robust-0.4.71对应的路径为
\sdk\build\intermediates\transforms\proguard\develop\release\0.jar;gradle-2.3.3 & robust-0.4.7对应的路径为 \sdk\build\intermediates\transforms\proguard\develop\release\jars\3\1f\main.jar,用查看工具可知Robust已经在编译过程中插入热修复需要的代码

QQ截图20171026152420.png

然后其它资源文件,清单文件,参考标准的aar解压后的结构可以在\sdk\build下能找到其余对应的路径,这里就不再赘述,参考 sdk 目录下build.gradle 的打包jar包task即可

QQ截图20171026153115.png

2.jar包的处理以及aar的打包

从上面的分析我们得知了jar包和各种资源的路径,因此我们可以介入gradle打包apk的过程,将打包apk过程中生成的各项文件进行处理,然后合并成aar包,输出指定的目录。下面就以多渠道的打包处理,讲解一下如何处理

在gradle.properties配置两个变量,用于方便切换SDK的打包模式,宿主Module在开发依赖的时候可以根据变量采取不同依赖方式

# Application模式,Robust需要是Application才能插入代码和打补丁
isAppModule=true
# Application模式下开启这个就可以打补丁
isPatchModule=false

apply plugin的配置

// apply plugin表示该项目会使用指定的插件,sdk对应的是com.android.library
if (isAppModule.toBoolean()) {
    # Application模式,使用robust
    apply plugin: 'com.android.application'
    if (isPatchModule.toBoolean()) {
        //制作补丁时将这个打开,auto-patch-plugin紧跟着com.android.application
        apply plugin: 'auto-patch-plugin'
    }
    apply plugin: 'robust'
} else {
    apply plugin: 'com.android.library'
}

配置两个渠道

    // 配置渠道
    productFlavors {
        // 测试渠道
        develop {
            dimension "test"
        }
        // 默认
        normal {
            dimension "test"
        }
    }

配置清单文件指定的路径,因为application和library的清单文件是有明显区别的

    sourceSets {
        main {
            // 指定jni的文件源为文件夹libs
            jniLibs.srcDirs = ['libs']

            // Application和Library清单文件处理方式不同
            if (isAppModule.toBoolean()) {
                manifest.srcFile 'src/main/debug/AndroidManifest.xml'
            } else {
                manifest.srcFile 'src/main/release/AndroidManifest.xml'
            }

        }
    }

依赖的配置

dependencies {

    // 避免宿主与我们sdk造成第三方jar包冲突,本地依赖第三方jar包不打入sdk模块的jar包
    compileOnly fileTree(dir: 'libs', include: ['*.jar'])

    androidTestImplementation('com.android.support.test.espresso:espresso-core:2.2.2', {
        exclude group: 'com.android.support', module: 'support-annotations'
    })
    testImplementation 'junit:junit:4.12'

    // 制作补丁的时候robust打入jar包里面,不用宿主再去compile,这样sdk的热修复对于宿主无感知
    implementation 'com.meituan.robust:robust:0.4.71'

    // 远程依赖aar包的话不支持provided或者compileOnly形式,由此需要在自定义task打jar包的时候过滤掉
    implementation 'com.orhanobut:logger:2.1.1'

    // 与依赖logger同理
    implementation 'cn.bmob.android:bmob-sdk:3.5.8'

    // secret.jar由secret模块输出的jar包,直接打入sdk模块的jar包,使用api而不是implementation是因为在app模块有直接使用secret里面的方法,因此暴露此依赖
    api files('libs/secret.jar')

}

自定义task进行打包的处理,介入项目打release版本apk包的过程

// 项目打release版本apk包的话,必然会调用到assemble(渠道)Release的命令,于是我们可以用正则匹配来匹配所有渠道的打Release包过程
Pattern p = Pattern.compile("^assemble(.*)Release\$")

// 在task添加到列表的时候,进行打包task的匹配
tasks.whenTaskAdded { task ->
    if (!isAppModule.toBoolean()) {
        // 不是Application模式不处理
        return
    }
    // 在任务执行的时候,匹配执行assemble(渠道)Release的打APK任务
    Matcher m = p.matcher(task.name)
    if (m.find()) {
        // 取出渠道
        String flavor = m.group(1)
        if (flavor.length() > 1) {
            // 渠道命名的修正,首字母小写,例如develop渠道对应命令为assembleDevelopRelease
            flavor = flavor.substring(0, 1).toLowerCase() + flavor.substring(1)
        }

        // 打release包task完成之后进行资源的整合以及jar包去指定class文件,并且生成aar包
        task.doLast {

            delete {
                // 删除上次生成的文件目录,目录为 \sdk\robustjar\(渠道)\release
                delete projectDir.toString() + File.separator + 'robustjar' + File.separator + flavor + File.separator + "release"
            }

            // 打包所需资源所在的父目录, \sdk\build\intermediates
            String intermediatesPath = buildDir.toString() + File.separator + "intermediates"

            // gradle-3.0.0 & robust-0.4.71对应的路径为 \sdk\build\intermediates\transforms\proguard\(渠道)\release\0.jar
            String robustJarPath = intermediatesPath + File.separator + "transforms" + File.separator + "proguard" + File.separator + flavor + File.separator + "release" + File.separator + "0.jar"

            // gradle-2.3.3 & robust-0.4.7对应的路径为 \sdk\build\intermediates\transforms\proguard\(渠道)\release\jars\3\1f\main.jar
            // String robustJarPath = intermediatesPath + File.separator + "transforms" + File.separator + "proguard" + File.separator + flavor + File.separator + "release" + File.separator + "jars" + File.separator + "3" + File.separator + "1f" + File.separator + "main.jar"

            // 资源文件的路径,\sdk\build\intermediates\assets\(渠道)\release
            String assetsPath = intermediatesPath + File.separator + "assets" + File.separator + flavor + File.separator + "release"

            // 依赖本地jar包路径,\sdk\build\intermediates\jniLibs\(渠道)\release
            String libsPath = intermediatesPath + File.separator + "jniLibs" + File.separator + flavor + File.separator + "release"

            // res资源文件的路径,\sdk\build\intermediates\res\merged\(渠道)\release,经测试发现此目录下生成的.9图片会失效,因此弃置,换另外方式处理
            // String resPath = intermediatesPath + File.separator + "res" + File.separator + "merged" + File.separator + flavor + File.separator + "release"

            // 由于上述问题,直接用项目的res路径 \sdk\src\main\res ,因此第三方依赖的资源文件无法整合,但是我是基于生成只包含自身代码的jar包和资源,其余依赖宿主另外再依赖的方案,所以可以这样处理
            String resPath = projectDir.toString() + File.separator + "src" + File.separator + "main" + File.separator + "res"

            // 资源id路径,\sdk\build\intermediates\symbols\(渠道)\release
            String resIdPath = intermediatesPath + File.separator + "symbols" + File.separator + flavor + File.separator + "release"

            // 清单文件路径,\sdk\build\intermediates\manifests\full\(渠道)\release,由于是生成的application的清单文件,因此下面还会做删除组件声明的处理
            String manifestPath = intermediatesPath + File.separator + "manifests" + File.separator + "full" + File.separator + flavor + File.separator + "release"

            // 整合上述文件后的目标路径,\sdk\robustjar\(渠道)\release\origin
            String destination = projectDir.toString() + File.separator + /*'outputs' + File.separator +*/ 'robustjar' + File.separator + flavor + File.separator + 'release' + File.separator + 'origin'

            // 貌似aidl的文件夹没啥用,打包会根据例如G:\\sms-hotfix\\SmsParsingForRcs-Library\\library\\src\\main\\aidl\\com\\cmic\\IMyAidlInterface.aidl的定义代码生成com.cmic.IMyAidlInterface到jar包里面,因此aidl仅仅是空文件夹
            // String aidlPath = buildDir.toString() + File.separator + "generated" + File.separator + "source" + File.separator + "aidl" + File.separator + flavor + File.separator + "release"

            File file = file(robustJarPath)
            if (file.exists()) {
                println '渠道是:' + flavor + ';开始复制robust插桩jar包'
                copy {

                    // 拷贝到assets目录
                    from(assetsPath) {
                        into 'assets'
                    }

                    //  第三方本地jar包不处理,提供宿主集成的时候另外提供
                    //  from(libsPath) {
                    //      into 'libs'
                    //      include '**/*.jar'
                    //      exclude {
                    //          // println it.path+";"+it.isDirectory()
                    //          it.isDirectory()
                    //      }
                    //  }

                    // .so文件拷贝到jni目录
                    from(libsPath) {
                        into 'jni'
                        include '**/*/*.so'
                    }

                    // 资源文件拷贝到res目录
                    from(resPath) {
                        // 排除MainActivity加载的布局文件,因为输出的是jar包,加MainActivity仅仅是为了能让打apk包任务执行
                        exclude '/layout/activity_main.xml'
                        exclude {
                            // 排除空文件夹
                            it.isDirectory() && it.getFile().listFiles().length == 0
                        }
                        into 'res'
                    }

                    // aidl的文件夹没啥用,不处理
                    // from(aidlPath) {
                    //     into 'aidl'
                    // }

                    // 拷贝此目录下资源id文件 R.txt
                    from resIdPath

                    // 拷贝到目录 \sdk\robustjar\(渠道)\release\origin
                    into destination

                }

                copy {
                    // 复制供宿主的混淆规则,这里我在android{ defaultConfig { consumerProguardFiles 'lib-proguard-rules.pro' }},配置了一个混淆规则
                    def files = android.defaultConfig.consumerProguardFiles
                    if (files != null && files.size() > 0) {
                        def file1 = files.get(0);
                        //  println '混淆文件路径:'+file1.path
                       
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值