简书链接:即时运行app的逆向分析以及原理浅析以及谈谈xposed免重启更新是否能够兼容即时运行的可行性
文章字数:888,阅读全文大约需要3分钟
首先即时运行app的即时更新是通过内容提供者的启动而更新的,通过分析源码发现并没有修改的是application节点,也就是说合application multidex的方式不同。

1
2
<provider android:name="com.android.tools.ir.server.InstantRunContentProvider" android:multiprocess="true" android:authorities="cn.qssq666.radiogroupdemo.com.android.tools.ir.server.InstantRunContentProvider" />

image.png

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

public final class InstantRunContentProvider extends ContentProvider {
public boolean onCreate() {
if (isMainProcess()) {
Log.i("InstantRun", "starting instant run server: is main process");
Server.create(getContext());
} else {
Log.i("InstantRun", "not starting instant run server: not main process");
}
return true;
}

private boolean isMainProcess() {
boolean foundPackage = false;
boolean isMainProcess = false;
if (AppInfo.applicationId == null) {
return isMainProcess;
}
int pid = Process.myPid();
for (RunningAppProcessInfo processInfo : ((ActivityManager) getContext().getSystemService("activity")).getRunningAppProcesses()) {
if (AppInfo.applicationId.equals(processInfo.processName)) {
foundPackage = true;
if (processInfo.pid == pid) {
isMainProcess = true;
break;
}
}
}
if (isMainProcess || foundPackage) {
return isMainProcess;
}
isMainProcess = true;
Log.w("InstantRun", "considering this process main process:no process with this package found?!");
return isMainProcess;
}

Server源码

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

public class Server {
private static final boolean POST_ALIVE_STATUS = false;
private static final boolean RESTART_LOCALLY = false;
private static int wrongTokenCount;
private final Context context;
private LocalServerSocket serverSocket;

private Server(java.lang.String r1, android.content.Context r2) {
/* JADX: method processing error */
/*
Error: jadx.core.utils.exceptions.DecodeException: Load method exception in method: com.android.tools.ir.server.Server.<init>(java.lang.String, android.content.Context):void
at jadx.core.dex.nodes.MethodNode.load(MethodNode.java:116)
at jadx.core.dex.nodes.ClassNode.load(ClassNode.java:249)
at jadx.core.ProcessClass.process(ProcessClass.java:34)
at jadx.api.JadxDecompiler.processClass(JadxDecompiler.java:306)
at jadx.api.JavaClass.decompile(JavaClass.java:62)
Caused by: java.lang.NullPointerException
at jadx.core.dex.nodes.MethodNode.addJump(MethodNode.java:370)
at jadx.core.dex.nodes.MethodNode.initJumps(MethodNode.java:356)
at jadx.core.dex.nodes.MethodNode.load(MethodNode.java:106)
... 4 more
*/
/*
r0 = this;
r4.<init>();
r4.context = r6;
r0 = new android.net.LocalServerSocket; Catch:{ IOException -> 0x005c }
r0.<init>(r5); Catch:{ IOException -> 0x005c }
r4.serverSocket = r0; Catch:{ IOException -> 0x005c }
r0 = "InstantRun"; Catch:{ IOException -> 0x005c }
r1 = 2; Catch:{ IOException -> 0x005c }
r0 = android.util.Log.isLoggable(r0, r1); Catch:{ IOException -> 0x005c }
if (r0 == 0) goto L_0x0039; Catch:{ IOException -> 0x005c }
L_0x0015:
r0 = "InstantRun"; Catch:{ IOException -> 0x005c }
r2 = new java.lang.StringBuilder; Catch:{ IOException -> 0x005c }
r2.<init>(); Catch:{ IOException -> 0x005c }
r3 = "Starting server socket listening for package "; Catch:{ IOException -> 0x005c }
r2.append(r3); Catch:{ IOException -> 0x005c }
r2.append(r5); Catch:{ IOException -> 0x005c }
r3 = " on "; Catch:{ IOException -> 0x005c }
r2.append(r3); Catch:{ IOException -> 0x005c }
r3 = r4.serverSocket; Catch:{ IOException -> 0x005c }
r3 = r3.getLocalSocketAddress(); Catch:{ IOException -> 0x005c }
r2.append(r3); Catch:{ IOException -> 0x005c }
r2 = r2.toString(); Catch:{ IOException -> 0x005c }
android.util.Log.v(r0, r2); Catch:{ IOException -> 0x005c }
r4.startServer();
r0 = "InstantRun";
r0 = android.util.Log.isLoggable(r0, r1);
if (r0 == 0) goto L_0x005b;
r0 = "InstantRun";
r1 = new java.lang.StringBuilder;
r1.<init>();
r2 = "Started server for package ";
r1.append(r2);
r1.append(r5);
r1 = r1.toString();
android.util.Log.v(r0, r1);
return;
L_0x005c:
r0 = move-exception;
r1 = "InstantRun";
r2 = new java.lang.StringBuilder;
r2.<init>();
r3 = "IO Error creating local socket at ";
r2.append(r3);
r2.append(r5);
r2 = r2.toString();
android.util.Log.e(r1, r2, r0);
return;
*/
throw new UnsupportedOperationException("Method not decompiled: com.android.tools.ir.server.Server.<init>(java.lang.String, android.content.Context):void");
}

static /* synthetic */ int access$208() {
int i = wrongTokenCount;
wrongTokenCount = i + 1;
return i;
}

public static Server create(Context context) {
return new Server(context.getPackageName(), context);
}

private void startServer() {
try {
new Thread(new SocketServerThread(this, null)).start();
} catch (Throwable e) {
if (Log.isLoggable("InstantRun", 6)) {
Log.e("InstantRun", "Fatal error starting Instant Run server", e);
}
}
}

public void shutdown() {
if (this.serverSocket != null) {
try {
this.serverSocket.close();
} catch (IOException e) {
}
this.serverSocket = null;
}
}

private static boolean isResourcePath(String path) {
if (!path.equals("resources.ap_")) {
if (!path.startsWith("res/")) {
return false;
}
}
return true;
}

private static boolean hasResources(List<ApplicationPatch> changes) {
for (ApplicationPatch change : changes) {
if (isResourcePath(change.getPath())) {
return true;
}
}
return false;
}

private int handlePatches(List<ApplicationPatch> changes, boolean hasResources, int updateMode) {
if (hasResources) {
FileManager.startUpdate();
}
for (ApplicationPatch change : changes) {
String path = change.getPath();
if (path.equals("classes.dex.3")) {
updateMode = handleHotSwapPatch(updateMode, change);
} else if (isResourcePath(path)) {
updateMode = handleResourcePatch(updateMode, change, path);
}
}
if (hasResources) {
FileManager.finishUpdate(true);
}
return updateMode;
}

private static int handleResourcePatch(int updateMode, ApplicationPatch patch, String path) {
if (Log.isLoggable("InstantRun", 2)) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Received resource changes (");
stringBuilder.append(path);
stringBuilder.append(")");
Log.v("InstantRun", stringBuilder.toString());
}
FileManager.writeAaptResources(path, patch.getBytes());
return Math.max(updateMode, 2);
}

private int handleHotSwapPatch(int updateMode, ApplicationPatch patch) {
if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "Received incremental code patch");
}
try {
String dexFile = FileManager.writeTempDexFile(patch.getBytes());
if (dexFile == null) {
Log.e("InstantRun", "No file to write the code to");
return updateMode;
}
if (Log.isLoggable("InstantRun", 2)) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Reading live code from ");
stringBuilder.append(dexFile);
Log.v("InstantRun", stringBuilder.toString());
}
Class<?> aClass = Class.forName("com.android.tools.ir.runtime.AppPatchesLoaderImpl", true, new DexClassLoader(dexFile, this.context.getCacheDir().getPath(), FileManager.getNativeLibraryFolder().getPath(), getClass().getClassLoader()));
if (Log.isLoggable("InstantRun", 2)) {
StringBuilder stringBuilder2 = new StringBuilder();
stringBuilder2.append("Got the patcher class ");
stringBuilder2.append(aClass);
Log.v("InstantRun", stringBuilder2.toString());
}
PatchesLoader loader = (PatchesLoader) aClass.newInstance();
if (Log.isLoggable("InstantRun", 2)) {
StringBuilder stringBuilder3 = new StringBuilder();
stringBuilder3.append("Got the patcher instance ");
stringBuilder3.append(loader);
Log.v("InstantRun", stringBuilder3.toString());
}
int i = 0;
String[] getPatchedClasses = (String[]) aClass.getDeclaredMethod("getPatchedClasses", new Class[0]).invoke(loader, new Object[0]);
if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "Got the list of classes ");
int length = getPatchedClasses.length;
while (i < length) {
String getPatchedClass = getPatchedClasses[i];
StringBuilder stringBuilder4 = new StringBuilder();
stringBuilder4.append("class ");
stringBuilder4.append(getPatchedClass);
Log.v("InstantRun", stringBuilder4.toString());
i++;
}
}
if (!loader.load()) {
updateMode = 3;
}
return updateMode;
} catch (Exception e) {
Log.e("InstantRun", "Couldn't apply code changes", e);
e.printStackTrace();
updateMode = 3;
} catch (Throwable e2) {
Log.e("InstantRun", "Couldn't apply code changes", e2);
updateMode = 3;
}
}

