Leo's Blog

Tinker集成优化

1. 兼容多渠道包,使用zip comment方式生成渠道包(完成)

多渠道打包 packer-ng-plugin

  • 1.1 引入

    • 根目录build.gradle

      1
      2
      3
      4
      5
      6
      7
      buildscript {
      ......
      dependencies{
      // add packer-ng
      classpath 'com.mcxiaoke.gradle:packer-ng:1.0.8'
      }
      }
    • 修改android模块的 build.gradle

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      apply plugin: 'packer'
      dependencies {
      compile 'com.mcxiaoke.gradle:packer-helper:1.0.8'
      }
      android {
      ...
      signingConfigs {
      release {
      // 满足下面两个条件时需要此配置
      // 1. Gradle版本 >= 2.14.1
      // 2. Android Gradle Plugin 版本 >= 2.2.0
      // 作用是只使用旧版签名,禁用V2版签名模式
      v2SigningEnabled false
      }
      }
      }
  • 1.2 打包流程

    • 指定market属性 : 两种方式
      • 打包时命令行使用 -Pmarket = yourMarketPath
      • 在gradle.properties 里加入 market=yourMarketFilePath
        market文件是渠道名列表文件,路径为相对路径 一般放在项目根目录
    • market 格式

      1
      2
      lianxiang#联想乐商店 // #前面为渠道号,后面为注释
    • 打包命令:market.txt 在项目根目录

      • 普通打包

        1
        ./gradlew -Pmarket=markets.txt clean apkRelease
      • 指定productFlavors 打包
        如果项目中有多个productFlavors,默认使用第一个flavor生成的apk文件作为打包工具的输入参数,也可以指定flavor生成渠道包

        1
        2
        // 指定 使用名称为demo 的flavor 打包
        ./gradlew -Pmarket=markets.txt clean apkDemoRelease
  • 1.3 插件配置
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    packer {
    // 是否检查signingConfig ,默认false
    checkSigningConfig = true
    // 是否检查gradle配置中的zipAlignEnabled,默认false
    checkZipAlign = true
    // 输出的apk文件名称
    archiveNameFormat = '${appPkg}-${flavorName}-${buildType}-v${versionName}-${versionCode}'
    // 输出目录
    archiveOutput = file(new File(project.buildDir.path, "archives"))
    }
1
2
3
4
5
6
7
8
9
10
11
12
apk 文件 名称配置:
projectName - 项目名字
appName - App模块名字
appPkg - applicationId (App包名packageName)
buildType - buildType (release/debug/beta等)
flavorName - flavorName (对应渠道打包中的渠道名字)
versionName - versionName (显示用的版本号)
versionCode - versionCode (内部版本号)
buildTime - buildTime (编译构建日期时间)
fileMD5 - fileMD5 (最终APK文件的MD5哈希值) (v1.0.5新增)
fileSHA1 - fileSHA1 (最终APK文件的SHA1哈希值) (v1.0.5新增)

2. 资源文件未做修改,patch文件过大(完成)

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
// 1. 关闭aapt对png优化
android{
...
aaptOptions {
cruncherEnabled false
}
}
// 2. 确认png文件没有修改的情况下 忽略png的修改
tinkerPatch{
...
res{
...
ignoreChange = ["*.png"]
}
}

3. TinkerPatch 补丁管理后台集成(完成)

