集成騰訊bugly的熱修復功能sdk步驟

首先爲什麼要集成bugly熱修復。市面上有其他的熱修復框架,爲什麼就用bugly?這裏給出2張圖大家就明白了。




引用騰訊bugly官網的一段話:

  • 無需關注Tinker是如何合成補丁的
  • 無需自己搭建補丁管理後臺
  • 無需考慮後臺下發補丁策略的任何事情
  • 無需考慮補丁下載合成的時機,處理後臺下發的策略
  • 我們提供了更加方便集成Tinker的方式
  • 我們提供應用升級一站式解決方案
進入正題:接入流程主要是以下幾個步驟:

  • 打基準包安裝並上報聯網(注:填寫唯一的tinkerId)
  • 對基準包的bug修復(可以是Java代碼變更,資源的變更)
  • 修改基準包路徑、填寫補丁包tinkerId、mapping文件路徑、resId文件路徑
  • 執行tinkerPatchRelease打Release版本補丁包
  • 選擇app/build/outputs/patch目錄下的補丁包並上傳(注:不要選擇tinkerPatch目錄下的補丁包,不然上傳會有問題)
  • 編輯下發補丁規則,點擊立即下發
  • 重啓基準包,請求補丁策略(SDK會自動下載補丁併合成)
  • 再次重啓基準包,檢驗補丁應用結果

1:新建基準包工程項目(人爲製造有BUG的app版本)

 btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
//                String str = LoadBugClass.getBugString();
                String str = BugClass.bug();
                Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
            }
        });
public class BugClass {

    public static String bug(){
        String str = null;
        int str_length = str.length();
        return "this is bug class";
    }
}
這個可以看出點擊一個按鈕會報空指針異常。

2:接着就是配置相關屬性和添加一個插件依賴了。

官方教程地址:點擊打開鏈接

下面也給出我自己配置的過程。

首先在最外層的build.gradle文件中添加依賴,看下圖:



其次新建sampleapplication和sampleapplicationLike兩個java類

package com.henry.testappbugly;

import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.content.res.AssetManager;
import android.content.res.Resources;
import android.os.Build;
import android.support.multidex.MultiDex;

import com.tencent.bugly.Bugly;
import com.tencent.bugly.beta.Beta;
import com.tencent.tinker.loader.app.DefaultApplicationLike;

/**
 * Created by W61 on 2016/11/29.
 */

public class SampleApplicationLike extends DefaultApplicationLike {

    public static final String TAG = "Tinker.SampleApplicationLike";

    public SampleApplicationLike(Application application, int tinkerFlags,
                                 boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime,
                                 long applicationStartMillisTime, Intent tinkerResultIntent, Resources[] resources,
                                 ClassLoader[] classLoader, AssetManager[] assetManager) {
        super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime,
                applicationStartMillisTime, tinkerResultIntent, resources, classLoader,
                assetManager);
    }


    @Override
    public void onCreate() {
        super.onCreate();
        // 這裏實現SDK初始化,appId替換成你的在Bugly平臺申請的appId
        Bugly.init(getApplication(), "", true);
    }


    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    @Override
    public void onBaseContextAttached(Context base) {
        super.onBaseContextAttached(base);
        // you must install multiDex whatever tinker is installed!
        MultiDex.install(base);

        // 安裝tinker
        // TinkerManager.installTinker(this); 替換成下面Bugly提供的方法
        Beta.installTinker(this);
    }

    @TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
    public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
        getApplication().registerActivityLifecycleCallbacks(callbacks);
    }

}

package com.henry.testappbugly;

import com.tencent.tinker.loader.app.TinkerApplication;
import com.tencent.tinker.loader.shareutil.ShareConstants;

/**
 * Created by W61 on 2016/11/29.
 */

public class SampleApplication extends TinkerApplication {
    public SampleApplication() {
        super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike所在的包名路徑",
                "com.tencent.tinker.loader.TinkerLoader", false);
    }
}

