即使所有的努力都付诸东流那又怎样
或许你遇到过这样的情况——新版本的app刚提交应用市场没多久,突然发现有一个很严重的bug。这时候该怎么办呐,传统的方式就是修复bug重新提交应用市场,且不说审核需要时间,对用户也是极大的伤害。热修复恰好能解决这样的问题,其实大厂已经开源了很多热修复框架。bugly推出热修复框架已经有一段时间了,腾讯出品,必属于精品,不用担心会出大的问题。下面我来讲一下集成的过程。
https://bugly.qq.com/docs/user-guide/instruction-manual-android-hotfix/?v=20161206145314 这个是官方的文档,不过你若是按照官方文档一步步做下去,不会发现根本不行。
-1- 工程根目录下“build.gradle”文件中添加
// 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" }
-2-自定义ApplicationLike,把之前Application中的初始化方法全部都搬到这个类中,Appid替换成bugly上自己项目的Appid
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); }
}
-3-自定义Application
public class App extends TinkerApplication{
public App() {
super(ShareConstants.TINKER_ENABLE_ALL, "SampleApplicationLike带包名地址",
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
-4-AndroidManifest.xml配置
- 1. 权限配置
- 2.配置FileProvider(Android N之后配置,若是N之前这步不用配置也可以
android:name="android.support.v4.content.FileProvider"
android:authorities="com.tencent.bugly.hotfix.fileProvider"
android:exported="false"
android:grantUriPermissions="true">
android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths"/>
provider_paths文件内容如下
-5-在到app目录下新建
keep_in_main_dex.txt对应的内容如下:
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!
-6-app目录下的build.gradle文件配置
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
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" } } }
}
} - task type, you want to bak
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"
}
特别注意下面的三块地方:
release.keystore和debug.keystore换成你自己签名,当然对应的storePassword,keyAlias和keyPassword也要做对应的修改
这两块在热更新时要用app-release-1209-11-41.apk对应是要修复的apk,
修复版和线上版本tinkerId要一致。
-7- 最后别忘了加上混淆
第三方的bugly
-dontwarn com.tencent.bugly.**
-keep public class com.tencent.bugly.*{;}
-8-接下来就可以生成测试包了,你可以造一个bug来测试,然后运行生成测试包,不过这里的运行跟以往不同,按下面点击右侧的Gradle,若出现Nothing to show,那就点一下我红圈标注的部分
然后运行assembleRelease生成测试包
测试包在下图所示的位置
然后把测试包上传,可以上传到测试分发,然后将app下载下来,看看是否存在你故意设置的bug,如果本身就没bug就搞笑了。
-9-然后生成修复包
将你故意设置的bug修复,注意我上面说的地方,修改ext下oldApp的路径。然后运行tinkerPatchRelease生成修复包。
修复包生成的位置在patch目录下
然后将发布新补丁,补丁可能需要1到2分钟生效。重新运行你之前下载的app,注意这里需要2次启动才能生效。看看之前的bug是否已在不知不觉中修复。