教你如何在SDK開發使用美團Robust進行熱更新

RobustForSdk

演示如何在SDK開發中使用美團的Robust進行代碼的熱更新java

一開始要作SDK的熱更新,個人心裏是拒絕的-_-。看了大名鼎鼎的Tinker、Sophix、Robust以後,基於SDK的應用場景和時效性,我選擇了Robust,如下介紹SDK接入Robust的整個流程~android

接入流程

1.Robust沒法直接應用在SDK項目的解決方式

首先參考Robust接入指南完成配置,這裏再也不贅述git

裏面寫到Module是須要應用程序的形式github

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',若是編譯的話會報相似以下錯誤api

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包呢?bash

咱們不妨將SDK的Module配置成application形式先,而後打個develop渠道的apk包,能夠看到\sdk\build\outputs\apk\develop\release目錄下生成了apk包,
網絡

QQ截圖20171026151701.png
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已經在編譯過程當中插入熱修復須要的代碼app

QQ截圖20171026152420.png
QQ截圖20171026152420.png

而後其它資源文件,清單文件,參考標準的aar解壓後的結構能夠在\sdk\build下能找到其他對應的路徑,這裏就再也不贅述,參考 sdk 目錄下build.gradle 的打包jar包task便可異步

QQ截圖20171026153115.png
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
                        from file1.path
                        into destination
                        // 複製混淆規則而且重命名
                        rename(file1.name, 'proguard.txt')
                    }
                }

                // 補丁生成須要的mapping.txt和methodsMap.robust文件
                copy {
                    // 混淆mapping文件的路徑,\sdk\build\outputs\mapping\(渠道)\release\mapping.txt
                    from(buildDir.toString() + File.separator + 'outputs' + File.separator + 'mapping' + File.separator + flavor + File.separator + 'release') {
                        include 'mapping.txt'
                    }
                    // 拷貝到目錄 \sdk\robustjar\(渠道)\release
                    into projectDir.toString() + File.separator + 'robustjar' + File.separator + flavor + File.separator + 'release'
                }

                copy {
                    // robust生成的methodsMap文件路徑,\sdk\build\outputs\robust\methodsMap.robust
                    from(buildDir.toString() + File.separator + 'outputs' + File.separator + 'robust') {
                        include 'methodsMap.robust'
                    }
                    // 拷貝到目錄 \sdk\robustjar\(渠道)\release
                    into projectDir.toString() + File.separator + 'robustjar' + File.separator + flavor + File.separator + 'release'
                }

                // 若不存在aidl目錄,建立aidl空目錄
                createDir(destination + File.separator + "aidl")
                // 同上
                createDir(destination + File.separator + "assets")
                // 同上
                createDir(destination + File.separator + "jni")
                // 同上
                createDir(destination + File.separator + "libs")
                // 同上
                createDir(destination + File.separator + "res")

                // 將清單文件application節點的內容和activity節點的內容替換,例以下面
                <application
                    android:allowBackup="true"
                    tools:replace="android:label"
                    android:label="sdk"
                    android:supportsRtl="true"
                    android:icon="@android:drawable/ic_dialog_info"
                    android:theme="@android:style/Theme.Black"
                    >
                    <activity android:name=".MainActivity">
                        <intent-filter>
                            <action android:name="android.intent.action.MAIN"/>
                            <category android:name="android.intent.category.LAUNCHER"/>
                        </intent-filter>
                    </activity>
                </application>
                轉換成
                <application
                    android:allowBackup="true"
                    tools:replace="android:label"
                    android:label="sdk"
                    android:supportsRtl="true">
                </application>

                def oldStr = ["<application[\\s\\S]*?>", "<activity[\\s\\S]*?</activity>"]
                def newStr = ["<application\n" + " android:allowBackup=\"true\"\n" + " android:supportsRtl=\"true\">", ""]
                // 處理 \sdk\build\intermediates\manifests\full\(渠道)\release\AndroidManifest.xml
                String strBuffer = fileReader(manifestPath + File.separator + "AndroidManifest.xml", oldStr, newStr)
                // 輸出至 \sdk\robustjar\(渠道)\release\origin\AndroidManifest.xml
                fileWrite(destination + File.separator + "AndroidManifest.xml", strBuffer)

                println '輸出robust插樁jar包成功!'

                // 執行打jar包的task,這裏會作原jar包的過濾處理,只保留咱們須要的代碼
                tasks.findByName('jar_' + flavor).execute()

                // 執行打aar包的task,其實就是將目錄\sdk\robustjar\develop\release\origin壓縮成aar後綴的壓縮包
                tasks.findByName('aar_' + flavor).execute()

            }
        }
    }
}複製代碼

