bugly热修复的使用

即使所有的努力都付诸东流那又怎样

或许你遇到过这样的情况——新版本的app刚提交应用市场没多久,突然发现有一个很严重的bug。这时候该怎么办呐,传统的方式就是修复bug重新提交应用市场,且不说审核需要时间,对用户也是极大的伤害。热修复恰好能解决这样的问题,其实大厂已经开源了很多热修复框架。bugly推出热修复框架已经有一段时间了,腾讯出品,必属于精品,不用担心会出大的问题。下面我来讲一下集成的过程。

https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20161206145314 这个是官方的文档,不过你若是按照官方文档一步步做下去,不会发现根本不行。

-1- 工程根目录下“build.gradle”文件中添加
<pre>
// tinker gradle插件
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.7.5')
// tinkersupport插件
classpath "com.tencent.bugly:tinker-support:latest.release"

task wrapper(type: Wrapper){
    gradleVersion = "2.14.1"
 }

</pre>
-2-自定义ApplicationLike,把之前Application中的初始化方法全部都搬到这个类中,Appid替换成bugly上自己项目的Appid
<pre>
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();

    Bugly.init(getApplication(), "Appid", true);
}


@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
    super.onBaseContextAttached(base);

    MultiDex.install(base);
    Beta.installTinker(this);
}

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

}
</pre>
-3-自定义Application
<pre>
public class App extends TinkerApplication{
public App() {
super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike带包名地址",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
</pre>

-4-AndroidManifest.xml配置

  • 1. 权限配置
    <pre>
    <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"/>
    </pre>
  • 2.配置FileProvider(Android N之后配置,若是N之前这步不用配置也可以
    <pre>
    <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>
    </pre>

provider_paths文件内容如下
<pre>
<?xml version="1.0" encoding="utf-8"?>
<paths xmlns:android="http://schemas.android.com/apk/res/android">
<external-path name="beta_external_path" path="Download/"/>

<external-path name="beta_external_files_path" path="Android/data/"/>
</paths>
</pre>

-5-在到app目录下新建

keepin.png

keep_in_main_dex.txt对应的内容如下:
<pre>

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!

</pre>

-6-app目录下的build.gradle文件配置
<pre>
apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "25.0.1"

// 编译选项
compileOptions {
    sourceCompatibility JavaVersion.VERSION_1_7
    targetCompatibility JavaVersion.VERSION_1_7
}

dexOptions {
    jumboMode = true
}

// 签名配置
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.llf.update"
    minSdkVersion 11
    targetSdkVersion 25
    versionCode 1
    versionName "1.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/")

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-1209-11-19-41.apk"
// proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}\\app-release-1209-11-19-41-mapping.txt"
// resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}\\app-release-1209-11-19-41-R.txt"

}

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()) {
    apply plugin: 'com.tencent.bugly.tinker-support'
    // 依赖tinker插件
    apply plugin: 'com.tencent.tinker.patch'

    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 = "addd91ae18b1f9c685c2d51f97391723" // 必选,默认为null
     }
    
     // dex相关配置项
     dex {
         dexMode = "jar" // 可选,默认为jar
         usePreGeneratedPatchDex = true // 可选,默认为false
         pattern = ["classes*.dex",
                    "assets/secondary-dex-?.jar"]
         // 必选
         loader = ["com.tencent.tinker.loader.*",
                   "com.llf.update.App",
         ]
     }
    
     // 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
     }
    

    }
    }

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 {
    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"
             }
    
         }
     }
    

    }
    }

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.1'
testCompile 'junit:junit:4.12'
compile "com.android.support:multidex:1.0.1"
compile "com.tencent.bugly:crashreport_upgrade:1.2.0"
}
</pre>

特别注意下面的三块地方:
release.keystore和debug.keystore换成你自己签名,当然对应的storePassword,keyAlias和keyPassword也要做对应的修改

签名.png

这两块在热更新时要用app-release-1209-11-41.apk对应是要修复的apk,
修复版和线上版本tinkerId要一致。
![旧版.png](http://upload-images.jianshu.io/upload_images/1976633-76576993ee0b2613.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240)

-7-最后别忘了加上混淆
<pre>

第三方的bugly

-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.*{;}
</pre>

-8-接下来就可以生成测试包了,你可以造一个bug来测试,然后运行生成测试包,不过这里的运行跟以往不同,按下面点击右侧的Gradle,若出现Nothing to show,那就点一下我红圈标注的部分

测试.png

然后运行assembleRelease生成测试包

测试.png

测试包在下图所示的位置

测试包位置.png

然后把测试包上传,可以上传到测试分发,然后将app下载下来,看看是否存在你故意设置的bug,如果本身就没bug就搞笑了。

-9-然后生成修复包
将你故意设置的bug修复,注意我上面说的地方,修改ext下oldApp的路径。然后运行tinkerPatchRelease生成修复包。

修复.png

修复包生成的位置在patch目录下


修复包.png

然后将发布新补丁,补丁可能需要1到2分钟生效。重新运行你之前下载的app,注意这里需要2次启动才能生效。看看之前的bug是否已在不知不觉中修复。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 194,390评论 5 459
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 81,821评论 2 371
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 141,632评论 0 319
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 52,170评论 1 263
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 61,033评论 4 355
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 46,098评论 1 272
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 36,511评论 3 381
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 35,204评论 0 253
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 39,479评论 1 290
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 34,572评论 2 309
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 36,341评论 1 326
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 32,213评论 3 312
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 37,576评论 3 298
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 28,893评论 0 17
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 30,171评论 1 250
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 41,486评论 2 341
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 40,676评论 2 335

推荐阅读更多精彩内容