InstantRun框架源码分析之一

本文深入解析了Android Studio的InstantRun特性,介绍了其如何减少构建和部署时间,通过字节码注入、增量类加载等机制实现代码修改即时生效,提高开发效率。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

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对象。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值