-
Notifications
You must be signed in to change notification settings - Fork 3.3k
/
TinkerPatchService.java
321 lines (280 loc) · 11.7 KB
/
TinkerPatchService.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
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
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
/*
* Tencent is pleased to support the open source community by making Tinker available.
*
* Copyright (C) 2016 THL A29 Limited, a Tencent company. All rights reserved.
*
* Licensed under the BSD 3-Clause License (the "License"); you may not use this file except in
* compliance with the License. You may obtain a copy of the License at
*
* https://opensource.org/licenses/BSD-3-Clause
*
* Unless required by applicable law or agreed to in writing, software distributed under the License is
* distributed on an "AS IS" basis, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND,
* either express or implied. See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.tencent.tinker.lib.service;
import static com.tencent.tinker.lib.util.TinkerServiceInternals.getTinkerPatchServiceName;
import android.app.ActivityManager;
import android.app.IntentService;
import android.app.Notification;
import android.app.Service;
import android.content.Context;
import android.content.Intent;
import android.os.Build;
import android.os.IBinder;
import android.os.SystemClock;
import com.tencent.tinker.lib.patch.AbstractPatch;
import com.tencent.tinker.lib.tinker.Tinker;
import com.tencent.tinker.loader.TinkerRuntimeException;
import com.tencent.tinker.loader.shareutil.ShareConstants;
import com.tencent.tinker.loader.shareutil.ShareIntentUtil;
import com.tencent.tinker.loader.shareutil.SharePatchFileUtil;
import com.tencent.tinker.loader.shareutil.ShareTinkerLog;
import java.io.File;
import java.util.List;
import java.util.concurrent.atomic.AtomicBoolean;
/**
* Created by zhangshaowen on 16/3/14.
*/
public class TinkerPatchService extends IntentService {
private static final String TAG = "Tinker.TinkerPatchService";
private static final String PATCH_PATH_EXTRA = "patch_path_extra";
private static final String PATCH_USE_EMERGENCY_MODE = "patch_use_emergency_mode";
private static final String RESULT_CLASS_EXTRA = "patch_result_class";
private static AbstractPatch upgradePatchProcessor = null;
private static int notificationId = ShareConstants.TINKER_PATCH_SERVICE_NOTIFICATION;
private static Class<? extends AbstractResultService> resultServiceClass = null;
public TinkerPatchService() {
super("TinkerPatchService");
setIntentRedelivery(true);
}
public static void runPatchService(final Context context, final String path) {
runPatchService(context, path, false);
}
public static void runPatchService(final Context context, final String path, boolean useEmergencyMode) {
ShareTinkerLog.i(TAG, "run patch service...");
Intent intent = new Intent(context, TinkerPatchService.class);
intent.putExtra(PATCH_PATH_EXTRA, path);
intent.putExtra(PATCH_USE_EMERGENCY_MODE, useEmergencyMode);
intent.putExtra(RESULT_CLASS_EXTRA, resultServiceClass.getName());
try {
context.startService(intent);
} catch (Throwable thr) {
ShareTinkerLog.e(TAG, "run patch service fail, exception:" + thr);
}
}
public static void setPatchProcessor(AbstractPatch upgradePatch, Class<? extends AbstractResultService> serviceClass) {
upgradePatchProcessor = upgradePatch;
resultServiceClass = serviceClass;
//try to load
try {
Class.forName(serviceClass.getName());
} catch (ClassNotFoundException e) {
ShareTinkerLog.printErrStackTrace(TAG, e, "patch processor class not found.");
}
}
public static String getPatchPathExtra(Intent intent) {
if (intent == null) {
throw new TinkerRuntimeException("getPatchPathExtra, but intent is null");
}
return ShareIntentUtil.getStringExtra(intent, PATCH_PATH_EXTRA);
}
public static boolean getPatchUseEmergencyMode(Intent intent) {
if (intent == null) {
throw new TinkerRuntimeException("getPatchUseEmergencyMode, but intent is null");
}
return ShareIntentUtil.getBooleanExtra(intent, PATCH_USE_EMERGENCY_MODE, false);
}
public static String getPatchResultExtra(Intent intent) {
if (intent == null) {
throw new TinkerRuntimeException("getPatchResultExtra, but intent is null");
}
return ShareIntentUtil.getStringExtra(intent, RESULT_CLASS_EXTRA);
}
@Override
protected void onHandleIntent(Intent intent) {
increasingPriority();
doApplyPatch(this, intent);
}
/**
* set the tinker notification id you want
* @param id
*/
public static void setTinkerNotificationId(int id) {
notificationId = id;
}
private static final String RUNNING_MARKER_FILE_RELPATH_PREFIX = "patch_service_status/running_";
/**
* Check if TinkerPatchService is running.
* @param context
*/
public static boolean isRunning(Context context) {
try {
final String serviceName = getTinkerPatchServiceName(context);
if (serviceName == null) {
return false;
}
final ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
if (am == null) {
return false;
}
final List<ActivityManager.RunningAppProcessInfo> runningProcInfos = am.getRunningAppProcesses();
if (runningProcInfos == null || runningProcInfos.size() == 0) {
return false;
}
int targetPid = 0;
for (ActivityManager.RunningAppProcessInfo procInfo : runningProcInfos) {
if (procInfo.processName.equals(serviceName)) {
targetPid = procInfo.pid;
break;
}
}
if (targetPid == 0) {
return false;
}
final File tinkerBaseDir = SharePatchFileUtil.getPatchDirectory(context);
final File runningMarkerFile = new File(tinkerBaseDir, RUNNING_MARKER_FILE_RELPATH_PREFIX + targetPid);
return runningMarkerFile.exists();
} catch (Throwable ignored) {
return false;
}
}
static void markRunning(Context context) {
final File tinkerBaseDir = SharePatchFileUtil.getPatchDirectory(context);
final File runningMarkerFile = new File(tinkerBaseDir,
RUNNING_MARKER_FILE_RELPATH_PREFIX + android.os.Process.myPid());
if (runningMarkerFile.exists()) {
return;
}
final File runningMarkerDir = runningMarkerFile.getParentFile();
if (runningMarkerDir.exists()) {
final File[] markerFiles = runningMarkerDir.listFiles();
if (markerFiles != null) {
for (File markerFile : markerFiles) {
markerFile.delete();
}
}
} else {
runningMarkerDir.mkdirs();
}
try {
if (!runningMarkerFile.createNewFile()) {
throw new IllegalStateException();
}
} catch (Throwable thr) {
ShareTinkerLog.printErrStackTrace(TAG, thr, "Fail to create running marker file.");
}
}
static void unmarkRunning(Context context) {
final File tinkerBaseDir = SharePatchFileUtil.getPatchDirectory(context);
final File runningMarkerFile = new File(tinkerBaseDir,
RUNNING_MARKER_FILE_RELPATH_PREFIX + android.os.Process.myPid());
if (runningMarkerFile.exists()) {
runningMarkerFile.delete();
}
}
private static AtomicBoolean sIsPatchApplying = new AtomicBoolean(false);
private static void doApplyPatch(Context context, Intent intent) {
// Since we may retry with IntentService, we should prevent
// racing here again.
if (!sIsPatchApplying.compareAndSet(false, true)) {
ShareTinkerLog.w(TAG, "TinkerPatchService doApplyPatch is running by another runner.");
return;
}
try {
markRunning(context);
Tinker tinker = Tinker.with(context);
tinker.getPatchReporter().onPatchServiceStart(intent);
if (intent == null) {
ShareTinkerLog.e(TAG, "TinkerPatchService received a null intent, ignoring.");
return;
}
String path = getPatchPathExtra(intent);
if (path == null) {
ShareTinkerLog.e(TAG, "TinkerPatchService can't get the path extra, ignoring.");
return;
}
File patchFile = new File(path);
final boolean useEmergencyMode = getPatchUseEmergencyMode(intent);
long begin = SystemClock.elapsedRealtime();
boolean result;
long cost;
Throwable e = null;
PatchResult patchResult = new PatchResult();
try {
if (upgradePatchProcessor == null) {
throw new TinkerRuntimeException("upgradePatchProcessor is null.");
}
result = upgradePatchProcessor.tryPatch(context, path, useEmergencyMode, patchResult);
} catch (Throwable throwable) {
e = throwable;
result = false;
tinker.getPatchReporter().onPatchException(patchFile, e);
}
cost = SystemClock.elapsedRealtime() - begin;
tinker.getPatchReporter()
.onPatchResult(patchFile, result, cost);
patchResult.isSuccess = result;
patchResult.rawPatchFilePath = path;
patchResult.useEmergencyMode = useEmergencyMode;
patchResult.totalCostTime = cost;
patchResult.type = tinker.getCustomPatcher() == null ? PatchResult.PATCH_TYPE_BSDIFF : PatchResult.PATCH_TYPE_CUSTOM;
patchResult.e = e;
unmarkRunning(context);
sIsPatchApplying.set(false);
AbstractResultService.runResultService(context, patchResult, getPatchResultExtra(intent));
} finally {
unmarkRunning(context);
}
}
private void increasingPriority() {
if (Build.VERSION.SDK_INT >= 26) {
ShareTinkerLog.i(TAG, "for system version >= Android O, we just ignore increasingPriority "
+ "job to avoid crash or toasts.");
return;
}
if ("ZUK".equals(Build.MANUFACTURER)) {
ShareTinkerLog.i(TAG, "for ZUK device, we just ignore increasingPriority "
+ "job to avoid crash.");
return;
}
ShareTinkerLog.i(TAG, "try to increase patch process priority");
try {
Notification notification = new Notification();
if (Build.VERSION.SDK_INT < 18) {
startForeground(notificationId, notification);
} else {
startForeground(notificationId, notification);
// start InnerService
startService(new Intent(this, InnerService.class));
}
} catch (Throwable e) {
ShareTinkerLog.i(TAG, "try to increase patch process priority error:" + e);
}
}
/**
* I don't want to do this, believe me
*/
public static class InnerService extends Service {
@Override
public void onCreate() {
super.onCreate();
try {
startForeground(notificationId, new Notification());
} catch (Throwable e) {
ShareTinkerLog.e(TAG, "InnerService set service for push exception:%s.", e);
}
stopSelf();
}
@Override
public void onDestroy() {
stopForeground(true);
super.onDestroy();
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
}