private void restart(int updateMode, boolean incrementalResources, boolean toast) {
if (Log.isLoggable("InstantRun", 2)) {
StringBuilder stringBuilder = new StringBuilder();
stringBuilder.append("Finished loading changes; update mode =");
stringBuilder.append(updateMode);
Log.v("InstantRun", stringBuilder.toString());
}
if (updateMode != 0) {
if (updateMode != 1) {
StringBuilder stringBuilder2;
List<Activity> activities = Restarter.getActivities(this.context, false);
if (incrementalResources && updateMode == 2) {
File file = FileManager.getExternalResourceFile();
if (Log.isLoggable("InstantRun", 2)) {
stringBuilder2 = new StringBuilder();
stringBuilder2.append("About to update resource file=");
stringBuilder2.append(file);
stringBuilder2.append(", activities=");
stringBuilder2.append(activities);
Log.v("InstantRun", stringBuilder2.toString());
}
if (file != null) {
MonkeyPatcher.monkeyPatchExistingResources(this.context, file.getPath(), activities);
} else {
Log.e("InstantRun", "No resource file found to apply");
updateMode = 3;
}
}
Activity activity = Restarter.getForegroundActivity(this.context);
if (updateMode == 2) {
if (activity != null) {
if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "Restarting activity only!");
}
boolean handledRestart = false;
try {
Object result = activity.getClass().getMethod("onHandleCodeChange", new Class[]{Long.TYPE}).invoke(activity, new Object[]{Long.valueOf(0)});
if (Log.isLoggable("InstantRun", 2)) {
stringBuilder2 = new StringBuilder();
stringBuilder2.append("Activity ");
stringBuilder2.append(activity);
stringBuilder2.append(" provided manual restart method; return ");
stringBuilder2.append(result);
Log.v("InstantRun", stringBuilder2.toString());
}
if (Boolean.TRUE.equals(result)) {
handledRestart = true;
if (toast) {
Restarter.showToast(activity, "Applied changes");
}
}
} catch (Throwable th) {
}
if (!handledRestart) {
if (toast) {
Restarter.showToast(activity, "Applied changes, restarted activity");
}
Restarter.restartActivityOnUiThread(activity);
}
return;
}
if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "No activity found, falling through to do a full app restart");
}
updateMode = 3;
}
if (updateMode != 3) {
if (Log.isLoggable("InstantRun", 6)) {
StringBuilder stringBuilder3 = new StringBuilder();
stringBuilder3.append("Unexpected update mode: ");
stringBuilder3.append(updateMode);
Log.e("InstantRun", stringBuilder3.toString());
}
return;
}
if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "Waiting for app to be killed and restarted by the IDE...");
}
return;
}
}
if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "Applying incremental code without restart");
}
if (toast) {
Activity foreground = Restarter.getForegroundActivity(this.context);
if (foreground != null) {
Restarter.showToast(foreground, "Applied code changes without activity restart");
} else if (Log.isLoggable("InstantRun", 2)) {
Log.v("InstantRun", "Couldn't show toast: no activity found");
}
}
}
}

