InstantRun框架源码分析之二
来源:互联网 发布:drcom校园网客户端mac 编辑:程序博客网 时间:2024/04/29 20:03
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的顺序都是不可变的,这样的就会出错。
- InstantRun框架源码分析之二
- InstantRun框架源码分析之一
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 深度理解Android InstantRun原理以及源码分析
- 【mybatis源码分析】原理分析之二:框架整体设计
- Android InstantRun原理分析
- skynet框架 源码分析 二
- min3d框架源码分析(二)
- MINA框架源码分析(二)
- skynet框架 源码分析 二
- AS新功能之InstantRun
- nginx 源码分析之框架
- Memcached源码分析之二
- AsyncTask源码分析之二
- LoaderManager源码分析之二
- RabbitMQ消息队列安装及在Myeclipse下的开发实例
- PermGen space内存溢出解决
- openCV识别定位五个圆的标识物进行定位和位姿确定
- 42. Trapping Rain Water
- 微信小程序城市天气预报切换显示
- InstantRun框架源码分析之二
- LaTex数学公式
- GCD的使用小结
- 一些不是特别常用的linux命令
- 僵尸进程和孤儿进程
- 欢迎使用CSDN-markdown编辑器
- 基于Maven远程仓库的第一个控件(控件阴影效果)
- PAT1001. A+B Format (20)
- c++ typeid对多种类型名称的输出