上述task須要執行的另外兩個task

// 根據渠道生成打jar包和aar包的對應的task
for (String flavor : android.productFlavors.names) {

    // 遍歷全部渠道,生成對應渠道打jar包的task,名字爲jar_(渠道)
    tasks.create(name: 'jar_' + flavor, type: Jar) {

        println "當前渠道是:" + flavor

        // jar包命名爲classes.jar
        baseName 'classes'

        String intermediatesPath = buildDir.toString() + File.separator + "intermediates"

        // 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"

         // 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"

        def zipFile = new File(robustJarPath)
        // 將jar包解壓
        FileTree jarTree = zipTree(zipFile)

        from jarTree

        // jar包輸出路徑爲 \sdk\robustjar\(渠道)\release\origin
        File destDir = file(projectDir.toString() + File.separator + 'robustjar' + File.separator + flavor + File.separator + 'release' + File.separator + 'origin')
        // 設置輸出路徑
        setDestinationDir destDir

        include {
            // 只打包咱們須要的類
            it.path.startsWith('com/oubowu/sdk') || it.path.startsWith('com/meituan/robust') || it.path.startsWith('com/oubowu/secret')
        }

        exclude {
            // println "執行排除:" + it.path
            // 排除R相關class文件,排除MainActivity.class文件
            it.path.startsWith('com/oubowu/sdk/R$') || it.path.startsWith('com/oubowu/sdk/R.class') || it.path.startsWith('com/oubowu/sdk/MainActivity.class')
        }

        println '壓縮jar包完畢!!!!!!!!'
    }

    // 遍歷全部渠道,生成對應渠道打aar包的task,名字爲aar_(渠道)
    tasks.create(name: 'aar_' + flavor, type: Zip) {
        // aar包輸出路徑爲 \sdk\robustjar\(渠道)\release\aar
        File destDir = file(projectDir.toString() + File.separator + 'robustjar' + File.separator + flavor + File.separator + 'release' + File.separator + 'aar')
        // aar包命名爲 library-(渠道)-release.aar
        archiveName 'library-' + flavor + '-release.aar'
        // 源路徑爲 \sdk\robustjar\(渠道)\release\origin
        from projectDir.toString() + File.separator + 'robustjar' + File.separator + flavor + File.separator + 'release' + File.separator + 'origin'
        // 設置壓縮後輸出的路徑
        destinationDir destDir

        println '壓縮aar包完畢!!!!!!!!'
    }

}複製代碼

至此咱們的自定義打包task就編寫完成了,執行assembleDevelopRelease任務,便可生成咱們想要的jar包和aar包了

QQ截圖20171101111237.png
QQ截圖20171101111237.png

牛皮扯完了,給出項目地址RobustForSdk,能夠下載下來運行方便理解。

接下來我還會講解我是如何處理補丁下發和安全應用補丁的。敬請期待吧!Peace!!!

3.補丁下發和加載的策略

參考官方示例PatchManipulateImp.java來定製咱們的下發加載策略

補丁策略.png
補丁策略.png

如上流程圖所述,首先去本地查詢是否已經下發過補丁,如有優先加載本地補丁,這種場景是針對沒有網絡或者請求網絡下發補丁失敗致使沒法修復的狀況;而後去請求網絡下發補丁的列表信息,sdk版本做爲補丁的下發依據,而後補丁版本高的包含低版本的修復代碼,我考慮是方便處理,不用加載多個補丁,對應項目發包的策略是一個大版本做爲封版創建版本分支,後續此版本上線遇到的bug都基於這個分支作處理,直到下個版本上線合併修復的代碼。

具體代碼實現以下:

// 建立1個固定線程的線程池,用於串行進行本地補丁的加載和網絡請求補丁而後加載的邏輯
        ExecutorService fixedThreadPool = Executors.newFixedThreadPool(1);
        // 讀取本地保存的上次加載的補丁的名稱
        final String pName = context.getSharedPreferences("com.oubowu.sdk.sp", Context.MODE_PRIVATE).getString("pName", "");
        if (!pName.isEmpty()) {
            // 建立本地補丁加載的線程
            PatchExecutor patchExecutor1 = new PatchExecutor(context.getApplicationContext(), new PatchManipulateImp(true, pName),...);
            // 執行本地補丁加載的線程
            fixedThreadPool.execute(patchExecutor1);
        }

        // 建立網絡請求補丁而後加載的線程
        PatchExecutor patchExecutor2 = new PatchExecutor(context.getApplicationContext(), new PatchManipulateImp(false, pName), ...);
        fixedThreadPool.execute(patchExecutor2);複製代碼

