InstantRun框架源码分析之二

4, onCreate

BootstrapApplication的onCreate方法如下,

public void onCreate() {
		if (!AppInfo.usingApkSplits) {
			MonkeyPatcher.monkeyPatchApplication(this, this,
					this.realApplication, this.externalResourcePath);

			MonkeyPatcher.monkeyPatchExistingResources(this,
					this.externalResourcePath, null);
		} else {
			MonkeyPatcher.monkeyPatchApplication(this, this,
					this.realApplication, null);
		}
		super.onCreate();
		if (AppInfo.applicationId != null) {
			try {
				boolean foundPackage = false;
				int pid = Process.myPid();
				ActivityManager manager = (ActivityManager) getSystemService("activity");

				List<ActivityManager.RunningAppProcessInfo> processes = manager
						.getRunningAppProcesses();
				boolean startServer = false;
				if ((processes != null) && (processes.size() > 1)) {
					for (ActivityManager.RunningAppProcessInfo processInfo : processes) {
						if (AppInfo.applicationId
								.equals(processInfo.processName)) {
							foundPackage = true;
							if (processInfo.pid == pid) {
								startServer = true;
								break;
							}
						}
					}
					if ((!startServer) && (!foundPackage)) {
						startServer = true;
						if (Log.isLoggable("InstantRun", 2)) {
							Log.v("InstantRun",
									"Multiprocess but didn't find process with package: starting server anyway");
						}
					}
				} else {
					startServer = true;
				}
				if (startServer) {
					Server.create(AppInfo.applicationId, this);
				}
			} catch (Throwable t) {
				if (Log.isLoggable("InstantRun", 2)) {
					Log.v("InstantRun", "Failed during multi process check", t);
				}
				Server.create(AppInfo.applicationId, this);
			}
		}
		if (this.realApplication != null) {
			this.realApplication.onCreate();
		}
	}

依次调用MonkeyPatcher的monkeyPatchApplication/ monkeyPatchExistingResources和Server的create方法,

然后利用反射调用realApplication也就是业务代码Application类的onCreate方法。

 

monkeyPatchApplication方法主要逻辑如下,

1.替换ActivityThread的变量mInitialApplication为realApplication

2.替换ActivityThread的变量mAllApplications 中所有的Application为realApplication。

3.替换ActivityThread的变量mPackages,mResourcePackages中的mLoaderApk中的application为realApplication。

反正一句话,替换ActivityThread中和Application有关的所有变量,并且将对应的资源文件resource.ap_也替换。

 

monkeyPatchExistingResources方法逻辑如下,

1.如果resource.ap_文件有改变,那么新建一个AssetManager对象newAssetManager,

然后用newAssetManager对象替换所有当前Resource、Resource.Theme的mAssets成员变量。

2.如果当前的已经有Activity启动了,还需要替换所有Activity中mAssets成员变量。

5, Server

Server主要负责热部署、温部署和冷部署。调用的流程图如下,


Server的create方法如下,

public static void create(String packageName, Application application) {
		new Server(packageName, application);
	}

直接调用Server的构造方法。

内部类SocketServerReplyThread的handle方法从线程中读取数据之后,进行简单的校验。

1,如果读到7,则表示已经读到文件的末尾,退出读取操作

2,如果读到2,则表示获取当前Activity活跃状态,并且进行记录

3,如果读到3,读取UTF-8字符串路径,读取该路径下文件长度,并且进行记录

4,如果读到4,读取UTF-8字符串路径,获取该路径下文件MD5值,如果没有,则记录0,否则记录MD5值和长度。

5,如果读到5,先校验输入的值是否正确(根据token来判断),如果正确,则在UI线程重启Activity

6,如果读到1,先校验输入的值是否正确(根据token来判断),如果正确,获取代码变化的ApplicationPatch列表,

首先调用Server的handlePatches方法进行处理,然后调用Server的restart方法进行重启

7,如果读到6,读取UTF-8字符串,showToast

InstantRun内部使用了Socket来进行通信。也就是说当我们修改完程序点击run之后,

AndroidStudio会通过socket将数据传递给我们,最终调用的是handlePatches方法。

Server的handlePatches方法如下,

