您的位置:首页 > 移动开发 > Android开发

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对象。
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息