InstantRun框架源码分析之一
2017-05-05 21:59
531 查看
1, 概念
Instant Run是Android Studio2.0以后新增的一个运行机制,能够显著减少你第二次及以后的构建和部署时间。简单通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的看到修改的效果。
而在没有Instant Run之前,一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果。
传统的代码修改及编译部署流程
构建整个apk →部署app → app重启 → 重启Activity
Instant Run编译和部署流程
Cold Swap: app需要重启,比如继承关系的改变或方法的签名变化等。
Warm Swap: app无需重启,但是activity需要重启,比如资源的修改。
Hop Swap:方法内的简单逻辑修改,无需重启app和Activity。
2, 例子
利用Android Studio开发举例,界面有一个Button,点击之后显示” some bugs!!”。
public class MainActivity extends Activity { private TextView tv; private String changeStr; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); tv = (TextView)findViewById(R.id.tv); tv.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { change(v); } }); } public void change(View view){ changeStr = "some bugs!!"; tv.setText(changeStr); } }
如果想让该button显示” fix it!!”,修改change方法为
public void change(View view){ changeStr = " fix it!!"; tv.setText(changeStr); }
如果是以前,点击run之后,程序会重新构建并且重新部署到手机上,而如果你开启了InstantRun,
点击run之后,程序不会重新构造,当点下Button的时候, TextView的文字改成了fix it。
进行Hop Swap 时,Android Studio到底做了什么呢?
(1) 在第一次构建app的时候,它利用了transform去在每一个类注入了一个字段叫做change,
它实现了IncrementalChange接口,并且在每一个方法中插入了一个逻辑,如果change不为空,
就执行的change的accessdispatch方法,否则执行原方法的原来逻辑。
对应的类在app/build/intermediates/transforms/instantRun/debug/folders/1/5目录下。这里多说一句,InstantRun操作字节码用的是asm。
(2) 当修改完对应的代码点击run按钮之后,InstantRun会去生成对应的patch文件,
在app/build/intermediates/transforms/instantRun/debug/folders/4000/5目录下。
而对应patch文件中的补丁类的名字是你修改的那个类的名字后面加$override,并且实现了IncrementalChange接口。
(3) 生成一个纪录类AppPatchesLoaderImpl,用来记录哪些类被修改过。
(4) 通过AppPatchesLoaderImpl类将修改过的类中的赋值成中生成的change赋值成(2)中生成的xxxxoverride。
从上面的例子分析,
(1)中通过asm修改字节码后的MainActivity:
public class MainActivity extends Activity { private TextView tv; private String changeStr; public MainActivity() { IncrementalChange var1 = $change; if(var1 != null) { Object[] var2; Object[] var10003 = var2 = new Object[1]; var10003[0] = var2; Object[] var3 = (Object[])var1.access$dispatch("init$args.([Ljava/lang/Object;)Ljava/lang/Object;", var10003); this(var3, (InstantReloadException)null); } else { super(); } if(var1 != null) { var1.access$dispatch("init$body.(Lzjutkz/com/instantrundemo/MainActivity;)V", new Object[]{this}); } } public void onCreate(Bundle savedInstanceState) { IncrementalChange var2 = $change; if(var2 != null) { var2.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[]{this, savedInstanceState}); } else { super.onCreate(savedInstanceState); this.setContentView(45613595456); this.tv = (TextView)this.findViewById(225641111); } } public void change(View view) { IncrementalChange var2 = $change; if(var2 != null) { var2.access$dispatch("change.(Landroid/view/View;)V", new Object[]{this, view}); } else { this.changeStr = "some bugs!!"; this.tv.setText(this.changeStr); } } MainActivity(Object[] var1, InstantReloadException var2) { String var3 = (String)var1[0]; switch(var3.hashCode()) { case 1545445545: super(); return; case 4854255454: this(); return; default: throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var3, Integer.valueOf(var3.hashCode()), "zjutkz/com/instantrundemo/MainActivity"})); } } }
(2)中patch文件中的补丁类:
public class MainActivity$override implements IncrementalChange { public MainActivity$override() { } public static Object init$args(Object[] var0) { Object[] var1 = new Object[]{"android/support/v7/app/AppCompatActivity.()V"}; return var1; } public static void init$body(MainActivity $this) { } public static void onCreate(MainActivity $this, Bundle savedInstanceState) { Object[] var2 = new Object[]{savedInstanceState}; MainActivity.access$super($this, "onCreate.(Landroid/os/Bundle;)V", var2); $this.setContentView(5454521212312); AndroidInstantRuntime.setPrivateField($this, (TextView)$this.findViewById(1454899124), MainActivity.class, "tv"); } public static void change(MainActivity $this, View view) { AndroidInstantRuntime.setPrivateField($this, "fix it!!", MainActivity.class, "changeStr"); ((TextView)AndroidInstantRuntime.getPrivateField($this, MainActivity.class, "tv")).setText((String)AndroidInstantRuntime.getPrivateField($this, MainActivity.class, "changeStr")); } public Object access$dispatch(String var1, Object... var2) { switch(var1.hashCode()) { case -454613552: return init$args((Object[])var2[0]); case -145685212: onCreate((MainActivity)var2[0], (Bundle)var2[1]); return null; case 1441240363: change((MainActivity)var2[0], (View)var2[1]); return null; case 0154323232: init$body((MainActivity)var2[0]); return null; default: throw new InstantReloadException(String.format("String switch could not find \'%s\' with hashcode %s in %s", new Object[]{var1, Integer.valueOf(var1.hashCode()), "zjutkz/com/instantrundemo/MainActivity"})); } } }
(3)中生成的记录类:
public class AppPatchesLoaderImpl extends AbstractPatchesLoaderImpl { public AppPatchesLoaderImpl() { } public String[] getPatchedClasses() { return new String[]{"zjutkz.com.instantrundemo.MainActivity"}; }
Instant Run基本原理:
1,利用插件化思想, Instant-Run代码作为一个宿主程序,将app作为资源dex加载起来。
2,启动真正运行的业务代码。
将com.android.tools.fd.runtime.BootstrapApplication替换业务的Application类,反客为主,控制业务代码。
在此,主要分析BootstrapApplication的2个方法, attachBaseContext和onCreate。
3, attachBaseContext
首先看看看一下app/build/intermediates/bundles/debug/instant-run目录下的AndroidMenifest文件,中对应的Application被替换成了BootstrapApplication
android:name="com.android.tools.fd.runtime.BootstrapApplication"
BootstrapApplication.java位于app/build/intermediates/incremental-runtime-classes/debug目录下的instant-run.jar中。
BootstrapApplication的attachBaseContext方法如下,
protected void attachBaseContext(Context context) { if (!AppInfo.usingApkSplits) { String apkFile = context.getApplicationInfo().sourceDir; //获取资源路径 //最后一次修改值 long apkModified = apkFile != null ? new File(apkFile).lastModified() : 0L; createResources(apkModified); setupClassLoaders(context, context.getCacheDir().getPath(),apkModified); } createRealApplication(); super.attachBaseContext(context); if (this.realApplication != null) { try { Method attachBaseContext = ContextWrapper.class .getDeclaredMethod("attachBaseContext",new Class[] { Context.class }); attachBaseContext.setAccessible(true); attachBaseContext.invoke(this.realApplication, new Object[] { context }); } catch (Exception e) { throw new IllegalStateException(e); } } }
依次调用createResources/ setupClassLoaders/ createRealApplication,
然后利用反射调用realApplication也就是业务代码Application类的attachBaseContext方法。
3.1 createResources
BootstrapApplication的attachBaseContext方法如下,private void createResources(long apkModified) { FileManager.checkInbox(); File file = FileManager.getExternalResourceFile(); this.externalResourcePath = (file != null ? file.getPath() : null); ••••
主要是判断资源resource.ap_是否改变,然后保存resource.ap_的路径到变量externalResourcePath中。
资源文件resource.ap_ 位于
/data/data/trip.taobao.com.testandroid/files/instant-run/路径下。
trip.taobao.com.testandroid是apk的包名。
3.2 setupClassLoaders
setupClassLoaders调用流程图如下,BootstrapApplication的setupClassLoaders方法如下,
private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) { List<String> dexList = FileManager.getDexList(context, apkModified); Class<Server> server = Server.class; Class<MonkeyPatcher> patcher = MonkeyPatcher.class; if (!dexList.isEmpty()) { if (Log.isLoggable("InstantRun", 2)) { Log.v("InstantRun", "Bootstrapping class loader with dex list " + join('\n', dexList)); } ClassLoader classLoader = BootstrapApplication.class .getClassLoader(); String nativeLibraryPath; try { nativeLibraryPath = (String) classLoader.getClass() .getMethod("getLdLibraryPath", new Class[0]) .invoke(classLoader, new Object[0]); if (Log.isLoggable("InstantRun", 2)) { Log.v("InstantRun", "Native library path: " + nativeLibraryPath); } } catch (Throwable t) { Log.e("InstantRun", "Failed to determine native library path " + t.getMessage()); nativeLibraryPath = FileManager.getNativeLibraryFolder() .getPath(); } IncrementalClassLoader.inject(classLoader, nativeLibraryPath, codeCacheDir, dexList); } }
首先获取类加载器ClassLoader对象,然后获取natvie库的路径,一般位于
/data/data/包名/lib 路径下,最后调用IncrementalClassLoader 的inject方法,该方法如下,
public static ClassLoader inject(ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir, List<String> dexes) { IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader( classLoader, nativeLibraryPath, codeCacheDir, dexes); setParent(classLoader, incrementalClassLoader); return incrementalClassLoader; }
此处的classLoader对象是PathClassLoader对象,然后利用反射强制将IncrementalClassLoader设置为PathClassLoader的父类。
因此,这几个类的框架结构如下,
并且IncrementalClassLoader中还有一个委托类DelegateClassLoader,继承于BaseDexClassLoader。
Java的类加载模型是双亲委托的,所以之后加载类都会从IncrementalClassLoader中加载。
3.3 createRealApplication
private void createRealApplication() { if (AppInfo.applicationClass != null) { if (Log.isLoggable("InstantRun", 2)) { Log.v("InstantRun", "About to create real application of class name = " + AppInfo.applicationClass); } try { Class<? extends Application> realClass = (Class<? extends Application>) Class .forName(AppInfo.applicationClass); if (Log.isLoggable("InstantRun", 2)) { Log.v("InstantRun", "Created delegate app class successfully : " + realClass + " with class loader " + realClass.getClassLoader()); } Constructor<? extends Application> constructor = realClass .getConstructor(new Class[0]); this.realApplication = ((Application) constructor .newInstance(new Object[0])); if (Log.isLoggable("InstantRun", 2)) { Log.v("InstantRun", "Created real app instance successfully :" + this.realApplication); } } catch (Exception e) { throw new IllegalStateException(e); } } else { this.realApplication = new Application(); } }
createRealApplication首先获取classes.dex中的AppInfo类的applicationClass常量中保存的app真实的application,
然后利用发射创建application对象。如果classes.dex中没有application,就直接调用系统的接口创建application对象。
相关文章推荐
- InstantRun框架源码分析之二
- yii框架源码分析之Yii::createWebApplication()->run() 执行过程分析
- yii框架源码分析之Yii::createWebApplication()->run() 执行过程分析
- Prototype框架源码分析汇总
- 应用框架的设计与实现——.NET平台(6 源码分析)
- cxxtest单元测试框架源码分析(三):文本Listener实现
- TOMCAT源码分析(启动框架)(转)
- s3c2410 RTC驱动框架linux内核源码分析
- TOMCAT源码分析(启动框架)
- 优秀的轻量级网络开发框架spserver源码分析(二)
- cxxtest单元测试框架源码分析(一):类的组成关系
- 网上流传的天龙源码框架分析之一 --- 客户端简单介绍
- Linux网桥源码框架分析初步
- TOMCAT源码分析(启动框架)
- cxxtest单元测试框架源码分析(二):所有对外功能实现分析
- TOMCAT源码分析(启动框架)
- TOMCAT源码分析(启动框架)
- Linux操作系统网桥源码框架初步分析
- TOMCAT源码分析(启动框架)
- TOMCAT源码分析(启动框架)