接着android:一步步实现插件化与热更新(二)的内容继续下面的操作
热更新热更新其实来源于插件化,如果你使用了上面的Small进行插件化开发,就可以直接进行插件化更新了!如果你没有使用插件化开发,你就可以只有热更新实现代码的热修复。
这里主要使用的是腾讯的Tinker。
- 在peoject的build中配置如下:
dependencies {
classpath 'com.android.tools.build:gradle:2.3.3'
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
}
TINKER_VERSION需要在gradle.properties中进行配置
TINKER_VERSION=1.7.7
- 接下来是配置app的build
这是我的,可以直接使用:
apply plugin: 'com.android.application'
//配置java版本
def javaVersion = JavaVersion.VERSION_1_7
android {
compileSdkVersion 26
buildToolsVersion "26.0.2"
compileOptions {
sourceCompatibility javaVersion
targetCompatibility javaVersion
}
//recommend
dexOptions {
jumboMode = true
}
signingConfigs {
release {
keyAlias 'tsou'
keyPassword 'tsou123'
storeFile file('D:/study/TinkerTest/app/keystore/tinkertest.jks')
storePassword 'tsou123'
}
debug {
keyAlias 'tsou'
keyPassword 'tsou123'
storeFile file('D:/study/TinkerTest/app/keystore/tinkertest.jks')
storePassword 'tsou123'
}
}
defaultConfig {
applicationId "tsou.cn.tinkertest"
minSdkVersion 15
targetSdkVersion 26
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", "\"2.0\""
buildConfigField "String", "PLATFORM", "\"all\""
}
buildTypes {
release {
minifyEnabled true
signingConfig signingConfigs.release
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
debug {
debuggable true
minifyEnabled false
signingConfig signingConfigs.debug
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
sourceSets {
main {
jniLibs.srcDirs = ['libs']
}
}
}
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:26.+'
compile 'com.android.support.constraint:constraint-layout:1.0.2'
testCompile 'junit:junit:4.12'
//可选,用于生成application类
compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
//tinker的核心库
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
compile 'com.android.support:multidex:1.0.1'
}
def bakPath = file("${buildDir}/bakApk/")
//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改
ext {
tinkerEnabled = true
tinkerOldApkPath = "${bakPath}/app-release-1110-16-24-35.apk"
tinkerApplyMappingPath = "${bakPath}/app-release-1110-16-24-35-mapping.txt"
tinkerApplyResourcePath = "${bakPath}/app-release-1110-16-24-35-R.txt"
//only use for build all flavor, if not, just ignore this field
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 buildWithTinker() {
return hasProperty("TINKER_ENABLE") ? TINKER_ENABLE : ext.tinkerEnabled
}
def getTinkerBuildFlavorDirectory() {
return ext.tinkerBuildFlavorDirectory
}
/**
* tinkerId一定要有
*/
if (buildWithTinker()) {
apply plugin: 'com.tencent.tinker.patch'
tinkerPatch {
oldApk = getOldApkPath()
ignoreWarning = true
useSign = true
tinkerEnable = buildWithTinker()
buildConfig {
applyMapping = getApplyMappingPath()
applyResourceMapping = getApplyResourceMappingPath()
tinkerId = "1.0"/*getTinkerIdValue()*/
keepDexApply = false
}
dex {
dexMode = "jar"
pattern = ["classes*.dex",
"assets/secondary-dex-?.jar"]
loader = [
//use sample, let BaseBuildInfo unchangeable with tinker
"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"
// path = "/usr/local/bin/7za"
}
}
List<String> flavors = new ArrayList<>();
project.android.productFlavors.each { flavor ->
flavors.add(flavor.name)
}
boolean hasFlavors = flavors.size() > 0
android.applicationVariants.all { variant ->
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 {
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"
}
}
}
}
}
}
注意 修改buildConfigField
//客户端版本更新补丁
buildConfigField "String", "TINKER_ID", "\"2.0\""
如果更新客户端补丁需要修改,例如我把1.0改为2.0
- 配置application
/**
* Created by Administrator on 2017/10/27 0027.
*/
@DefaultLifeCycle(application = ".SimpleTinkerInApplication",
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag =true)
public class SimpleTinkerInApplicationLike extends ApplicationLike {
public SimpleTinkerInApplicationLike (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);
MultiDex.install(base);
TinkerInstaller.install(this);
}
@Override
public void onCreate() {
super.onCreate();
}
}
SimpleTinkerInApplicationLike 并不是一个application
真正的application是@DefaultLifeCycle(application = “.SimpleTinkerInApplication”,这个
所以在androidmanifest中配置applica的name
<application
android:name=".SimpleTinkerInApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity android:name=".MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />
- 运行你的项目使用release版本
效果如下:
- 更改你的build.gradle
//老版本的文件所在的位置,大家也可以动态配置,不用每次都在这里修改
ext {
tinkerEnabled = true
tinkerOldApkPath = "${bakPath}/app-release-1201-13-55-26.apk"
tinkerApplyMappingPath = "${bakPath}/app-release-1201-13-55-26-mapping.txt"
tinkerApplyResourcePath = "${bakPath}/app-release-1201-13-55-26-R.txt"
//only use for build all flavor, if not, just ignore this field
tinkerBuildFlavorDirectory = "${bakPath}/app-1018-17-32-47"
}
-
更改代码:把“我的”改为“2222”
-
然后点击 编译
-
生成目录如下
-
把patch_signed_7zip.apk放到手机根目录下,再执行更新
- 热更新代码如下:
public class MainActivity extends AppCompatActivity {
private TextView tv;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tv = (TextView) findViewById(R.id.tv);
Button loadPath = (Button) findViewById(R.id.loadPath);
tv.setText("2222");
loadPath.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
requestPermi();
}
});
}
private void requestPermi() {
if (ContextCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
// Should we show an explanation?
if (ActivityCompat.shouldShowRequestPermissionRationale(MainActivity.this,
Manifest.permission.READ_EXTERNAL_STORAGE)) {
} else {
ActivityCompat.requestPermissions(MainActivity.this,
new String[]{Manifest.permission.READ_EXTERNAL_STORAGE},
100);
}
} else {
//有权限直接加载apk
loadApk();
}
}
@Override
public void onRequestPermissionsResult(int requestCode, @NonNull String[] permissions, @NonNull int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
switch (requestCode) {
case 100: {
// If request is cancelled, the result arrays are empty.
if (grantResults.length > 0
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
//权限申请成功加载apk
loadApk();
} else {
}
return;
}
}
}
public void loadApk() {
String path = Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk";
File file = new File(path);
if (file.exists()) {
Toast.makeText(this, "补丁已经存在", Toast.LENGTH_SHORT).show();
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), path);
} else {
Toast.makeText(this, "补丁已经不存在", Toast.LENGTH_SHORT).show();
}
}
}
好了到此插件化与热更新都可以实现了.......
android插件化开发Demo:https://gitee.com/huangxiaoguo/androidChaJianHuaKaiFa
android-Tinker热修复Demo:https://gitee.com/huangxiaoguo/android-TinkerReXiuFu