1
2
3
4
5
6
7
8
9
10
gradle接入
// tinker server
compile("com.tencent.tinker:tinker-server-android:0.3.2")
// 配置appkey appVersion
android{
...
// 微信Tinker 后台的appkey
buildConfigField "String", "APP_KEY", "\"68cfdc1fa61a19df\""
buildConfigField "String", "APP_VERSION", "\"${versionName}\""
}
1
2
3
4
5
// 初始化TinkerPatch SDK
TinkerServerManager.installTinkerServer(
getApplication(),Tinker.with(getApplication()), 1,BuildConfig.APP_KEY, BuildConfig.APP_VERSION, channel);
// 检查是否有新补丁,默认1小时检查一次
TinkerServerManager.checkTinkerUpdate(false);
1
2
3
4
5
// 注册service
<service
android:name="com.tencent.tinker.app.service.TinkerServerResultService"
android:exported="false"
/>
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
// TinkerServerManager 及 Callback的封装
public class TinkerServerManager {
private static final String TAG = "Tinker.ServerManager";
private static final String CONDITION_CHANNEL = "channel";
static TinkerServerClient sTinkerServerClient;
static String channel;
/**
* 初始化 TinkerServer 实例
* @param context context
* @param tinker {@link Tinker} 实例
* @param hours 访问服务器的时间间隔, 单位为小时, 应为 >= 0
* @param appKey 从Tinkerpatch中得到的appKey
* @param appVersion 在Tinkerpatch中填写的appVersion
* @param channel 发布的渠道名称,由于GooglePlay渠道的政策限制,我们会停止所有channel中含有google关键字的动态下发功能。
*/
public static void installTinkerServer(Context context, Tinker tinker,
int hours, String appKey, String appVersion, String channel) {
final boolean debug = Debugger.getInstance(context).isDebug();
TinkerLog.w(TAG, String.format("installTinkerServer, debug value: %s appVersion: %s, channel: %s",
String.valueOf(debug), appVersion, channel)
);
sTinkerServerClient = TinkerServerClient.init(
context,
tinker,
appKey,
appVersion,
debug,
new TinkerServerPatchRequestCallback()
);
// add channel condition
sTinkerServerClient.updateTinkerCondition(CONDITION_CHANNEL, channel);
sTinkerServerClient.setCheckIntervalByHours(hours);
TinkerServerManager.channel = channel;
}
/**
* 检查服务器是否有补丁更新
* @param immediately 是否立刻检查,忽略时间间隔限制
*/
public static void checkTinkerUpdate(final boolean immediately) {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "checkTinkerUpdate, sTinkerServerClient == null");
return;
}
Tinker tinker = sTinkerServerClient.getTinker();
//only check at the main process
if (tinker.isMainProcess()) {
Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
sTinkerServerClient.checkTinkerUpdate(immediately);
return false;
}
});
}
}
/**
* 向服务器请求在线参数信息
* @param configRequestCallback
* @param immediately 是否立刻请求,忽略时间间隔限制
*/
public static void getDynamicConfig(final ConfigRequestCallback configRequestCallback, final boolean immediately) {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "checkTinkerUpdate, sTinkerServerClient == null");
return;
}
Tinker tinker = sTinkerServerClient.getTinker();
//only check at the main process
if (tinker.isMainProcess()) {
Looper.getMainLooper().myQueue().addIdleHandler(new MessageQueue.IdleHandler() {
@Override
public boolean queueIdle() {
sTinkerServerClient.getDynamicConfig(configRequestCallback, immediately);
return false;
}
});
}
}
/**
* 设置在线参数的时间间隔
* @param hours 大于等于0的整数
*/
public static void setGetConfigIntervalByHours(int hours) {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "setGetConfigIntervalByHours, sTinkerServerClient == null");
return;
}
sTinkerServerClient.setGetConfigIntervalByHours(hours);
}
/**
* 将在线参数返回的 json 转化为 Hashmap
* @param jsonString
* @return
* @throws JSONException
*/
public static HashMap<String, String> jsonToMap(String jsonString) throws JSONException {
HashMap<String, String> map = new HashMap<>();
JSONObject jObject = new JSONObject(jsonString);
Iterator<String> keys = jObject.keys();
while (keys.hasNext()) {
String key = keys.next();
String value = jObject.getString(key);
map.put(key, value);
}
return map;
}
/**
* 设置条件下发的属性
* @param key
* @param value
*/
public static void updateTinkerCondition(String key, String value) {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "updateTinkerCondition, sTinkerServerClient == null");
return;
}
sTinkerServerClient.updateTinkerCondition(key, value);
}
/**
* 上报补丁合成情况
* @param patchResult
*/
public static void reportTinkerPatchFail(PatchResult patchResult) {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "reportTinkerPatchFail, sTinkerServerClient == null");
return;
}
if (patchResult == null) {
TinkerLog.e(TAG, "reportTinkerPatchFail, patchResult == null");
return;
}
if (patchResult.isSuccess) {
TinkerLog.i(TAG, "reportTinkerPatchFail, patch success, just return");
return;
}
String patchMd5 = (patchResult.patchVersion != null)
? patchResult.patchVersion : SharePatchFileUtil.getMD5(new File(patchResult.rawPatchFilePath));
if (!patchMd5.equals(sTinkerServerClient.getCurrentPatchMd5())) {
TinkerLog.e(TAG, "reportTinkerPatchFail, md5 not equal, patchMd5:%s, currentPatchMd5:%s",
patchMd5, sTinkerServerClient.getCurrentPatchMd5()
);
return;
}
sTinkerServerClient.reportPatchFail(
sTinkerServerClient.getCurrentPatchVersion(),
DefaultPatchRequestCallback.ERROR_PATCH_FAIL
);
}
/**
* 上报补丁合成情况
* @param patchMd5
*/
public static void reportTinkerPatchListenerFail(int returnCode, String patchMd5) {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "reportTinkerPatchListenerFail, sTinkerServerClient == null");
return;
}
if (returnCode == ShareConstants.ERROR_PATCH_OK) {
return;
}
if (patchMd5 == null) {
TinkerLog.e(TAG, "reportTinkerPatchListenerFail, patchMd5 == null");
return;
}
if (!patchMd5.equals(sTinkerServerClient.getCurrentPatchMd5())) {
TinkerLog.e(TAG, "reportTinkerPatchListenerFail, md5 not equal, patchMd5:%s, currentPatchMd5:%s",
patchMd5, sTinkerServerClient.getCurrentPatchMd5()
);
return;
}
sTinkerServerClient.reportPatchFail(
sTinkerServerClient.getCurrentPatchVersion(),
DefaultPatchRequestCallback.ERROR_LISTENER_CHECK_FAIL
);
}
/**
* 上报补丁加载情况
*/
public static void reportTinkerLoadFail() {
if (sTinkerServerClient == null) {
TinkerLog.e(TAG, "reportTinkerPatchFail, sTinkerServerClient == null");
return;
}
sTinkerServerClient.reportPatchFail(
sTinkerServerClient.getCurrentPatchVersion(),
DefaultPatchRequestCallback.ERROR_LOAD_FAIL
);
}
public static boolean isGooglePlayChannel() {
return channel.contains("google");
}
public static boolean is360Channel(){
return channel.contains("360");
}
}
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
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
public class TinkerServerPatchRequestCallback extends DefaultPatchRequestCallback {
private static final String TAG = "Tinker.TinkerServerDefaultRequestCallback";
public static final String TINKER_RETRY_PATCH = "tinker_retry_patch";
public static final int TINKER_MAX_RETRY_COUNT = 3;
@Override
public boolean beforePatchRequest() {
boolean result = super.beforePatchRequest();
if (result) {
TinkerServerClient client = TinkerServerClient.get();
Tinker tinker = client.getTinker();
Context context = client.getContext();
if (!tinker.isMainProcess()) {
TinkerLog.e(TAG, "beforePatchRequest, only request on the main process");
return false;
}
if (TinkerServerManager.isGooglePlayChannel()) {
TinkerLog.e(TAG, "beforePatchRequest, google play channel, return false");
return false;
}
if (TinkerServerManager.is360Channel()) {
TinkerLog.e(TAG, "beforePatchRequest, 360 channel, return false");
return false;
}
// main process must be the newly version
// check whether it is pending work
String currentPatchMd5 = client.getCurrentPatchMd5();
TinkerLoadResult tinkerLoadResult = tinker.getTinkerLoadResultIfPresent();
if (tinkerLoadResult.currentVersion == null || !currentPatchMd5.equals(tinkerLoadResult.currentVersion)) {
Integer version = client.getCurrentPatchVersion();
if (version > 0) {
File patchFile = ServerUtils.getServerFile(
context, client.getAppVersion(), String.valueOf(version)
);
if (patchFile.exists() && patchFile.isFile() && handlePatchFile(context, version, patchFile)) {
return false;
}
}
}
}
return result;
}
private boolean handlePatchFile(Context context, Integer version, File patchFile) {
SharedPreferences sp = context.getSharedPreferences(
TinkerServerClient.SHARE_SERVER_PREFERENCE_CONFIG, Context.MODE_PRIVATE
);
int current = sp.getInt(TINKER_RETRY_PATCH, 0);
if (current >= TINKER_MAX_RETRY_COUNT) {
SharePatchFileUtil.safeDeleteFile(patchFile);
sp.edit().putInt(TINKER_RETRY_PATCH, 0).commit();
TinkerLog.w(TAG,
"beforePatchRequest, retry patch install more than %d times, version: %d, patch:%s",
current, version, patchFile.getPath()
);
} else {
TinkerLog.w(TAG, "beforePatchRequest, have pending patch to install, version: %d, patch:%s",
version, patchFile.getPath()
);
sp.edit().putInt(TINKER_RETRY_PATCH, ++current).commit();
TinkerInstaller.onReceiveUpgradePatch(context, patchFile.getAbsolutePath());
return true;
}
return false;
}
@Override
public void onPatchRollback() {
TinkerLog.w(TAG, "onPatchRollback");
TinkerServerClient client = TinkerServerClient.get();
if (!client.getTinker().isTinkerLoaded()) {
TinkerLog.w(TAG, "onPatchRollback, tinker is not loaded, just return");
return;
}
if (TinkerServerUtils.isBackground()) {
TinkerLog.i(TAG, "onPatchRollback, it is in background, just clean patch and kill all process");
rollbackPatchDirectly();
} else {
//we can wait process at background, such as onAppBackground
//or we can restart when the screen off
TinkerLog.i(TAG, "tinker wait screen to clean patch and kill all process");
new TinkerServerUtils.ScreenState(client.getContext(), new TinkerServerUtils.IOnScreenOff() {
@Override
public void onScreenOff() {
rollbackPatchDirectly();
}
});
}
}
@Override
public void onPatchDownloadFail(Exception e, Integer newVersion, Integer currentVersion) {
super.onPatchDownloadFail(e, newVersion, currentVersion);
}
@Override
public void onPatchSyncFail(Exception e) {
super.onPatchSyncFail(e);
}
@Override
public boolean onPatchUpgrade(File file, Integer newVersion, Integer currentVersion) {
boolean result = super.onPatchUpgrade(file, newVersion, currentVersion);
if (result) {
TinkerServerClient client = TinkerServerClient.get();
Context context = client.getContext();
SharedPreferences sp = context.getSharedPreferences(
TinkerServerClient.SHARE_SERVER_PREFERENCE_CONFIG, Context.MODE_PRIVATE
);
sp.edit().putInt(TINKER_RETRY_PATCH, 0).commit();
}
return result;
}
}

4. 合成进程可能被中断,补丁合成的重试

重试三次 TinkerServePatchRequestCallBack – handlePatchFile

5. 补丁后程序无法启动的处理(完成)

停用Tinker 及 清理补丁

1
2
Tinker.with(context).setTinkerDisable();
Tinker.with(context).cleanPatch();

6. Tinker 默认的 DefaultTinkerResultService 会在加载patch成功之后立刻杀掉进程,应该在程序进入后台或者手机锁屏后杀掉进程,使补丁生效! (完成)