在在Androidmanifest.xml文件中配置權限及application類名
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="com.henry.testappbugly">

    <application
        android:name=".SampleApplication"
        android:allowBackup="true"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:supportsRtl="true"
        android:theme="@style/AppTheme">
        <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />

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


        <!--API 24以上配置-->
        <provider
            android:name="android.support.v4.content.FileProvider"
            android:authorities="com.tencent.bugly.hotfix.fileProvider"
            android:exported="false"
            android:grantUriPermissions="true">
            <meta-data
                android:name="android.support.FILE_PROVIDER_PATHS"
                android:resource="@xml/provider_paths"/>
        </provider>

    </application>


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

</manifest>

在到res目錄下:



<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
    <!-- 這裏配置的兩個外部存儲路徑是升級SDK下載的文件可能存在的路徑 -->
    <!-- /storage/emulated/0/Download/com.bugly.upgrade.demo/.beta/apk-->
    <external-path name="beta_external_path" path="Download/"/>
    <!--/storage/emulated/0/Android/data/com.bugly.upgrade.demo/files/apk/-->
    <external-path name="beta_external_files_path" path="Android/data/"/>
</paths>


在到app目錄下新建:

# you can copy the tinker keep rule at
# build/intermediates/tinker_intermediates/tinker_multidexkeep.pro

-keep class com.tencent.tinker.loader.** {
    *;
}

-keep class com.tencent.bugly.hotfix.SampleApplication {
    *;
}

-keep public class * implements com.tencent.tinker.loader.app.ApplicationLifeCycle {
    *;
}

-keep public class * extends com.tencent.tinker.loader.TinkerLoader {
    *;
}

-keep public class * extends com.tencent.tinker.loader.app.TinkerApplication {
    *;
}

# here, it is your own keep rules.
# you must be careful that the class name you write won't be proguard
# but the tinker class above is OK, we have already keep for you!

然後在混淆文件.pro中添加這幾句代碼(bugly都有說明解釋)

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.**{*;}


最後就是app目錄下的build.gradle文件配置了:

apply plugin: 'com.android.application'

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    compile 'com.android.support:appcompat-v7:24.1.1'

    // 多dex配置
    compile "com.android.support:multidex:1.0.1"
    // 集成Bugly熱更新aar(灰度時使用方式)
//    compile(name: 'bugly_crashreport_upgrade-1.2.0', ext: 'aar')
    compile "com.tencent.bugly:crashreport_upgrade:1.2.0"
}


android {
    compileSdkVersion 23
    buildToolsVersion "23.0.2"

    // 編譯選項
    compileOptions {
        sourceCompatibility JavaVersion.VERSION_1_7
        targetCompatibility JavaVersion.VERSION_1_7
    }

    // recommend
    dexOptions {
        jumboMode = true
    }
    // 簽名配置
    signingConfigs {
        // 簽名配置
        signingConfigs {
            release {
                try {
                    storeFile file("./keystore/release.keystore")
                    storePassword "testres"
                    keyAlias "testres"
                    keyPassword "testres"
                } catch (ex) {
                    throw new InvalidUserDataException(ex.toString())
                }
            }

            debug {
                storeFile file("./keystore/debug.keystore")
            }
        }
    }

    defaultConfig {
        applicationId "com.henry.testappbugly"
        minSdkVersion 14
        targetSdkVersion 23
        versionCode 2
        versionName "2.0"

        // 開啓multidex
        multiDexEnabled true
        // 以Proguard的方式手動加入要放到Main.dex中的類
        multiDexKeepProguard file("keep_in_main_dex.txt")
    }
    buildTypes {

        release {
            minifyEnabled true
            signingConfig signingConfigs.release
            proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        }
        debug {
            debuggable true
            minifyEnabled false
            signingConfig signingConfigs.debug
        }
    }
    sourceSets {
        main {
            jniLibs.srcDirs = ['libs']
        }
    }

    repositories {
        flatDir {
            dirs 'libs'
        }
    }

    lintOptions {
        checkReleaseBuilds false
        abortOnError false
    }
}


def gitSha() {
    try {
        String gitRev = 'git rev-parse --short HEAD'.execute(null, project.rootDir).text.trim()
        if (gitRev == null) {
            throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
        }
        return gitRev
    } catch (Exception e) {
        throw new GradleException("can't get git rev, you should add git to system path or just input test value, such as 'testTinkerId'")
    }
}


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

