您的位置:首页 > 其它

关于使用腾讯 Bugly 平台 Tinker开源热修复框架的 项目集成

2016-12-23 15:10 543 查看
Tinker是什么

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

      
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
相关文章推荐