補丁的處理

public class PatchManipulateImp extends PatchManipulate {

    // 是否只作本地補丁的判斷
    private boolean mOnlyLocal = true;
    // 保存於本地的補丁的名稱
    private String mSavePatchName;

    public PatchManipulateImp(boolean onlyLocal, String savePatchName) {
        mOnlyLocal = onlyLocal;
        mSavePatchName = savePatchName;
    }

    /***
     * connect to the network ,get the latest patches
     * 聯網獲取最新的補丁
     * @param context
     *
     * @return
     */
    @Override
    protected List<Patch> fetchPatchList(final Context context) {

        final List<Patch> patches = new ArrayList<>();

        if (mOnlyLocal) {
            // 只是作本地判斷的話
            if (!mSavePatchName.isEmpty()) {
                // 名稱不爲空說明存在,添加本地保存的補丁信息,而後返回
                addPatchInfo(context, mSavePatchName, patches);
            }
            return patches;
        }

        // 因爲下面作的是異步的網絡請求下發補丁,因此使用CountDownLatch進行同步
        final CountDownLatch mCountDownLatch = new CountDownLatch(1);

        //Bmob初始化
        Bmob.initialize(context.getApplicationContext(), "52e558b89195c84cd761afbeabc3df52");

        BmobQuery<com.oubowu.sdk.Patch> query = new BmobQuery<>();
        // 經過sdkVersion查詢此sdk版本的線上補丁
        query.addWhereEqualTo("sdkVersion", BuildConfig.VERSION_NAME);
        // 根據patchVersion字段降序顯示數據
        query.order("-patchVersion");
        // query.setLimit(1);
        query.findObjects(new FindListener<com.oubowu.sdk.Patch>() {
            @Override
            public void done(List<com.oubowu.sdk.Patch> list, BmobException e) {
                if (e != null) {
                    // 請求補丁列表數據失敗
                    Logger.e(e.getMessage());
                    mCountDownLatch.countDown();
                } else {
                    if (list != null && list.size() > 0) {
                        // 取最高補丁版本的補丁
                        final com.oubowu.sdk.Patch p = list.get(0);
                        Logger.e(p.toString());
                        final String filename = p.getPatchUrl().getFilename();
                        // 若sp存的補丁名稱跟下發的最高版本的補丁名稱不同的話,下載並應用補丁;或者名稱同樣,可是本地沒有此補丁,下載並應用補丁
                        if (!filename.equals(mSavePatchName) || !(new File(context.getFilesDir(), mSavePatchName).exists())) {
                            File saveFile = new File(context.getFilesDir(), filename);
                            if (!saveFile.exists()) {
                                // 本地沒有保存的話,下載補丁
                                p.getPatchUrl().download(saveFile, new DownloadFileListener() {
                                    @Override
                                    public void done(String s, BmobException e) {
                                        if (e != null) {
                                            mCountDownLatch.countDown();
                                        } else {
                                            Logger.e("下載成功," + s);
                                            context.getSharedPreferences("com.oubowu.sdk.sp", Context.MODE_PRIVATE).edit().putString("pName", filename).apply();
                                            addPatchInfo(context, filename, patches);
                                            mCountDownLatch.countDown();
                                        }
                                    }

                                    @Override
                                    public void onProgress(Integer integer, long l) {
                                    }
                                });
                            } else {
                                // 本地已經保存了的話,保存名稱,直接使用
                                context.getSharedPreferences("com.oubowu.sdk.sp", Context.MODE_PRIVATE).edit().putString("pName", filename).apply();
                                addPatchInfo(context, filename, patches);
                                mCountDownLatch.countDown();
                            }
                        }

                    } else {
                        // 此sdk版本沒有補丁
                        mCountDownLatch.countDown();
                    }
                }
            }
        });

        try {
            // 阻塞等待網絡請求結束
            mCountDownLatch.await();
        } catch (InterruptedException e) {
            e.printStackTrace();
        }

        return patches;
    }

    /**
     * 添加補丁信息
     *
     * @param context
     * @param fileName
     * @param patches
     */
    private void addPatchInfo(Context context, String fileName, List<Patch> patches) {
        // 解密下發的已加密補丁而且返回解密後的文件路徑,使用ndk保證解密安全性
        String dPatchPath = NdkHelper.p(context, fileName, false);
        File dPatchFile = new File(dPatchPath);
        if (dPatchFile.exists()) {
            // 解密文件存在的話,添加到補丁列表
            Patch patch = new Patch();
            patch.setName(dPatchFile.getName().replace(".jar", ""));
            patch.setLocalPath(dPatchFile.getPath().replace(".jar", ""));
            patch.setPatchesInfoImplClassFullName("com.oubowu.sdk.lib.PatchManipulateImp.PatchesInfoImpl");
            patches.add(patch);
        }
    }