重点代码

1
2
3
4
5
6
7
8
9
10
private void startServer() {
try {
new Thread(new SocketServerThread(this, null)).start();
} catch (Throwable e) {
if (Log.isLoggable("InstantRun", 6)) {
Log.e("InstantRun", "Fatal error starting Instant Run server", e);
}
}
}

base.apk

image.png
base.apk就有很多个apk。那么到底是如何实现多dex加载的呢?不通过multidex技术,仅仅是通过内容提供者.

而上面的几个dex并不是主程序的,也就是说真正的代码被分割在data/app/包名/下,
包含了如下:split_lib_dependencies_apk.apk以及 split_lib_slice_[0_9]_.apk的10个apk中。

ok,下面的分析交给大家了,我只是遇到了一个问题,就是一个插件apk里面多个dex 在解压后加载然后融合出现了问题,融合貌似成功了,但是还是找不到,我特么快疯了。 各位大佬懂得求支个招。哈哈

另外懂了大概即时运行原理,那么也自然可以让xposed免重启技术兼容即时运行,但是无奈老夫功力欠缺,暂时先放一放,等功力提升的时候再研究研究。 另外对于官方的即时运行apk我都没玩过,也许我这分析半天还没有一个官方的demo详细,逆向只是野路子,哈哈。

另外我还发愁的一个问题就是开发工具不能自动识别设备、或针对部分项目不开启即时运行,对不同设备自动切换非即时运行,搞的我搞逆向插件研究的时候总是要把即时运行给关了。不然死活都激活不了。