/**
 * you can use assembleRelease to build you base apk
 * use tinkerPatchRelease -POLD_APK=  -PAPPLY_MAPPING=  -PAPPLY_RESOURCE= to build patch
 * add apk from the build/bakApk
 */
ext {
    // for some reason, you may want to ignore tinkerBuild, such as instant run debug build?
    tinkerEnabled = true

    // for normal build
    // old apk file to build patch apk
    tinkerOldApkPath = "${bakPath}/app-release-1201-09-46-25.apk"
    // proguard mapping file to build patch apk
    tinkerApplyMappingPath = "${bakPath}/app-release-1201-09-46-25-mapping.txt"
    // resource R.txt to build patch apk, must input if there is resource changed
    tinkerApplyResourcePath = "${bakPath}/app-release-1201-09-46-25-R.txt"

    // only use for build all flavor, if not, just ignore this field
    tinkerBuildFlavorDirectory = "${bakPath}/app-release-1201-09-46-25"
}

def getOldApkPath() {
    return hasProperty("OLD_APK") ? OLD_APK : ext.tinkerOldApkPath
}

def getApplyMappingPath() {
    return hasProperty("APPLY_MAPPING") ? APPLY_MAPPING : ext.tinkerApplyMappingPath
}

def getApplyResourceMappingPath() {
    return hasProperty("APPLY_RESOURCE") ? APPLY_RESOURCE : ext.tinkerApplyResourcePath
}

def getTinkerIdValue() {
    return hasProperty("TINKER_ID") ? TINKER_ID : gitSha()
}

def buildWithTinker() {
    return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}

def getTinkerBuildFlavorDirectory() {
    return ext.tinkerBuildFlavorDirectory
}

/**
 * 更多Tinker插件詳細的配置,參考:https://github.com/Tencent/tinker/wiki
 */