    /**
     * @param context
     * @param patch
     * @return you can verify your patches here
     */
    @Override
    protected boolean verifyPatch(Context context, Patch patch) {
        //do your verification, put the real patch to patch
        //放到app的私有目錄
        patch.setTempPath(context.getCacheDir() + File.separator + "robust" + File.separator + patch.getName());
        //in the sample we just copy the file
        try {
            copy(patch.getLocalPath(), patch.getTempPath());
        } catch (Exception e) {
            e.printStackTrace();
            throw new RuntimeException("copy source patch to local patch error, no patch execute in path " + patch.getTempPath());
        }

        // 刪除解密的本地補丁
        patch.delete(patch.getLocalPath());

        return true;
    }

    ...

}複製代碼

4.補丁的生成和使用

在SdkTest.java我寫了一個會拋出NumberFormatException的方法

public static void callBugMethod(Context context) {

        String strFromCPlus = NdkHelper.getStrFromCPlus();

        Logger.e(strFromCPlus);

        int i = Integer.parseInt(strFromCPlus);

    }複製代碼

而後修復callBugMethod方法,添加一個靜態內部類

@Modify
    public static void callBugMethod(Context context) {

        String strFromCPlus = NdkHelper.getStrFromCPlus();

        Logger.e(strFromCPlus);

        try {
            int i = Integer.parseInt(strFromCPlus);
        } catch (NumberFormatException e) {
            e.printStackTrace();
            Logger.e("我使用Robust熱更新把空指針修復啦!!!");
        }

        MyClass.call();

    }

    @Add
    public static class MyClass {
        public static void call() {
            Logger.e("我使用Robust熱更新新增了一個靜態內部類");
        }
    }複製代碼

將 \RobustForSdk\gradle.properties isPatchModule設爲true,將\sdk\robustjar\develop\release文件夾下的mapping.txt以及methodsMap.robust放到\sdk\robust文件夾

# Application模式,Robust須要是Application才能插入代碼和打補丁
    isAppModule=true
    # Application模式下開啓這個就能夠打補丁
    isPatchModule=true複製代碼

執行assembleDevelopRelease,獲得如下提示,即說明生成補丁成功

* What went wrong:
Execution failed for task ':sdk:transformClassesWithAutoPatchTransformForDevelopRelease'.
> auto patch end successfully複製代碼

QQ截圖20171102112715.png
QQ截圖20171102112715.png

補丁爲了保證安全性,須要加密後再上傳到Bmob後臺,爲了方便操做,我用MFC寫了個Window程序,能夠看下 AesWindowsApplication

加解密工具.png
加解密工具.png

上傳到Bmob後臺

Bmob後臺.png
Bmob後臺.png

在app模塊使用library-develop-release.aar,在主頁面MainActivity.class進行SDK初始化,SdkTest.init會執行補丁請求和加載的邏輯

@Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        mTvHello = (TextView) findViewById(R.id.tv_hello);

        findViewById(R.id.bt_sdk).setOnClickListener(this);

        checkPermissionAndCallSdk();

    }

    private void checkPermissionAndCallSdk() {
        boolean checkPermission = MPermissionUtils.getInstance()
                .checkPermission(this, REQUEST_PERMISSION_SUCCESS, Manifest.permission.READ_PHONE_STATE, Manifest.permission.WRITE_EXTERNAL_STORAGE,
                        Manifest.permission.READ_EXTERNAL_STORAGE);
        if (checkPermission) {
            SdkTest.init(this);
        }
    }複製代碼

第一次啓動APP而且斷網的狀況下,在點擊事件調用SDK有bug的方法

@Override
    public void onClick(View v) {
        switch (v.getId()) {
            case R.id.bt_sdk:
                SdkTest.callBugMethod(this);
                mTvHello.setText(NdkHelper.getStrFromCPlus());
                break;
            default:
                break;
        }
    }複製代碼

Logcat打印如下字符串轉換整型拋出的異常

FATAL EXCEPTION: main
    Process: com.oubowu.robustforsdk, PID: 18306
    java.lang.NumberFormatException: Invalid int: "Hello from C++"複製代碼

