demo下载地址
https://download.csdn.net/download/u010672559/10551901
一、概述
项目上线之后,经常会临时被告知某个小功能需要被修改,或者说上线了之后才发现某个bug需要紧急修复,于是总能匆匆忙改了段代码又得上线新的版本,极其麻烦,Tinker 是是微信官方的 Android 热修复解决方案,能让用户在没有察觉,没有更新app的情况下实现修复。
二、环境配置
说明:调试时发现麻烦的是配置gradle文件,需要保证没错,其他的注意步骤别搞错了,一步步的来,手机高于6.0的注意需要动态申请权限,demo是在4.4的机器调的,所以没加动态申请权限
1.ProjectName\build.gradle
dependencies {
classpath 'com.android.tools.build:gradle:3.1.2'
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
}
2.ProjectName\gradle.properties
添加TINKER_VERSION=1.9.6
3.ProjectName\app\build.gradle配置见具体文件,实际可复制粘贴过去,然后修改applicationId即可,即工程的包名,然后注意导包的不同
4.清单中加权限,application,service定义,其中SampleApplication为打包的时候自动生成的
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<application
android:name=".app.SampleApplication"
<service
android:name=".service.SampleResultService"
android:exported="false"/>
5.添加SampleApplicationLike和SampleResultService文件,文件名其实可以修改,但是注意清单中做相应更改,
@SuppressWarnings("unused")
@DefaultLifeCycle(application = "com.example.wzhang.tinkertest0719b.SampleApplication",------修改为实际项目的名称
flags = ShareConstants.TINKER_ENABLE_ALL,
loadVerifyFlag = false)
6..代码调用时如果loadpath出错,可能是没有加动态申请权限的代码,注意机器的Android版本
public static final int REQUEST_CODE = 0;
private void checkAndRequestPermission() {
if (Build.VERSION.SDK_INT >= 23) {
ArrayList<String> permissions = new ArrayList<String>();
if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.READ_EXTERNAL_STORAGE);
Log.i(TAG, "检测无READ_EXTERNAL_STORAGE权限");
}
if (ContextCompat.checkSelfPermission(this, Manifest.permission.WRITE_EXTERNAL_STORAGE)
!= PackageManager.PERMISSION_GRANTED) {
permissions.add(Manifest.permission.WRITE_EXTERNAL_STORAGE);
Log.i(TAG, "检测无WRITE_EXTERNAL_STORAGE权限");
}
if(permissions.size()>0){
// if (!ActivityCompat.shouldShowRequestPermissionRationale(this, Manifest.permission.RECORD_AUDIO)) {
Log.i(TAG, "请求权限");
ActivityCompat.requestPermissions(this, permissions.toArray(new String[permissions.size()]), REQUEST_CODE);
//弹出对话框,注意请求码和之前的检测权限字符想对应
// }
}
}
}
@Override
public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) {
Log.i(TAG, "onRequestPermissionsResult-requestCode="+requestCode);
switch (requestCode) {
case REQUEST_CODE: {
// If request is cancelled, the result arrays are empty.
for(int i=0;i<permissions.length;i++){
Log.i(TAG, "i="+i+",permissions[i]="+permissions[i]);
}
}
}
}
7.实际loadpath逻辑如下,可能需要根据实际项目做修改,一般是将文件给服务器,然后现在服务器文件,然后再更新
调试时是将apk文件push到sd卡根目录,且与代码需一致
storage/emulated/0
btnLoad.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "load");
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
}
});
btnKill.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Log.d(TAG, "kill");
ShareTinkerInternals.killAllOtherProcess(getApplicationContext());
android.os.Process.killProcess(android.os.Process.myPid());
}
});
-------
public void onPatchResult(final PatchResult result) {
返回是否加载成功的逻辑
}
三、相关文件
ProjectName\app\build.gradle-实际可以复制粘贴意思全部内容,然后修改applicationId即可,同时注意修改对应的complile依赖
apply plugin: 'com.android.application'
dependencies {
compile fileTree(include: ['*.jar'], dir: 'libs')
testCompile 'junit:junit:4.12'
compile 'com.android.support:appcompat-v7:23.1.1'
compile("com.tencent.tinker:tinker-android-lib:${TINKER_VERSION}") { changing = true }
provided("com.tencent.tinker:tinker-android-anno:${TINKER_VERSION}") { changing = true }
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:multidex:1.0.1'
testCompile 'junit:junit:4.12'
//use to test multiDex
// compile group: 'com.google.guava', name: 'guava', version: '19.0'
// compile "org.scala-lang:scala-library:2.11.7"
}
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
android {
compileSdkVersion 26
buildToolsVersion '26.0.2'
compileOptions {
sourceCompatibility javaVersion
targetCompatibility javaVersion
}
//recommend
dexOptions {
jumboMode = true
}
signingConfigs {
release {
try {
//官方的demo里面是用的keystore
// storeFile file("./keystore/release.keystore")
// storePassword "testres"
// keyAlias "testres"