Tinker热更新快速集成

前言

由于公司需要,入坑Tinker,结果发现dex以及资源文件,可以替换。而So文件,Log日志提示替换成功,而使用时不好使。

目标

更新Dex,资源文件,以及So库文件

原理

简单说下Tinker的原理。通过算法,将新的更新的APK和原版的BaseApk之间的差异生成一个Patch补丁包。将补丁包发送到手机本地,在用户打开手机时将补丁包加载进手机。更加具体的原理,此处不做叙述。

集成

tinker-patch-gradle-plugin插件

在全局的build.gradle中新增classpath

1
classpath ('com.tencent.tinker:tinker-patch-gradle-plugin:1.9.1')

tinker依赖

在app的build.gradle中添加依赖

1
2
3
4
5
6
7
8
9
dependencies {
//tinker核心库
implementation 'com.tencent.tinker:tinker-android-lib:1.9.1'
//可选,用于生成application类
annotationProcessor 'com.tencent.tinker:tinker-android-anno:1.9.1'
compileOnly("com.tencent.tinker:tinker-android-anno:1.9.1") {changing = true}
//multidex
implementation 'androidx.multidex:multidex:2.0.1'
}

导入Tinker功能封装类并注册Service

参考官方:https://github.com/Tencent/tinker/tree/dev/tinker-sample-android/app/src/main/java/tinker/sample/android

1
2
<service android:name=".service.SampleResultService"
android:exported="false"/>

生成Application

