关于使用腾讯 Bugly 平台 Tinker开源热修复框架的 项目集成
2016-12-23 15:10
543 查看
Tinker是什么
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。
它主要包括以下几个部分:
gradle编译插件:
核心sdk库:
非gradle编译用户的命令行版本:
为什么使用Tinker
总的来说:
AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。
特别是在Android N之后,由于混合编译的inline策略修改,对于市面上的各种方案都不太容易解决。而Tinker热补丁方案不仅支持类、So以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做bugfix,甚至可以替代功能的发布。Tinker已运行在微信的数亿Android设备上,那么为什么你不使用Tinker呢?
由于原理与系统限制,Tinker有以下已知问题:
Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;
由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
在Android N上,补丁对应用启动时间有轻微的影响;
不支持部分三星android-21机型,加载补丁时会主动抛出
tinker的一般模式并不支持加固,需要使用usePreGeneratedPatchDex模式,即提前生成补丁模式。某些加固工具可能会将非exported的四大组件类名替换,这些类将无法修改。对于Android N之后的设备,本模式可能会因为内联而出现问题,建议过滤N之后的设备;
对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。
如何使用Tinker
接下来带大家 在项目中一步步使用 Tinker,我们选一个平台 我选的是bugly ,腾讯的,当然你也可以选择其他的平台,
TinkerPatch ,这个也可以 ,但以后肯定要收费的, TinkerPatch平台文档。
首先第一步
添加插件依赖
工程根目录下“build.gradle”文件中添加:
[b]第二步:在app module的“build.gradle”文件中添加(我的配置):[/b]
apply plugin: 'com.android.application'
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
dependencies {
compile fileTree(dir:
'libs', include: ['*.jar'])
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:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0
compile 'com.tencent.bugly:nativecrashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.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 {
debug {
storeFile file("../KeyDianda.jks")
storePassword "111111"
keyAlias "key_dianda"
keyPassword
"111111"
}
release {
storeFile file("../****.jks")//修改成自己的秘钥
storePassword "***"// 改成自己的密码
keyAlias "***"
keyPassword
"***"
}
}
//
默认配置
defaultConfig {
applicationId "com.tinker.ddinfo"
minSdkVersion 14
targetSdkVersion 23
versionCode
200
versionName
"2.0.0"
//
开启multidex
multiDexEnabled
true
//
以Proguard的方式手动加入要放到Main.dex中的类
multiDexKeepProguard file("keep_in_main_dex.txt")
}
//
构建类型
buildTypes {
// release {
// //
不显示Log
// buildConfigField "boolean", "LOG_DEBUG", "false"
// //
是否进行混淆
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// signingConfig signingConfigs.release
// applicationVariants.all { variant ->
// variant.outputs.each { output ->
// def outputFile = output.outputFile
// if (outputFile != null && outputFile.name.endsWith('.apk')) {
// //
输出apk名称为ddmall1.0.1_6.apk boohee_v1.0_2015-01-15_wandoujia.apk
// def fileName = "tinker${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
// output.outputFile = new File(outputFile.parent, fileName)
// }
// }
// }
// }
//
// debug {
// debuggable true
// minifyEnabled false
// signingConfig signingConfigs.debug
// }
release {
//
是否进行混淆
minifyEnabled
false
signingConfig
signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled
false
signingConfig
signingConfigs.debug
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
java.srcDirs = ['src/main/java']
}
}
repositories {
flatDir {
dirs 'libs'
}
maven { url "https://jitpack.io" }
}
// //
多渠道打包
// productFlavors {
// "Dianda" {}
//// "Tencent" {}
// }
//
// productFlavors.all { flavor ->
// flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
// }
lintOptions {
checkReleaseBuilds
false
abortOnError
false
}
splits {
abi {
enable true
reset()
include 'armeabi'//,'x86'//, 'x86_64', 'arm64-v8a', 'armeabi-v7a'
universalApk
false
}
}
sourceSets.main {
jniLibs.srcDirs = ['libs']
}
}
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
*
打一个补丁包需要改哪些东西?
修复bug的类、修改资源
修改oldApk配置
修改tinkerId
*/
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-1223-13-43-58.apk"
// proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/app-release-1223-13-43-58-mapping.txt"
// resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-release-1223-13-43-58-R.txt"
// Todo需要修改
tinkerBuildFlavorDirectory = "${bakPath}/app-release-1223-13-43-58"
}
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签名是否一致
//
编译相关配置项
// Todo需要修改tinkerId每次打补丁时tinkerId跟打基准包时得tinkerId不能一样
buildConfig {
applyMapping = getApplyMappingPath()
// 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
applyResourceMapping = getApplyResourceMappingPath()//可选,设置R.txt文件,通过旧apk文件保持ResId的分配
tinkerId = "bugbugbug_v2.0.0gggggg"
}
// dex相关配置项
dex {
dexMode = "jar" //可选,默认为jar
usePreGeneratedPatchDex = true
// 可选,默认为false
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
// Todo需要修改 自己的包名
loader = ["com.tencent.tinker.loader.*",
"com.tinker.ddinfo.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"
}
}
}
}
}
}
*这里要注意 需要修改tinkerId每次打补丁时tinkerId跟打基准包时得tinkerId不能一样
第三步:
package com.tinker.ddinfo;
import com.tencent.tinker.loader.app.TinkerApplication;
import com.tencent.tinker.loader.shareutil.ShareConstants;
/**
* 自定义Application.
*
* 注意:这个类集成TinkerApplication类,这里面不做任何操作,所有Application的代码都会放到ApplicationLike继承类当中<br/>
* <pre>
* 参数解析:
* 参数1:int tinkerFlags 表示Tinker支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
* 参数2:String delegateClassName Application代理类 这里填写你自定义的ApplicationLike
* 参数3:String loaderClassName
Tinker的加载器,使用默认即可
* 参数4:boolean tinkerLoadVerifyFlag 加载dex或者lib是否验证md5,默认为false
* </pre>
* @author wenjiewu
* @since 2016/11/15
*/
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "com.tinker.ddinfo.SampleApplicationLike",//修改成自己的包名
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
第四步:自定义ApplicationLike
package com.tinker.ddinfo;
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;
import com.tinker.ddinfo.utils.ExampleConfig;
/**
* 自定义ApplicationLike类.
*
* 注意:这个类是Application的代理类,以前所有在Application的实现必须要全部拷贝到这里<br/>
*
* @author wenjiewu
* @since 2016/11/7
*/
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();
// TODO: 这里进行Bugly初始化
// 设置开发设备
Bugly.setIsDevelopmentDevice(getApplication(), true);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
Bugly.init(getApplication(), ExampleConfig.BUGLY_APP_ID, 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);
// TODO: 安装tinker
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
第五步:Androidmanifest 文件
第六步:MainActivity 这个类是为了配合显示 修改bug
第七步:根据命令打基准包 (就是apk包) 在bakAPK 目录下
第八步:这时要注意打完基准包后 ,需要修改bug 我在mainActivity 里面修改了一行Toast代码
然后修改 app 下面的build.gradle 文件
记得需要修改 tinkerId
第九步:执行tinkerPatchRelease 命令 打补丁包
第十步 上传 到腾讯Bugly 平台。
需要注意的是 在手机应用上 需要 冷启动 ,杀掉进程 重新 进入应用才会成功。 有时候需要等一两分钟
最后附上 demo 下载链接
http://download.csdn.net/detail/weitf/9719535
Tinker是微信官方的Android热补丁解决方案,它支持动态下发代码、So库以及资源,让应用能够在不需要重新安装的情况下实现更新。当然,你也可以使用Tinker来更新你的插件。
它主要包括以下几个部分:
gradle编译插件:
tinker-patch-gradle-plugin
核心sdk库:
tinker-android-lib
非gradle编译用户的命令行版本:
tinker-patch-cli.jar
为什么使用Tinker
总的来说:
AndFix作为native解决方案,首先面临的是稳定性与兼容性问题,更重要的是它无法实现类替换,它是需要大量额外的开发成本的;
Robust兼容性与成功率较高,但是它与AndFix一样,无法新增变量与类只能用做的bugFix方案;
Qzone方案可以做到发布产品功能,但是它主要问题是插桩带来Dalvik的性能问题,以及为了解决Art下内存地址问题而导致补丁包急速增大的。
特别是在Android N之后,由于混合编译的inline策略修改,对于市面上的各种方案都不太容易解决。而Tinker热补丁方案不仅支持类、So以及资源的替换,它还是2.X-7.X的全平台支持。利用Tinker我们不仅可以用做bugfix,甚至可以替代功能的发布。Tinker已运行在微信的数亿Android设备上,那么为什么你不使用Tinker呢?
Tinker的已知问题
由于原理与系统限制,Tinker有以下已知问题:Tinker不支持修改AndroidManifest.xml,Tinker不支持新增四大组件;
由于Google Play的开发者条款限制,不建议在GP渠道动态更新代码;
在Android N上,补丁对应用启动时间有轻微的影响;
不支持部分三星android-21机型,加载补丁时会主动抛出
"TinkerRuntimeException:checkDexInstall failed";
tinker的一般模式并不支持加固,需要使用usePreGeneratedPatchDex模式,即提前生成补丁模式。某些加固工具可能会将非exported的四大组件类名替换,这些类将无法修改。对于Android N之后的设备,本模式可能会因为内联而出现问题,建议过滤N之后的设备;
对于资源替换,不支持修改remoteView。例如transition动画,notification icon以及桌面图标。
如何使用Tinker
接下来带大家 在项目中一步步使用 Tinker,我们选一个平台 我选的是bugly ,腾讯的,当然你也可以选择其他的平台,
TinkerPatch ,这个也可以 ,但以后肯定要收费的, TinkerPatch平台文档。
首先第一步
添加插件依赖
工程根目录下“build.gradle”文件中添加:
[b]第二步:在app module的“build.gradle”文件中添加(我的配置):[/b]
apply plugin: 'com.android.application'
def releaseTime() {
return new Date().format("yyyy-MM-dd", TimeZone.getTimeZone("UTC"))
}
dependencies {
compile fileTree(dir:
'libs', include: ['*.jar'])
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:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如1.2.0
compile 'com.tencent.bugly:nativecrashreport:latest.release'//其中latest.release指代最新版本号,也可以指定明确的版本号,例如2.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 {
debug {
storeFile file("../KeyDianda.jks")
storePassword "111111"
keyAlias "key_dianda"
keyPassword
"111111"
}
release {
storeFile file("../****.jks")//修改成自己的秘钥
storePassword "***"// 改成自己的密码
keyAlias "***"
keyPassword
"***"
}
}
//
默认配置
defaultConfig {
applicationId "com.tinker.ddinfo"
minSdkVersion 14
targetSdkVersion 23
versionCode
200
versionName
"2.0.0"
//
开启multidex
multiDexEnabled
true
//
以Proguard的方式手动加入要放到Main.dex中的类
multiDexKeepProguard file("keep_in_main_dex.txt")
}
//
构建类型
buildTypes {
// release {
// //
不显示Log
// buildConfigField "boolean", "LOG_DEBUG", "false"
// //
是否进行混淆
// minifyEnabled false
// proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
// signingConfig signingConfigs.release
// applicationVariants.all { variant ->
// variant.outputs.each { output ->
// def outputFile = output.outputFile
// if (outputFile != null && outputFile.name.endsWith('.apk')) {
// //
输出apk名称为ddmall1.0.1_6.apk boohee_v1.0_2015-01-15_wandoujia.apk
// def fileName = "tinker${defaultConfig.versionName}_${releaseTime()}_${variant.productFlavors[0].name}.apk"
// output.outputFile = new File(outputFile.parent, fileName)
// }
// }
// }
// }
//
// debug {
// debuggable true
// minifyEnabled false
// signingConfig signingConfigs.debug
// }
release {
//
是否进行混淆
minifyEnabled
false
signingConfig
signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'),'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled
false
signingConfig
signingConfigs.debug
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
java.srcDirs = ['src/main/java']
}
}
repositories {
flatDir {
dirs 'libs'
}
maven { url "https://jitpack.io" }
}
// //
多渠道打包
// productFlavors {
// "Dianda" {}
//// "Tencent" {}
// }
//
// productFlavors.all { flavor ->
// flavor.manifestPlaceholders = [CHANNEL_VALUE: name]
// }
lintOptions {
checkReleaseBuilds
false
abortOnError
false
}
splits {
abi {
enable true
reset()
include 'armeabi'//,'x86'//, 'x86_64', 'arm64-v8a', 'armeabi-v7a'
universalApk
false
}
}
sourceSets.main {
jniLibs.srcDirs = ['libs']
}
}
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
*
打一个补丁包需要改哪些东西?
修复bug的类、修改资源
修改oldApk配置
修改tinkerId
*/
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-1223-13-43-58.apk"
// proguard mapping file to build patch apk
tinkerApplyMappingPath = "${bakPath}/app-release-1223-13-43-58-mapping.txt"
// resource R.txt to build patch apk, must input if there is resource changed
tinkerApplyResourcePath = "${bakPath}/app-release-1223-13-43-58-R.txt"
// Todo需要修改
tinkerBuildFlavorDirectory = "${bakPath}/app-release-1223-13-43-58"
}
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签名是否一致
//
编译相关配置项
// Todo需要修改tinkerId每次打补丁时tinkerId跟打基准包时得tinkerId不能一样
buildConfig {
applyMapping = getApplyMappingPath()
// 可选,设置mapping文件,建议保持旧apk的proguard混淆方式
applyResourceMapping = getApplyResourceMappingPath()//可选,设置R.txt文件,通过旧apk文件保持ResId的分配
tinkerId = "bugbugbug_v2.0.0gggggg"
}
// dex相关配置项
dex {
dexMode = "jar" //可选,默认为jar
usePreGeneratedPatchDex = true
// 可选,默认为false
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
// Todo需要修改 自己的包名
loader = ["com.tencent.tinker.loader.*",
"com.tinker.ddinfo.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"
}
}
}
}
}
}
*这里要注意 需要修改tinkerId每次打补丁时tinkerId跟打基准包时得tinkerId不能一样
第三步:
package com.tinker.ddinfo;
import com.tencent.tinker.loader.app.TinkerApplication;
import com.tencent.tinker.loader.shareutil.ShareConstants;
/**
* 自定义Application.
*
* 注意:这个类集成TinkerApplication类,这里面不做任何操作,所有Application的代码都会放到ApplicationLike继承类当中<br/>
* <pre>
* 参数解析:
* 参数1:int tinkerFlags 表示Tinker支持的类型 dex only、library only or all suuport,default: TINKER_ENABLE_ALL
* 参数2:String delegateClassName Application代理类 这里填写你自定义的ApplicationLike
* 参数3:String loaderClassName
Tinker的加载器,使用默认即可
* 参数4:boolean tinkerLoadVerifyFlag 加载dex或者lib是否验证md5,默认为false
* </pre>
* @author wenjiewu
* @since 2016/11/15
*/
public class SampleApplication extends TinkerApplication {
public SampleApplication() {
super(ShareConstants.TINKER_ENABLE_ALL, "com.tinker.ddinfo.SampleApplicationLike",//修改成自己的包名
"com.tencent.tinker.loader.TinkerLoader", false);
}
}
第四步:自定义ApplicationLike
package com.tinker.ddinfo;
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;
import com.tinker.ddinfo.utils.ExampleConfig;
/**
* 自定义ApplicationLike类.
*
* 注意:这个类是Application的代理类,以前所有在Application的实现必须要全部拷贝到这里<br/>
*
* @author wenjiewu
* @since 2016/11/7
*/
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();
// TODO: 这里进行Bugly初始化
// 设置开发设备
Bugly.setIsDevelopmentDevice(getApplication(), true);
// 这里实现SDK初始化,appId替换成你的在Bugly平台申请的appId
Bugly.init(getApplication(), ExampleConfig.BUGLY_APP_ID, 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);
// TODO: 安装tinker
Beta.installTinker(this);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallback(Application.ActivityLifecycleCallbacks callbacks) {
getApplication().registerActivityLifecycleCallbacks(callbacks);
}
}
第五步:Androidmanifest 文件
第六步:MainActivity 这个类是为了配合显示 修改bug
第七步:根据命令打基准包 (就是apk包) 在bakAPK 目录下
第八步:这时要注意打完基准包后 ,需要修改bug 我在mainActivity 里面修改了一行Toast代码
然后修改 app 下面的build.gradle 文件
记得需要修改 tinkerId
第九步:执行tinkerPatchRelease 命令 打补丁包
第十步 上传 到腾讯Bugly 平台。
需要注意的是 在手机应用上 需要 冷启动 ,杀掉进程 重新 进入应用才会成功。 有时候需要等一两分钟
最后附上 demo 下载链接
http://download.csdn.net/detail/weitf/9719535
相关文章推荐
- [Android]腾讯Tinker热修复框架简单使用
- 热修复框架 Tinker、自动内存泄漏检测工具 MLeaksFinder等10大开源项目
- 在真实项目中使用第三方或开源代的代码,组件,中间件,框架的基本规则
- 【转载】使用Json比用string返回数据更友好,也更面向对象一些 |Asp.net MVC 2.0 + Unity 2.0(IoC) + EF4.0 实例:RoRoWoBlog 开源项目框架代码
- 关于使用开源项目SlidingMenu的问题
- 使用SSH集成框架开发项目步骤
- 关于使用开源项目PhotoView-Mastor的问题记录
- (zt)目前正在使用的框架和一些开源的项目
- Android最好用的侧滑栏开源项目SlidingMenu,集成方法与使用
- [开源项目-MyBean轻量级配置框架] 使用MyBean快速搭建分模块的应用程序(主页面的TAB)(DLL-MDI)
- 安卓开发笔记——关于开源项目SlidingMenu的使用介绍(仿QQ5.0侧滑菜单)
- 关于在项目中使用开源项目的疑惑,恳请大家给点意见!
- 关于White框架在项目中的使用分析
- 关于经典开源框架STRUTS2的使用
- 第一次做安卓项目使用的开源框架列表
- 关于经典开源框架STRUTS2的使用
- 持续集成之道:在你的开源项目中使用Travis CI
- iOS开源项目之日志框架CocoaLumberjack的使用
- 由于Python本身自带的界面库功能并不强大,我们使用Python+wxPython作为界面开发平台,在公司新的项目中进行界面开发。开发过程使用Eclipse+PyDev 作为集成开发环境。产品发布时使用Py2exe进行打包。如今,在我的团队中,Pytho
- iOS平台软件开发工具(一)-新建的工程使用CocoaPods工具集成第三方框架