首先即时运行app的即时更新是通过内容提供者的启动而更新的,通过分析源码发现并没有修改的是application节点,也就是说合application multidex的方式不同。
<provider android:name="com.android.tools.ir.server.InstantRunContentProvider" android:multiprocess="true" android:authorities="cn.qssq666.radiogroupdemo.com.android.tools.ir.server.InstantRunContentProvider" />
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源码
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");
}
}
}
}
重点代码
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
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详细,逆向只是野路子,哈哈。
另外我还发愁的一个问题就是开发工具不能自动识别设备、或针对部分项目不开启即时运行,对不同设备自动切换非即时运行,搞的我搞逆向插件研究的时候总是要把即时运行给关了。不然死活都激活不了。