新建ApplicationLike,继承自DefaultApplication。这里是为了自动生成Application

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
//application为生成的Application的名称。flags为可以替换的类型,这里TINKER_ENABLE_ALL为全都可以替换(dex,资源文件,so库)
@DefaultLifeCycle(application = ".TestTinkerApplication", flags = ShareConstants.TINKER_ENABLE_ALL)
public class TestTinkerLike extends DefaultApplicationLike {
private static TestTinkerLike mTestTinkerLike;
public TestTinkerLike(Application application, int tinkerFlags, boolean tinkerLoadVerifyFlag, long applicationStartElapsedTime, long applicationStartMillisTime, Intent tinkerResultIntent) {
super(application, tinkerFlags, tinkerLoadVerifyFlag, applicationStartElapsedTime, applicationStartMillisTime, tinkerResultIntent);
mTestTinkerLike=this;
}

/**
* 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);
//you must install multiDex whatever tinker is installed!
MultiDex.install(base);

TinkerManager.fastInstallAll(this);
}

public static TestTinkerLike getmTestTinkerLike(){
return mTestTinkerLike;
}
}

注册Application

注册Application会报错,因为TestTinkerApplication没有生成,build一下即可

1
2
<application
android:name=".TestTinkerApplication"

热更新

配置签名

1
2
3
4
5
6
7
8
9
10
11
12
android {

//签名配置
signingConfigs {
releaseConfig {
keyAlias "AppBlog.CN"
keyPassword "AppBlog.CN"
storeFile file("../keystore/keystore.jks")
storePassword "AppBlog.CN"
}
}
}

生成基础APK包

生成方式:Gradle -> app -> build -> assembleRelease

生成位置可以自行配置(在build.gradle中修改bakPath)

生成补丁包

注意:这里生成的补丁必须是基于用户安装的基础APK。假如用户安装的是A版本,而你是基于B版本生成的补丁包,这样是无法在A版本上更新的。即基础APK必须是用户正在用的版本。

导入补丁包

将补丁包放到手机中,这里有一点需要注意就是权限问题

1
2
3
4
5
6
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(MainActivity.this, 
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
} else {
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
}

加载so库的几种方式

  • Hack方式(成功率最大的方式)
1
2
3
String CPU_ABI = android.os.Build.CPU_ABI;
// 将tinker library中的 CPU_ABI架构的so 注册到系统的library path中。
TinkerLoadLibrary.installNavitveLibraryABI(MainActivity.this, CPU_ABI);
  • 非Hack方式
1
2
String CPU_ABI = android.os.Build.CPU_ABI;
boolean a=TinkerLoadLibrary.loadLibraryFromTinker(getApplicationContext(), "lib/" + CPU_ABI, "native-lib");
  • Arm方式
1
TinkerLoadLibrary.loadArmLibrary(getApplicationContext(), "native-lib");
  • Arm-V7a方式
1
TinkerApplicationHelper.loadArmV7aLibrary(TestTinkerLike.getmTestTinkerLike(), "native-lib");

注意:这里有两个类可以加载So文件,TinkerLoadLibraryTinkerApplicationHelper,不过其原理是一样的

特别注意:so文件是打补丁的时候自动加载的,但是却需要手动的链一下,相当于,基础apk本身的so文件A。在打补丁的时候加载了so文件B。此时有两个so文件。你每次都要手动的用上面的方法加载so库B。否则默认调用的还是基础APK的so文件A中的方法。一旦在加载so库前,调用了A中的方法,默认加载的是so库A。此时要是调用so库B中新增的方法(so库B中有,但是so库A中没有的方法),就会报错。

  • MainActivity.java
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
public class MainActivity extends AppCompatActivity {
private Button installBtn;
private Button unInstallBtn;
private Button infoBtn;
private Button hackBtn;
private Button nohackBtn;
private Button armeabiBtn;
private Button v7aBtn;

// Used to load the 'native-lib' library on application startup.
static {
System.loadLibrary("native-lib");
}

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);

installBtn = findViewById(R.id.btn_load_install);
unInstallBtn = findViewById(R.id.btn_load_unInstall);
mImg = findViewById(R.id.iv_main_test);
hackBtn = findViewById(R.id.btn_load_hack);
nohackBtn = findViewById(R.id.btn_load_nohack);
armeabiBtn = findViewById(R.id.btn_load_armeabi);
v7aBtn = findViewById(R.id.btn_load_armeabiv7a);
infoBtn = findViewById(R.id.btn_load_info);

installBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M && ActivityCompat.checkSelfPermission(MainActivity.this,
Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(MainActivity.this, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, 0);
} else {
TinkerInstaller.onReceiveUpgradePatch(getApplicationContext(), Environment.getExternalStorageDirectory().getAbsolutePath() + "/patch_signed_7zip.apk");
}
Toast.makeText(MainActivity.this, "安装补丁", Toast.LENGTH_SHORT).show();
}
});

unInstallBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TinkerInstaller.cleanPatch(MainActivity.this);
Toast.makeText(MainActivity.this, "卸载补丁", Toast.LENGTH_SHORT).show();
}
});

infoBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Toast.makeText(MainActivity.this, getInfo(),Toast.LENGTH_SHORT).show();
}
});

hackBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String CPU_ABI = android.os.Build.CPU_ABI;
// 将tinker library中的 CPU_ABI架构的so 注册到系统的library path中。
TinkerLoadLibrary.installNavitveLibraryABI(MainActivity.this, CPU_ABI);
}
});

nohackBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
String CPU_ABI = android.os.Build.CPU_ABI;
boolean a = TinkerLoadLibrary.loadLibraryFromTinker(getApplicationContext(), "lib/" + CPU_ABI, "native-lib");
Toast.makeText(MainActivity.this, "so库安装:" + a, Toast.LENGTH_SHORT).show();
}
});

armeabiBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TinkerLoadLibrary.loadArmLibrary(getApplicationContext(), "native-lib");
}
});

v7aBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
TinkerApplicationHelper.loadArmV7aLibrary(TestTinkerLike.getmTestTinkerLike(), "native-lib");
}
});
}

/**
* A native method that is implemented by the 'native-lib' native library,
* which is packaged with this application.
*/
public native String getInfo();
}
  • native-lib.cpp
1
2
3
4
5
6
7
8
9
#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL
Java_cn_appblog_tinker_MainActivity_getInfo(JNIEnv *env, jobject instance) {
std::string hello = "AppBlog.CN";
return env->NewStringUTF(hello.c_str());
}

Powered by AppBlog.CN     浙ICP备14037229号

Copyright © 2012 - 2021 APP开发技术博客 All Rights Reserved.

访客数 : | 访问量 :