private int handlePatches(List<ApplicationPatch> changes,
			boolean hasResources, int updateMode) {
		if (hasResources) {
			FileManager.startUpdate();
		}
		for (ApplicationPatch change : changes) {
			String path = change.getPath();
			if (path.endsWith(".dex")) {
				handleColdSwapPatch(change);

				boolean canHotSwap = false;
				for (ApplicationPatch c : changes) {
					if (c.getPath().equals("classes.dex.3")) {
						canHotSwap = true;
						break;
					}
				}
				if (!canHotSwap) {
					updateMode = 3;
				}
			} else 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;
	}

根据ApplicationPatch列表中逐个取出改变的ApplicationPatch文件,

1.如果后缀为“.dex”,调用 handleColdSwapPatch方法进行冷部署处理

2.如果后缀为“classes.dex.3”,调用 handleHotSwapPatch方法进行热部署处理

3.其他情况,温部署,调用 handleResourcePatch方法处理资源

5.1 Cold Swap

Server 的handleColdSwapPatch方法如下,

private static void handleColdSwapPatch(ApplicationPatch patch) {
		if (patch.path.startsWith("slice-")) {
			File file = FileManager.writeDexShard(patch.getBytes(), patch.path);
			if (Log.isLoggable("InstantRun", 2)) {
				Log.v("InstantRun", "Received dex shard " + file);
			}
		}
	}

把dex文件写到私有目录,等待整个app重启,重启之后,使用IncrementalClassLoader加载dex。

5.2 Hop Swap

在Server 的handleHotSwapPatch方法中,

首先将patch的dex文件写入到临时目录,然后使用DexClassLoader去加载dex。AppPatchesLoaderImpl是编译生成的,在这里可以看到修改的类等信息。

然后利用反射调用AppPatchesLoaderImpl类的load方法,实际上是调用父类AbstractPatchesLoaderImpl的load方法。load方法如下,

public boolean load() {
		try {
			for (String className : getPatchedClasses()) {
				ClassLoader cl = getClass().getClassLoader();
				Class<?> aClass = cl.loadClass(className + "$override");
				Object o = aClass.newInstance();
				Class<?> originalClass = cl.loadClass(className);
				Field changeField = originalClass.getDeclaredField("$change");

				changeField.setAccessible(true);

				Object previous = changeField.get(null);
				if (previous != null) {
					Field isObsolete = previous.getClass().getDeclaredField(
							"$obsolete");
					if (isObsolete != null) {
						isObsolete.set(null, Boolean.valueOf(true));
					}
				}
				changeField.set(null, o);
				if ((Log.logging != null)
						&& (Log.logging.isLoggable(Level.FINE))) {
					Log.logging.log(Level.FINE, String.format("patched %s",
							new Object[] { className }));
				}
			}
		} catch (Exception e) {
			if (Log.logging != null) {
				Log.logging.log(Level.SEVERE, String.format(
						"Exception while patching %s",
						new Object[] { "foo.bar" }), e);
			}
			return false;
		}
		return true;
	}

加载class名称+override类,给$change赋值,这就是Instance Run的关键, $change又是什么意思呢?

在运行程序的时候,就可以根据该变量,执行被替换的函数。

5.3 Warm Swap

Server 的handleResourcePatch方法如下,

private static int handleResourcePatch(int updateMode,
			ApplicationPatch patch, String path) {
		if (Log.isLoggable("InstantRun", 2)) {
			Log.v("InstantRun", "Received resource changes (" + path + ")");
		}
		FileManager.writeAaptResources(path, patch.getBytes());

		updateMode = Math.max(updateMode, 2);
		return updateMode;
	}

将资源的patch写入到私有目录,等到restart之后生效. 可以看到获取了对应的资源文件,

就是/data/data/[applicationId]/files/instant-run/resources.ap_,InstantRun直接对它进行了字节码操作,

把通过Socket传过来的修改过的资源传递了进去。

最后,Server的restart方法根据不同的InstantRun的updateMode模式,进行重启,使上述的3中部署模式生效。

6,总结

第一次编译apk:

 

1.把Instant-Run.jar和instant-Run-bootstrap.jar打包到主dex中

2.替换AndroidManifest.xml中的application配置

3.使用asm工具,在每个类中添加$change,在每个方法前加逻辑

4.把源代码编译成dex,然后存放到压缩包instant-run.zip中

app运行期:

1.获取更改后资源resource.ap_的路径

2.设置ClassLoader。setupClassLoader:

使用IncrementalClassLoader加载apk的代码,将原有的BootClassLoader → PathClassLoader改为BootClassLoader

       → IncrementalClassLoader → PathClassLoader继承关系。

3.createRealApplication:

创建apk真实的application

4.monkeyPatchApplication

反射替换ActivityThread中的各种Application成员变量

5.monkeyPatchExistingResource

反射替换所有存在的AssetManager对象

6.调用realApplication的onCreate方法

7.启动Server,Socket接收patch列表

有代码修改时

1.生成对应的$override类

2.生成AppPatchesLoaderImpl类,记录修改的类列表

3.打包成patch,通过socket传递给app

4.app的server接收到patch之后,分别按照handleColdSwapPatch、handleHotSwapPatch、handleResourcePatch等待对patch进行处理

5.restart使patch生效


InstantRun利用了transform api去生成字节码,这样的方式不灵活,因为所有的transform操作是由TransformManager管理的,

也就是说它执行的时机是固定的,如果涉及到混淆,dex等操作,这些task的顺序都是不可变的,这样的就会出错。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值