if (buildWithTinker()) {
    // 依賴tinker插件
    apply plugin: 'com.tencent.tinker.patch'
    apply plugin: 'com.tencent.bugly.tinker-support'

    tinkerSupport {
    }

    // 全局信息相關配置項
    tinkerPatch {
        oldApk = getOldApkPath() //必選, 基準包路徑

        ignoreWarning = false // 可選,默認false

        useSign = true // 可選,默認true, 驗證基準apk和patch簽名是否一致

        // 編譯相關配置項
        buildConfig {
            applyMapping = getApplyMappingPath() //  可選,設置mapping文件,建議保持舊apk的proguard混淆方式
            applyResourceMapping = getApplyResourceMappingPath() // 可選,設置R.txt文件,通過舊apk文件保持ResId的分配
            tinkerId = "可以是簽名版本號字符串等等比如:assdhfkdshfksdhfuksfhuk" // 必選,默認爲null
        }

        // dex相關配置項
        dex {
            dexMode = "jar" // 可選,默認爲jar
            usePreGeneratedPatchDex = true // 可選,默認爲false
            pattern = ["classes*.dex",
                       "assets/secondary-dex-?.jar"]
            // 必選
            loader = ["com.tencent.tinker.loader.*",
                      "SampleApplication所在的全路徑",
            ]
        }

        // lib相關的配置項
        lib {
            pattern = ["lib/armeabi/*.so"]
        }

        // res相關的配置項
        res {
            pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"]
            ignoreChange = ["assets/sample_meta.txt"]
            largeModSize = 100
        }

        // 用於生成補丁包中的'package_meta.txt'文件
        packageConfig {
            configField("patchMessage", "tinker is sample to use")

            configField("platform", "all")

            configField("patchVersion", "1.0")
        }

        // 7zip路徑配置項,執行前提是useSign爲true
        sevenZip {
            zipArtifact = "com.tencent.mm:SevenZip:1.1.10" // optional
            //  path = "/usr/local/bin/7za" // optional
        }
    }

    List<String> flavors = new ArrayList<>();
    project.android.productFlavors.each { flavor ->
        flavors.add(flavor.name)
    }
    boolean hasFlavors = flavors.size() > 0
    /**
     * bak apk and mapping
     */
    android.applicationVariants.all { variant ->
        /**
         * task type, you want to bak
         */
        def taskName = variant.name
        def date = new Date().format("MMdd-HH-mm-ss")

        tasks.all {
            if ("assemble${taskName.capitalize()}".equalsIgnoreCase(it.name)) {

                it.doLast {
                    copy {
                        def fileNamePrefix = "${project.name}-${variant.baseName}"
                        def newFileNamePrefix = hasFlavors ? "${fileNamePrefix}" : "${fileNamePrefix}-${date}"

                        def destPath = hasFlavors ? file("${bakPath}/${project.name}-${date}/${variant.flavorName}") : bakPath
                        from variant.outputs.outputFile
                        into destPath
                        rename { String fileName ->
                            fileName.replace("${fileNamePrefix}.apk", "${newFileNamePrefix}.apk")
                        }

                        from "${buildDir}/outputs/mapping/${variant.dirName}/mapping.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("mapping.txt", "${newFileNamePrefix}-mapping.txt")
                        }

                        from "${buildDir}/intermediates/symbols/${variant.dirName}/R.txt"
                        into destPath
                        rename { String fileName ->
                            fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
                        }
                    }
                }
            }
        }
    }
    project.afterEvaluate {
        //sample use for build all flavor for one time
        if (hasFlavors) {
            task(tinkerPatchAllFlavorRelease) {
                group = 'tinker'
                def originOldPath = getTinkerBuildFlavorDirectory()
                for (String flavor : flavors) {
                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Release")
                    dependsOn tinkerTask
                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}ReleaseManifest")
                    preAssembleTask.doFirst {
                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 15)
                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release.apk"
                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-mapping.txt"
                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-release-R.txt"

                    }

                }
            }
            task(tinkerPatchAllFlavorDebug) {
                group = 'tinker'
                def originOldPath = getTinkerBuildFlavorDirectory()
                for (String flavor : flavors) {
                    def tinkerTask = tasks.getByName("tinkerPatch${flavor.capitalize()}Debug")
                    dependsOn tinkerTask
                    def preAssembleTask = tasks.getByName("process${flavor.capitalize()}DebugManifest")
                    preAssembleTask.doFirst {
                        String flavorName = preAssembleTask.name.substring(7, 8).toLowerCase() + preAssembleTask.name.substring(8, preAssembleTask.name.length() - 13)
                        project.tinkerPatch.oldApk = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug.apk"
                        project.tinkerPatch.buildConfig.applyMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-mapping.txt"
                        project.tinkerPatch.buildConfig.applyResourceMapping = "${originOldPath}/${flavorName}/${project.name}-${flavorName}-debug-R.txt"
                    }

                }
            }
        }
    }


}



最後run as生成有bug的基準包app


在去騰訊bugly官網將這個基準包上傳上去即可。



-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------

接下來,製作補丁包。

由於剛纔點擊按鈕報空指針,下面將代碼稍做改動如下:

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

        btn = (Button) findViewById(R.id.btn);
        btn.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                String str = LoadBugClass.getBugString();
//                String str = BugClass.bug();
                Toast.makeText(MainActivity.this,str,Toast.LENGTH_SHORT).show();;
            }
        });
    }
public class LoadBugClass {
    /**
     * 獲取bug字符串.
     *
     * @return 返回bug字符串
     */
    public static String getBugString() {
//        BugClass bugClass = new BugClass();
        return "iS OK";
    }
}
這樣點擊按鈕就會彈出is ok了不會報錯。

修改配置文件:



這裏注意點,補丁包是基於基準包所生成的patch文件並不是版本升級,所以此處補丁包不需要修改versioncode,versionname

然後雙擊下圖中所指地方:


稍等片刻就會出現下圖中類容:


其中的patch_signed_7zip.apk就是補丁包了。將這個補丁包上傳到騰訊bugly即可。

注意:上傳完補丁包點擊了立即下發,就需要重新啓動基準包策略。從有bug版本的app到修復有一個時間差的。估計1到2分鐘左右才能看到效果。


附上集成過程中可能遇到的坑解決辦法地址:點擊打開鏈接

最後附上自己寫的demo地址:點擊打開鏈接

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章