集成Tinker最主要的兩個部分:一個是接入文檔,另一個是github上的demo,可以將tinker-sample-android單獨下載下來,運行,參考裏邊的配置。集成中遇到了一些問題,記載一下
集成步驟
1.在項目的build.gradle,配置
dependencies {
classpath 'com.android.tools.build:gradle:3.2.1'
classpath ("com.tencent.tinker:tinker-patch-gradle-plugin:${TINKER_VERSION}")
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
問題1.如果出現下圖的Bug
說明sdk中有方法已經過時,需要將gradle 版本調低一些
2.在app的build.gradle,引入tinker庫
dependencies {
.......
//optional, help to generate the final application
//生成application時使用
compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {changing=true}
//tinker's main Android lib
implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"){changing=true}
implementation "com.android.support:multidex:1.0.3"
}
在gradle.properties裏定義TINKER_VERSION=1.9.1,方便管理
3.參考demo和文檔,對build.gradle進行配置
apply plugin: 'com.android.application'
android {
compileSdkVersion 28
defaultConfig {
applicationId "drag.mandala.com.tinkerdemo"
minSdkVersion 22
targetSdkVersion 28
versionCode 1
versionName "1.0"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions {
annotationProcessorOptions {
includeCompileClasspath true
}
}
}
signingConfigs {
release {
try {
storeFile file("test.jks")
storePassword "aaaaaa"
keyAlias "test1"
keyPassword "aaaaaa"
} catch (ex) {
throw new InvalidUserDataException(ex.toString())
}
}
}
buildTypes {
release {
//打開混淆,要不然不能生產mapping文件
minifyEnabled true
//配置signConfig,否則提示can't the get signConfig for this build
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:28.0.0'
implementation 'com.android.support.constraint:constraint-layout:1.1.3'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.2'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.2'
//optional, help to generate the final application
//生成application時使用
compileOnly("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") {changing=true}
//tinker's main Android lib
implementation("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}"){changing=true}
implementation "com.android.support:multidex:1.0.3"
}
def bakPath = file("${buildDir}/bakApk")
ext{
tinkerEnable = true
//tinkerOldApkPath,tinkerApplyMappingPath,tinkerApplyResourceMappingPath初始值爲${bakPath},
//打包成功以後,將build/bakApk下生成的文件名放到${bakPath}後,修改 minifyEnabled爲 true,要不然不會生產mapping文件
tinkerOldApkPath = "${bakPath}/app-release-1126-14-00-54.apk"
tinkerId = "1.0"
tinkerApplyMappingPath = "${bakPath}/app-release-1126-14-00-54-mapping.txt"
tinkerApplyResourceMappingPath = "${bakPath}/app-release-1126-14-00-54-R.txt"
}
def buildWithTinker(){
return ext.tinkerEnable
}
def getOldApkPath(){
return ext.tinkerOldApkPath
}
def getApplyMappingPath(){
return ext.tinkerApplyMappingPath
}
def getApplyResourceMappingPath(){
return ext.tinkerApplyResourceMappingPath
}
def getTinkerIdValue(){
return ext.tinkerId
}
if(buildWithTinker()){
//啓用了tinker
apply plugin: 'com.tencent.tinker.patch'
//所有tinker相關的參數配置
tinkerPatch{
oldApk = getOldApkPath()//指定old apk文件路徑
ignoreWarning = false //不忽略tinker的警告,有警告就停止生成patch文件
useSign = true //強制patch文件使用簽名
tinkerEnable = buildWithTinker() //指定是否啓用tinker
buildConfig{
applyMapping = getApplyMappingPath() //指定old apk打包時所使用的混淆文件
applyResourceMapping = getApplyResourceMappingPath() // 指定old apk的資源文件
tinkerId = getTinkerIdValue() // 指定TinkerId
keepDexApply = false
isProtectedApp = false
supportHotplugComponent = false
}
dex{
dexMode = "jar" //只能是'raw'或者'jar'
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"] // 指定dex文件目錄,第二個是官方例子配置
loader = ["drag.mandala.com.tinkerdemo.MyTinkerApplication"] //指定加載patch文件時用到的類
}
lib{
pattern = ["lib/*/*.so"]
}
res{
pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] //指定tinker可以修改的所有資源文件路徑
// ignoreChange = ["assets/sample.txt"] //在編譯時會忽略該文件的新增、刪除與修改
ignoreChange = ["assets/sample_meta.txt"]
largeModSize = 100 //資源修改的默認值 如果大於largeModSize,tinker將使用bsdiff算法。
// 這可以降低補丁包的大小,但是會增加合成時的複雜度。默認大小爲100kb
}
//不必須的配置
//說明配置信息
packageConfig{
configField("patchMessage", "tinker is sample to use")
configField("platform", "all")
configField("patchVersion", "1.0")
}
/**
* if you don't use zipArtifact or path, we just use 7za to try
*/
sevenZip {
/**
* optional,default '7za'
* the 7zip artifact path, it will use the right 7za with your platform
*/
zipArtifact = "com.tencent.mm:SevenZip:1.1.10"
/**
* optional,default '7za'
* you can specify the 7za path yourself, it will overwrite the zipArtifact value
*/
// path = "/usr/local/bin/7za"
}
}
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
if (variant.metaClass.hasProperty(variant, 'packageApplicationProvider')) {
def packageAndroidArtifact = variant.packageApplicationProvider.get()
if (packageAndroidArtifact != null) {
try {
from new File(packageAndroidArtifact.outputDirectory.getAsFile().get(), variant.outputs.first().apkData.outputFileName)
} catch (Exception e) {
from new File(packageAndroidArtifact.outputDirectory, variant.outputs.first().apkData.outputFileName)
}
} else {
from variant.outputs.first().mainOutputFile.outputFile
}
} else {
from variant.outputs.first().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"
from "${buildDir}/intermediates/symbol_list/${variant.dirName}/R.txt"
into destPath
rename { String fileName ->
fileName.replace("R.txt", "${newFileNamePrefix}-R.txt")
}
}
}
}
}
}
}
問題2: 如果出現下圖的提示
需要配置:
**問題3:**出現下圖的Bug
在gradle.properties配置android.enableAapt2=true
問題4: 不能生成mapping文件,這個需要打開混淆
minifyEnabled true
如果提示“can’t the get signConfig for this build”,需要配置signConfig
signingConfig signingConfigs.release
完整配置
buildTypes {
release {
//打開混淆,要不然不能生產mapping文件
minifyEnabled true
//配置signConfig,否則提示can't the get signConfig for this build
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
4.寫一個繼承自DefaultApplicationLike的類
@DefaultLifeCycle(application = ".MyTinkerApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
public class CustomTinkerLike extends DefaultApplicationLike
{
public CustomTinkerLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent)
{
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@Override
public void onBaseContextAttached(Context base)
{
super.onBaseContextAttached(base);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);
TinkerManager.setTinkerApplicationLike(this);
TinkerManager.initFastCrashProtect();
//should set before tinker is installed
TinkerManager.setUpgradeRetryEnable(true);
//installTinker after load multiDex
//or you can put com.tencent.tinker.** to main dex
TinkerManager.installTinker(this);
Tinker tinker = Tinker.with(getApplication());
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
}
}
build一下,AndroidManifest.xml裏的<application 的name設置成註解裏的application,
<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/AppTheme"
android:name=".MyTinkerApplication">
...
配置權限,否則onReceiveUpgradePatch的時候會閃退
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
5.其他的類可以複製demo裏的,別忘了在AndroidManifest.xml配置SampleResultService
6.配置完成以後,先打包
打包成功以後,查看build文件夾,
將這三個文件名賦值到build.gradle中的對應位置
將app-release-1126-14-00-54.apk安裝到手機上,作爲一個有Bug的app,修改bug,生成patch文件
生成成功以後,繼續看build文件夾
這個放到手機對應的目錄下,目錄地址是onReceiveUpgradePatch方法裏的
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
點擊按鈕加載patch,殺死進程,重啓,就會是修改後的app
問題5: 如果報com.tencent.tinker.build.util.TinkerPatchException: resource must contain resources.arsc pattern,檢查build.gradle裏的res下的pattern中的"resources.arsc"是否寫錯。還有就是通過AndroidStudio方式生成差異包的時候如果不小心修改了XML也會報這個錯
問題6: onLoadPatchListenerReceiveFail: patch receive fail: /storage/emulated/0/patch_signed_7zip.apk, code: -2
檢查文件路徑是否和代碼裏的一致,查看清單文件中是否有添加SD卡訪問權限,如果是Android7.0要考慮FileProvider(Android7.0不支持直接訪問sd卡)
問題7: Tinker does not support instant run mode, please trigger build by assembleDebug or disable instant run
去掉instance run