好久都没有写博客了,最近看了看关于热更新的内容。主要有阿里的,腾讯的,而实验的则是使用的是微信的热修复 Tinker ,以及在使用了 flavor 后的 热修复。
本文的内容是基于 AS3.0.1,注意:本例所截图的内容可能会和讲解的不一样,原因是,讲解的是按照versionCode 1,versionName "1.0"来的,截图是经过多次版本更新后来的
一. 首先集成微信的 Tinker,其github的链接:https://github.com/Tencent/tinker ,参考sample进行配置:
(1)在 project 目录下的 build.gradle 中的 dependencies 中添加
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')
(2)在 module 目录下的 build.gradle 中的 dependencies 中添加:
// 可选,用于生成application类
compileOnly('com.tencent.tinker:tinker-android-anno:1.9.1') { changing = true }
// tinker的核心库
implementation('com.tencent.tinker:tinker-android-lib:1.9.1') { changing = true }
annotationProcessor("com.tencent.tinker:tinker-android-anno:1.9.1") { changing = true }
其中的几个库引用后面的 { changing = true }不能少
(3)在 gradle.properties 中添加:
android.enableAapt2=false
在 module 的 build.gradle 中的 defaultConfig 中添加 :
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
这样在编译的时候才不报~~错误
(4)添加 keystore.jks 到项目中配置好 signingConfigs,该 signingConfigs 要在 buildTypes 之前,否则报错
(5)在 module 的 build.gradle 中添加如下配置:(该配置值包含热更新,不包含多渠道打包)
直接添加在最外层
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'")
}
}
// 热更新时,旧的 .apk,R.txt 目录就在本目录下
def bakPath = file("./tinker-old/")
ext {
// 每次更新的 tinkerId 都保持了不同,所以这里使用了 versionName 和 versionCode
// tinkerId 可以随意填写
tinkerId = "tinker_id_" + android.defaultConfig.versionName + "_" + android.defaultConfig.versionCode
//是否打开tinker的功能
tinkerEnabled = true
// 旧的 .apk 既本次更新前的版本,该版本必须存在,否则编译不过
// 在 bakPath 目录下相应的版本版本号填写在此处
tinkerOldApkPath = "${bakPath}/app-release-1.5-6.apk"
// 该版本为更新的版本,在 bakPath 目录下相应的版本版本号填写在此处
tinkerApplyMappingPath = "${bakPath}/app-release-1.6-7-mapping.txt"
// 该版本为更新的版本,在 bakPath 目录下相应的版本版本号填写在此处
tinkerApplyResourcePath = "${bakPath}/app-release-1.6-7-R.txt"
}
def getOldApkPath() {
return ext.tinkerOldApkPath
}
def getApplyMappingPath() {
return ext.tinkerApplyMappingPath
}
def getApplyResourceMappingPath() {
return ext.tinkerApplyResourcePath
}
def getTinkerIdValue() {
return ext.tinkerId
}
def buildWithTinker() {
return ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}
if (buildWithTinker()) {
// 必须有
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
oldApk = getOldApkPath()
ignoreWarning = true
useSign = true
buildConfig {
applyMapping = getApplyMappingPath()
applyResourceMapping = getApplyResourceMappingPath()
tinkerId = getTinkerIdValue()
/**
* 是否开启加固 ,默认 false
*/
isProtectedApp = false
}
dex {
dexMode = "jar"
pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]
}
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"
}
}
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 = "${fileNamePrefix}-" + android.defaultConfig.versionName + "-" + android.defaultConfig.versionCode
// def destPath = bakPath
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")
}
}
}
}
}
}
}
其中需要注意的是:
1. bakPath 的路径可以自己指定;
2. ext 目录下的版本号(如果是新项目,没有更新的,则所有的版本
内容都相同,如:app-release-1.0-1.apk",app-release-1.0-1-mapping.txt,app-release-1.0-1-R.txt,其中 1 为
versionCode ,1.0 为 versionName),待更新的版本的 .apk 一定不能少;
3. tinkerPatch 下的 ignoreWarning 这里设置的为 true ,tinkerId 要有值,要不然无法通过;
4. applicationVariants 下面的 copy 方法中的 from variant.outputs.first().outputFile 要注意,不要写成了
from variant.outputs.outputFile 否则在 更新的时候会出错,def destPath = bakPath 在热更新时也可以,
但是在使用 flavor 打包时,容易出现 .apk 找不到,所有还是建议写成上文中的方式。
二. 上文中添加了 compileOnly('com.tencent.tinker:tinker-android-anno:1.9.1') { changing = true } 用于生产 application 类,
所以自定义了一个 MyApplicationLike 类,具体的参照 MyApplicationLike 该类:
@DefaultLifeCycle(application = ".MyApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false)
public class MyApplicationLike extends DefaultApplicationLike {
public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
TinkerInstaller.install(this);
}
@Override
public void onCreate() {
super.onCreate();
// 原有Application逻辑可以写在这里
}
}
其中注解当中 DefaultLifeCycle 里面的 MyApplication 为本项目的 application 的 name。
三. 在 AndroidManifest 的 application 里面添加上文 MyApplicationLike 注解当中的类: android:name=".MyApplication",并添加
相应的读写存储的权限:
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
四. 打包,根据 module 目录下 build.gradle 中的相应文件进行打包,打包好后,在项目 app 目录下会增加一个 release 文件夹,该
文件下就有打包好的 app-release.apk。同时在 app 目录下还会生成一个 tinker-old 目录,该目录就为上文中配置的 bakPath。
本目录下的东西暂时不要删除,当有新版本更新时,这时的版本为旧版本,在更新时,需要使用。
五. 安装好打包的 .apk 后,修改部分代码;再修改 versionCode 和 versionName 的值,如 versionCode 2 versionName "1.1";
再次进行打包,打包好后就在 tinker-old 目录中就会新增: app-release-1.1-2.apk,app-release-1.1-2-R.txt。
本图中的 apk 为多层更新后的版本。
六. 配置 module 目录下 build.gradle 中的 ext 当中的版本号,如:
tinkerOldApkPath = "${bakPath}/app-release-1.0-1.apk"
tinkerApplyMappingPath = "${bakPath}/app-release-1.1-2-mapping.txt"
tinkerApplyResourcePath = "${bakPath}/app-release-1.1-2-R.txt"
图中的版本为多次更新后的。
七. 可以在 AndroidStudio 左下角的 Terminal 当中输入 gradlew tinkerPatchRelease ,或者在 Gradle projects 中找到相应的 project 运行即可。
当看到 Terminal 当中更新成功后,会有个 Success 什么的提示。这时就只需把 app/build/outputs/apk/tinkerPatch 下的
patch_signed_7zip.apk 复制到相应的文件当中(由于本例是使用 File file = new File(getExternalCacheDir(), "/patch_signed_7zip.apk");
创建的文件,所以在手机当中 Android/data/"包名"/cache 当中)。点击 “下载补丁” 即可,更新成功后,需要再次杀死进程,再次
启动即可看到更新后的版本。
MainActivity:
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
initViews();
}
private void initViews() {
final File file = new File(getExternalCacheDir(), "/patch_signed_7zip.apk");
if (!file.exists()) {
try {
file.createNewFile();
} catch (IOException e) {
e.printStackTrace();
}
}
// final String str = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
Button loadPatchButton = findViewById(R.id.loadPatch);
Log.d("TAG", "file=" + file.getAbsolutePath());
loadPatchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), str);
if (file.exists()) {
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), file.getPath());
} else {
Toast.makeText(MainActivity.this, "目录不存在", Toast.LENGTH_SHORT).show();
}
}
});
Button loadLibraryButton = findViewById(R.id.loadLibrary);
loadLibraryButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// #method 1, hack classloader library path
// TinkerLoadLibrary.installNavitveLibraryABI(getApplicationContext(), "armeabi");
// System.loadLibrary("stlport_shared");
}
});
Button cleanPatchButton = findViewById(R.id.cleanPatch);
cleanPatchButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Tinker.with(getApplicationContext()).cleanPatch();
}
});
Button killSelfButton = findViewById(R.id.killSelf);
killSelfButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
android.os.Process.killProcess(android.os.Process.myPid());
}
});
Button buildInfoButton = findViewById(R.id.showInfo);
buildInfoButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
boolean b = showInfo(MainActivity.this);
if (b) {
}
}
});
// 修改后,需要更新的内容
findViewById(R.id.main_btn).setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(new Intent(MainActivity.this, TwoActivity.class));
}
});
}
public boolean showInfo(Context context) {
// add more Build Info
final StringBuilder sb = new StringBuilder();
Tinker tinker = Tinker.with(getApplicationContext());
if (tinker.isTinkerLoaded()) {
sb.append(String.format("[patch is loaded] \n"));
// sb.append(String.format("[buildConfig TINKER_ID] %s \n", BuildInfo.TINKER_ID));
// sb.append(String.format("[buildConfig BASE_TINKER_ID] %s \n", BaseBuildInfo.BASE_TINKER_ID));
// sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildInfo.MESSAGE));
sb.append(String.format("[TINKER_ID] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName(ShareConstants.TINKER_ID)));
sb.append(String.format("[packageConfig patchMessage] %s \n", tinker.getTinkerLoadResultIfPresent().getPackageConfigByName("patchMessage")));
sb.append(String.format("[TINKER_ID Rom Space] %d k \n", tinker.getTinkerRomSpace()));
} else {
sb.append(String.format("[patch is not loaded] \n"));
// sb.append(String.format("[buildConfig TINKER_ID] %s \n", BuildInfo.TINKER_ID));
// sb.append(String.format("[buildConfig BASE_TINKER_ID] %s \n", BaseBuildInfo.BASE_TINKER_ID));
// sb.append(String.format("[buildConfig MESSSAGE] %s \n", BuildInfo.MESSAGE));
sb.append(String.format("[TINKER_ID] %s \n", ShareTinkerInternals.getManifestTinkerID(getApplicationContext())));
}
sb.append(String.format("[BaseBuildInfo Message] %s \n", BaseBuildInfo.TEST_MESSAGE));
final TextView v = new TextView(this);
v.setText(sb);
v.setGravity(Gravity.LEFT | Gravity.CENTER_VERTICAL);
v.setTextSize(TypedValue.COMPLEX_UNIT_DIP, 10);
v.setLayoutParams(new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT));
v.setTextColor(0xFF000000);
v.setTypeface(Typeface.MONOSPACE);
final int padding = 16;
v.setPadding(padding, padding, padding, padding);
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
builder.setCancelable(true);
builder.setView(v);
final AlertDialog alert = builder.create();
alert.show();
return true;
}
@Override
protected void onResume() {
super.onResume();
Utils.setBackground(false);
}
@Override
protected void onPause() {
super.onPause();
Utils.setBackground(true);
}
}
参照官方的 sample 修改了少量代码
八. flavor 更新,build.gradle 当中
(1)在 build.gradle 的相应位置添加 :
// 多dex 打包的类库
implementation "com.android.support:multidex:1.0.2"
(2)在 defaultConfig 中添加 :
flavorDimensions "versionCode"
multiDexEnabled true
/**
* buildConfig can change during patch!
* we can use the newly value when patch
*/
buildConfigField "String", "MESSAGE", "\"I am the base apk\""
/**
* client version would update with patch
* so we can get the newly git version easily!
*/
// buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
buildConfigField "String", "PLATFORM", "\"all\""
其中:flavorDimensions "versionCode" 是为了防止 AS3.0 以后会出现 Error:All flavors must now belong to a named flavor dimension 异常
(3)在 android 下添加:
dexOptions {
jumboMode = true
}
//用于测试 flavors
productFlavors {
flavor {
applicationId 'com.anti.updateonline'
}
flavor1 {
applicationId 'com.anti.updateonline.flavor1'
}
flavor2 {
applicationId 'com.anti.updateonline.flavor2'
}
}
注:此处采用了微信 sample 当中的方法,网上还有其他方法在 productFlavors 中进行添加
(4)在 if (buildWithTinker()) {} 方法中添加 :
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")
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"
}
}
}
}
}
在 buildConfig 下添加:keepDexApply = false
其中:def date = new Date().format("MMdd-HH-mm-ss") 的时间为, tinker-old 目录下的 app-0101-19-47-34 文件夹名后部分;
(5)在 ext 中添加:
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-0101-19-47-34"
project.android.productFlavors.each { flavor ->
tinkerOldApkPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor-release-1.8-9.apk"
tinkerApplyMappingPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor-release-1.9-10-mapping.txt"
tinkerApplyResourcePath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor-release-1.9-10-R.txt"
tinkerOldApkPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor1-release-1.8-9.apk"
tinkerApplyMappingPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor1-release-1.9-10-mapping.txt"
tinkerApplyResourcePath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor1-release-1.9-10-R.txt"
tinkerOldApkPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor2-release-1.8-9.apk"
tinkerApplyMappingPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor2-release-1.9-10-mapping.txt"
tinkerApplyResourcePath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor2-release-1.9-10-R.txt"
}
其中的 tinkerBuildFlavorDirectory = "${bakPath}/app-0101-19-47-34" 为(4)中介绍的目录;project.android.productFlavors.each { } 方法用于遍历 (3)处的 flavor1 等。本文最后会给出完整的 build.gradle
(6)拷贝官方 sample 当中的 TinkerManager,SampleResultService 等并在 AndroidManifest 进行相应的配置,MyApplicationLike类当中添加相应的代码。
MyApplicationLike 类:
@DefaultLifeCycle(application = ".MyApplication", flags = ShareConstants.TINKER_ENABLE_ALL, loadVerifyFlag = false)
public class MyApplicationLike extends DefaultApplicationLike {
public MyApplicationLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag,
long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
@Override
public void onBaseContextAttached(Context base) {
super.onBaseContextAttached(base);
MultiDex.install(base);
TinkerManager.setTinkerApplicationLike(this);
TinkerManager.initFastCrashProtect();
//should set before tinker is installed
TinkerManager.setUpgradeRetryEnable(true);
// TinkerInstaller.install(this);
TinkerManager.installTinker(this);
Tinker tinker = Tinker.with(getApplication());
}
@TargetApi(Build.VERSION_CODES.ICE_CREAM_SANDWICH)
public void registerActivityLifecycleCallbacks(Application.ActivityLifecycleCallbacks callback) {
getApplication().registerActivityLifecycleCallbacks(callback);
}
@Override
public void onCreate() {
super.onCreate();
// 原有Application逻辑可以写在这里
}
}
AndroidManifest 中:
<service
android:name=".service.SampleResultService"
android:exported="false"/>
(7)打包,在 app/build/outputs/apk 下生成 flavor, flavor1, flavor2 文件夹,其下还有相应的 apk,将生成的几个 apk 安装
在手机中,并且一定要将这生成的几个 apk 拷贝到 tinker-old/app-xxxx 目录下相同的包名目录中,如 flavor 到
tinker-old/app-xxxx/flavor 目录下,这是旧的 apk,不能拷贝更新后打包的 apk 到该目录下,否则不能不能实现更新成功。(这只是对于本例的配置而言)
(8)修改部分代码,改变 versionCode 和 versionName,再按照 tinker-old 中 app- 目录修改 ext 当中 project.android.productFlavors.each 方法中的 tinkerOldApkPath 等(如 flavor 文件中的 1.8-9.apk,新的 tinkerApplyMappingPath 按照 versionCode 和 versionName 的改变进行修改)。
(9)运行 AS 右上角的 Gradle tinker 下的 tinkerPatchAllFlavorRelease 即可得到所有flavor的补丁包。
(10)app/build/outputs/apk/flavor/tinkerPatch/flavor/release 目录下就有 patch_signed_7zip.apk 补丁包,拷贝各自的
patch_signed_7zip.apk 补丁包到各自的 cache 当中,运行,点击更新即可各自更新。
本例完整的 build.gradle
apply plugin: 'com.android.application'
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'")
}
}
android {
compileSdkVersion 26
defaultConfig {
applicationId "com.anti.updateonline"
minSdkVersion 15
targetSdkVersion 26
versionCode 10
versionName "1.9"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
javaCompileOptions { annotationProcessorOptions { includeCompileClasspath = true } }
flavorDimensions "versionCode"
multiDexEnabled true
/**
* buildConfig can change during patch!
* we can use the newly value when patch
*/
buildConfigField "String", "MESSAGE", "\"I am the base apk\""
/**
* client version would update with patch
* so we can get the newly git version easily!
*/
// buildConfigField "String", "TINKER_ID", "\"${getTinkerIdValue()}\""
buildConfigField "String", "PLATFORM", "\"all\""
}
//recommend
dexOptions {
jumboMode = true
}
signingConfigs {
release {
storeFile file("")
storePassword ""
keyAlias ""
keyPassword ""
v2SigningEnabled false
}
}
aaptOptions{
cruncherEnabled false
}
//use to test flavors support
productFlavors {
flavor {
applicationId 'com.anti.updateonline'
}
flavor1 {
applicationId 'com.anti.updateonline.flavor1'
}
flavor2 {
applicationId 'com.anti.updateonline.flavor2'
}
}
lintOptions {
checkReleaseBuilds false
abortOnError false
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig signingConfigs.release
}
debug {
signingConfig signingConfigs.release
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'com.android.support:appcompat-v7:26.1.0'
implementation 'com.android.support.constraint:constraint-layout:1.0.2'
testImplementation 'junit:junit:4.12'
androidTestImplementation 'com.android.support.test:runner:1.0.1'
androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1'
//可选,用于生成application类
compileOnly('com.tencent.tinker:tinker-android-anno:1.9.1') { changing = true }
//tinker的核心库
implementation('com.tencent.tinker:tinker-android-lib:1.9.1') { changing = true }
annotationProcessor("com.tencent.tinker:tinker-android-anno:1.9.1") { changing = true }
implementation "com.android.support:multidex:1.0.2"
}
def bakPath = file("./tinker-old/")
ext {
tinkerId = "tinker_id_" + android.defaultConfig.versionName + "_" + android.defaultConfig.versionCode
tinkerEnabled = true
tinkerOldApkPath = "${bakPath}/app-release-1.5-6.apk"
tinkerApplyMappingPath = "${bakPath}/app-release-1.6-7-mapping.txt"
tinkerApplyResourcePath = "${bakPath}/app-release-1.6-7-R.txt"
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-0101-19-47-34"
project.android.productFlavors.each { flavor ->
tinkerOldApkPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor-release-1.8-9.apk"
tinkerApplyMappingPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor-release-1.9-10-mapping.txt"
tinkerApplyResourcePath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor-release-1.9-10-R.txt"
tinkerOldApkPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor1-release-1.8-9.apk"
tinkerApplyMappingPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor1-release-1.9-10-mapping.txt"
tinkerApplyResourcePath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor1-release-1.9-10-R.txt"
tinkerOldApkPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor2-release-1.8-9.apk"
tinkerApplyMappingPath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor2-release-1.9-10-mapping.txt"
tinkerApplyResourcePath = "$tinkerBuildFlavorDirectory/${flavor.name}/app-flavor2-release-1.9-10-R.txt"
}
}
def getOldApkPath() {
return ext.tinkerOldApkPath
}
def getApplyMappingPath() {
return ext.tinkerApplyMappingPath
}
def getApplyResourceMappingPath() {
return ext.tinkerApplyResourcePath
}
def getTinkerIdValue() {
return ext.tinkerId
}
def buildWithTinker() {
return ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}
if (buildWithTinker()) {
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
oldApk = getOldApkPath()
ignoreWarning = true
useSign = true
buildConfig {
applyMapping = getApplyMappingPath()
applyResourceMapping = getApplyResourceMappingPath()
tinkerId = getTinkerIdValue()
/**
* if keepDexApply is true, class in which dex refer to the old apk.
* open this can reduce the dex diff file size.
*/
keepDexApply = false
/**
* 是否开启加固 ,默认 false
*/
isProtectedApp = false
}
dex {
dexMode = "jar"
pattern = ["classes*.dex", "assets/secondary-dex-?.jar"]
}
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 = "${fileNamePrefix}-" + android.defaultConfig.versionName + "-" + android.defaultConfig.versionCode
// def destPath = bakPath
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"
}
}
}
}
}
}