將網絡打開,第二次啓動APP,網絡請求下發正常的話,打印出咱們使用的補丁版本是3,對應SDK版本是2.6.0;下載成功後放在/data/data/(包名)/files;而且應用成功了

11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ Thread: main
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ From.Code  (PolicyQuery.java:264)
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: │    a$1.done  (PatchManipulateImp.java:95)
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ Patch{sdkVersion='2.6.0', patchVersion='3', patchUrl=http://bmob-cdn-14435.b0.upaiyun.com/2017/10/16/37e20fe240ae64a2803b3d5ed7047be9.jar} com.oubowu.sdk.Patch@42588d90
11-03 09:48:43.893 19711-19711/com.oubowu.robustforsdk E/PRETTY_LOGGER: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────

11-03 09:51:35.513 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
11-03 09:51:35.513 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ Thread: main
11-03 09:51:35.513 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:51:35.523 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ of.onPostExecute  (BmobFileDownloader.java:2095)
11-03 09:51:35.523 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │    a$1$1.done  (PatchManipulateImp.java:109)
11-03 09:51:35.523 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:51:35.523 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ 下載成功,/data/data/com.oubowu.robustforsdk/files/e-patch-2.6.0-4.jar
11-03 09:51:35.523 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────

11-03 09:51:35.533 20875-20875/com.oubowu.robustforsdk E/secret: /data/data/com.oubowu.robustforsdk/files/e-patch-2.6.0-4.jar
11-03 09:51:35.533 20875-20875/com.oubowu.robustforsdk E/secret: /data/data/com.oubowu.robustforsdk/files/d-e-patch-2.6.0-4.jar
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ Thread: pool-1-thread-1
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ PatchExecutor.applyPatchList  (PatchExecutor.java:71)
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: │    SdkTest$3.onPatchApplied  (SdkTest.java:97)
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ PatchExecutor
11-03 09:51:35.623 20875-20891/com.oubowu.robustforsdk E/PRETTY_LOGGER: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────複製代碼

這時候點擊點擊事件,能夠從log看到已經使用了修復的代碼

11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err: java.lang.NumberFormatException: Invalid int: "Hello from C++"
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at java.lang.Integer.invalidInt(Integer.java:137)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at java.lang.Integer.parse(Integer.java:374)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at java.lang.Integer.parseInt(Integer.java:365)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at java.lang.Integer.parseInt(Integer.java:331)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.oubowu.sdk.lib.PatchManipulateImp.SdkTestPatch.callBugMethod(SdkTestPatch.java:163)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.oubowu.sdk.lib.PatchManipulateImp.SdkTestPatchControl.accessDispatch(PatchTemplate.java)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.meituan.robust.PatchProxy.accessDispatch(PatchProxy.java:61)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.oubowu.sdk.SdkTest.callBugMethod(SdkTest.java)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.oubowu.robustforsdk.MainActivity.onClick(MainActivity.java:46)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at android.view.View.performClick(View.java:4444)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at android.view.View$PerformClick.run(View.java:18457)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at android.os.Handler.handleCallback(Handler.java:733)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at android.os.Handler.dispatchMessage(Handler.java:95)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at android.os.Looper.loop(Looper.java:136)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at android.app.ActivityThread.main(ActivityThread.java:5113)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at java.lang.reflect.Method.invokeNative(Native Method)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at java.lang.reflect.Method.invoke(Method.java:515)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:796)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:612)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk W/System.err:     at dalvik.system.NativeStart.main(Native Method)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ Thread: main
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ SdkTestPatchControl.accessDispatch  (PatchTemplate.java:-1)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │    SdkTestPatch.callBugMethod  (SdkTestPatch.java:166)
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ 我使用Robust熱更新把空指針修復啦!!!
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk D/robust: invoke static  method is       No:  23  e
11-03 09:53:46.213 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ┌────────────────────────────────────────────────────────────────────────────────────────────────────────────────
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ Thread: main
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ SdkTestPatch.callBugMethod  (SdkTestPatch.java:169)
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │    SdkTest$MyClass.call  (SdkTest.java:176)
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: ├┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄┄
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: │ 我使用Robust熱更新新增了一個靜態內部類
11-03 09:53:46.223 20875-20875/com.oubowu.robustforsdk E/PRETTY_LOGGER: └────────────────────────────────────────────────────────────────────────────────────────────────────────────────複製代碼

以上就是我實踐SDK熱修復的思路和方法,但願能給讀者帶來一些用處。以爲不錯的話能夠給項目RobustForSdk一個star,你的確定是對我最大的鼓勵哦!

相關文章
相關標籤/搜索