Android热修复框架Tinker接入
4步接入热修复框架tinker
- 项目build.gradle的配置
在dependencies添加
classpath('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
app目录下build.gradle的配置
这个需要配置的地方比较多,主要参考tinker 的GitHub上的app/build.gradleapply plugin: 'com.android.application' apply plugin: 'com.tencent.tinker.patch' android { signingConfigs { release { keyAlias 'key0' keyPassword '123456' storeFile file('../keystore/skill.jks') storePassword '123456' } } compileSdkVersion 25 buildToolsVersion '26.0.2' defaultConfig { applicationId "com.yk.skill.androidskillplatform" minSdkVersion 17 targetSdkVersion 25 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" multiDexEnabled true buildConfigField "String", "MESSAGE", "\"I am the base apk\"" buildConfigField "String", "TINKER_ID", "\"1.0\"" buildConfigField "String", "PLATFORM", "\"all\"" } dexOptions { jumboMode = true } buildTypes { release { minifyEnabled true proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' signingConfig signingConfigs.release } } 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' }) annotationProcessor 'com.tencent.tinker:tinker-android-anno:1.9.1' provided 'com.tencent.tinker:tinker-android-anno:1.9.1' compile 'com.tencent.tinker:tinker-android-lib:1.9.1' compile 'com.android.support:multidex:1.0.1' compile 'io.reactivex.rxjava2:rxjava:2.1.7' compile 'com.squareup.retrofit2:retrofit:2.3.0' } compileOptions { targetCompatibility 1.8 sourceCompatibility 1.8 } } repositories { mavenCentral() google() } 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 javaVersion = JavaVersion.VERSION_1_7 def bakPath = file("${buildDir}/bakApk/") ext { tinkerEnabled = true tinkerOldApkPath = "${bakPath}/app-release-0109-10-01-47.apk" tinkerApplyMappingPath = "${bakPath}/app-release-0109-10-01-47-mapping.txt" tinkerApplyResourcePath = "${bakPath}/app-release-0109-10-01-47-R.txt" tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47" } 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 } if (buildWithTinker()) { apply plugin: 'com.tencent.tinker.patch' tinkerPatch { oldApk = getOldApkPath() ignoreWarning = false useSign = true tinkerEnable = buildWithTinker() buildConfig { applyMapping = getApplyMappingPath() applyResourceMapping = getApplyResourceMappingPath() tinkerId = getTinkerIdValue() keepDexApply = false isProtectedApp = false supportHotplugComponent = false } dex { dexMode = "jar" pattern = ["classes*.dex", "assets/secondary-dex-?.jar"] loader = [ "tinker.sample.android.app.BaseBuildInfo" ] } lib { pattern = ["lib/*/*.so"] } res { pattern = ["res/*", "assets/*", "resources.arsc", "AndroidManifest.xml"] ignoreChange = ["assets/sample_meta.txt"] largeModSize = 100 } packageConfig { configField("patchMessage", "tinker is sample to use") configField("platform", "all") configField("patchVersion", "1.0") } sevenZip { zipArtifact = "com.tencent.mm:SevenZip:1.1.10" } } List<String> flavors = new ArrayList<>(); project.android.productFlavors.each { flavor -> flavors.add(flavor.name) } boolean hasFlavors = flavors.size() > 0 def date = new Date().format("MMdd-HH-mm-ss") android.applicationVariants.all { variant -> def taskName = variant.name 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.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" 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" } } } } } }
这里需要注意下Android–>defaultConfig下的TINKER_ID这个有可能报tinker_id错误,我是把这里修改成一个默认的值。
buildConfigField "String", "TINKER_ID", "\"1.0\""
还有一个地方需要针对性的修改:这里需要填入你原始包的对应的名词,如果没有修改过build.gradle文件,则默认在app/build/bakApk目录下
ext { tinkerEnabled = true tinkerOldApkPath = "${bakPath}/app-release-0109-10-01-47.apk" tinkerApplyMappingPath = "${bakPath}/app-release-0109-10-01-47-mapping.txt" tinkerApplyResourcePath = "${bakPath}/app-release-0109-10-01-47-R.txt" }
- 创建一个application继承DefaultApplicationLike,
package com.yk.skill.androidskillplatform;
import android.annotation.TargetApi;
import android.app.Application;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.support.multidex.MultiDex;
import com.tencent.tinker.anno.DefaultLifeCycle;
import com.tencent.tinker.lib.tinker.Tinker;
import com.tencent.tinker.lib.tinker.TinkerInstaller;
import com.tencent.tinker.loader.app.DefaultApplicationLike;
import com.tencent.tinker.loader.shareutil.ShareConstants;
@DefaultLifeCycle(application = "com.yk.skill.androidskillplatform.TinkerApplicationLike",flags = ShareConstants.TINKER_ENABLE_ALL,loadVerifyFlag = false)
public class SelfTinkerApplicationLike extends DefaultApplicationLike{
public SelfTinkerApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
/**
* install multiDex before install tinker
* so we don't need to put the tinker lib classes in the main dex
*
* @param base
*/
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
MultiDex.install(base);
TinkerInstaller.install(this);
}
}
- 在需要patch的地方调用
TinkerInstaller.onReceiveUpgradePatch(context,patchPkgPath+"xxx.apk")
TinkerInstaller.onReceiveUpgradePatch(context,patchPkgPath+”xxx.apk”)
tinker更新包的生成
- 先生成一个基础包:点击Android-studio的右侧的gradle,然后按图示展开,最后点击assembleRelease生成基础包(基础包的位置在app/build/bakApk目录下)
- 修改build.gradle中对应的名词:生成基础包后,按照图示的标记修改build.gradle中内容,必须要对应
- 生成patch包:按照图示生成patch包
- 将patch包放入到sd卡中对应的位置即可
patch包的位置对应就是“TinkerInstaller.onReceiveUpgradePatch”这个方法中对应的位置,包名和路径必须匹配
TinkerInstaller.onReceiveUpgradePatch(context,patchPkgPath+"xxx.apk")
问题分析
1. 出现code=-2 则检查热更新的路径,获取检查热更新文件是否有权限
2. tinker_id出错,把tinker_id写成一个默认值