Instant Run,是 android studio2.0 新增的一个运行机制,在你编码开发、测试或 debug 的时候,它都能显著减少你对当前应用的构建和部署的时间。通俗的解释就是,当你在 Android Studio 中改了你的代码,Instant Run 可以很快的让你看到你修改的效果。而在没有 Instant Run 之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果。
传统的代码修改及编译流程如下:构建整个 apk → 部署 app → app 重启 → 重启 Activity
Instant Run 构建项目的流程:构建修改的部分 → 部署修改的 dex 或资源 → 热部署,温部署,冷部署
热拔插:代码改变被应用、投射到 APP 上,不需要重启应用,不需要重建当前 activity。
场景:适用于多数的简单改变(包括一些方法实现的修改,或者变量值修改)
** 温拔插:**activity 需要被重启才能看到所需更改。
场景:典型的情况是代码修改涉及到了资源文件,即 resources。
** 冷拔插:**app 需要被重启(但是仍然不需要重新安装)
场景:任何涉及结构性变化的,比如:修改了继承规则、修改了方法签名等。
一个新的 App Server 类会被注入到 App 中,与 Bytecode instrumentation 协同监控代码的变化。
同时会有一个新的 Application 类,它注入了一个自定义类加载器(Class Loader), 同时该 Application 类会启动我们所需的新注入的 App Server。于是,Manifest 会被修改来确保我们的应用能使用这个新的 Application 类。(这里不必担心自己继承定义了 Application 类,Instant Run 添加的这个新 Application 类会代理我们自定义的 Application 类)
至此,Instant Run 已经可以跑起来了,在我们使用的时候,它会通过决策,合理运用冷温热拔插来协助我们大量地缩短构建程序的时间。
在 Instant Run 运行之前,Android Studio 会检查是否能连接到 App Server 中。并且确保这个 App Server 是 Android Studio 所需要的。这同样能确保该应用正处在前台。
Android Studio monitors: 运行着 Gradle 任务来生成增量. dex 文件(这个 dex 文件是对应着开发中的修改类) Android Studio 会提取这些. dex 文件发送到 App Server,然后部署到 App(Gradle 修改 class 的原理,请戳)。
App Server 会不断监听是否需要重写类文件,如果需要,任务会被立马执行。新的更改便能立即被响应。我们可以通过打断点的方式来查看。
温拔插需要重启 Activity,因为资源文件是在 Activity 创建时加载,所以必须重启 Activity 来重载资源文件。
目前来说,任何资源文件的修改都会导致重新打包再发送到 APP。但是,google 的开发团队正在致力于开发一个增量包,这个增量包只会包装修改过的资源文件并能部署到当前 APP 上。
所以温拔插实际上只能应对少数的情况,它并不能应付应用在架构、结构上的变化。
注:温拔插涉及到的资源文件修改,在 manifest 上是无效的(这里的无效是指不会启动 Instant Run),因为,manifest 的值是在 APK 安装的时候被读取,所以想要 manifest 下资源的修改生效,还需要触发一个完整的应用构建和部署。
应用部署的时候,会把工程拆分成十个部分,每部分都拥有自己的. dex 文件,然后所有的类会根据包名被分配给相应的. dex 文件。当冷拔插开启时,修改过的类所对应的. dex 文件,会重组生成新的. dex 文件,然后再部署到设备上。
之所以能这么做,是依赖于 Android 的 ART 模式,它能允许加载多个. dex 文件。ART 模式在 android4.4(API-19)中加入,但是 Dalvik 依然是首选,到了 android5.0(API-21),ART 模式才成为系统默认首选,所以 Instant Run 只能运行在 API-21 及其以上版本。
Instant Run 是被 Android Studio 控制的。所以我们只能通过 IDE 来启动它,如果通过设备来启动应用,Instant Run 会出现异常情况。在使用 Instant Run 来启动 Android app 的时候,应注意以下几点:
为了方便大家的理解,我们新建一个项目,里面不写任何的逻辑功能,只对 application 做一个修改:
首先,我们先反编译一下 APK 的构成,使用的工具:d2j-dex2jar 和 jd-gui。
我们要看的启动的信息就在这个 instant-run.zip 文件里面,解压 instant-run.zip,我们会发现,我们真正的业务代码都在这里。
从 instant-run 文件中我们猜想是 BootstrapApplication 替换了我们的 application,Instant-Run 代码作为一个宿主程序,将 app 作为资源 dex 加载起来。
那么 InstantRun 是怎么把业务代码运行起来的呢?
按照我们上面对 instant-run 运行机制的猜想,我们首先看一下 appliaction 的分析 attachBaseContext 和 onCreate 方法。
- 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 的 attachBaseContext 方法
- private void createResources(long apkModified) {
- FileManager.checkInbox();
- File file = FileManager.getExternalResourceFile();
- this.externalResourcePath = (file != null ? file.getPath() : null);
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Resource override is "
- + this.externalResourcePath);
- }
- if (file != null) {
- try {
- long resourceModified = file.lastModified();
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Resource patch last modified: "
- + resourceModified);
- Log.v("InstantRun", "APK last modified: " + apkModified
- + " "
- + (apkModified > resourceModified ? ">" : "<")
- + " resource patch");
- }
- if ((apkModified == 0L) || (resourceModified <= apkModified)) {
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun",
- "Ignoring resource file, older than APK");
- }
- this.externalResourcePath = null;
- }
- } catch (Throwable t) {
- Log.e("InstantRun", "Failed to check patch timestamps", t);
- }
- }
- }
说明:该方法主要是判断资源 resource.ap_是否改变,然后保存 resource.ap_的路径到 externalResourcePath 中。
- private static void setupClassLoaders(Context context, String codeCacheDir,
- long apkModified) {
- List dexList = FileManager.getDexList(context, apkModified);
- Class server = Server.class;
- Class 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);
- }
- }
说明,该方法是初始化一个 ClassLoaders 并调用 IncrementalClassLoader。
IncrementalClassLoader 的源码如下:
- public class IncrementalClassLoader extends ClassLoader {
- public static final boolean DEBUG_CLASS_LOADING = false;
- private final DelegateClassLoader delegateClassLoader;
- public IncrementalClassLoader(ClassLoader original,
- String nativeLibraryPath, String codeCacheDir, List dexes) {
- super(original.getParent());
- this.delegateClassLoader = createDelegateClassLoader(nativeLibraryPath,
- codeCacheDir, dexes, original);
- }
- public Class findClass(String className) throws ClassNotFoundException {
- try {
- return this.delegateClassLoader.findClass(className);
- } catch (ClassNotFoundException e) {
- throw e;
- }
- }
- private static class DelegateClassLoader extends BaseDexClassLoader {
- private DelegateClassLoader(String dexPath, File optimizedDirectory,
- String libraryPath, ClassLoader parent) {
- super(dexPath, optimizedDirectory, libraryPath, parent);
- }
- public Class findClass(String name) throws ClassNotFoundException {
- try {
- return super.findClass(name);
- } catch (ClassNotFoundException e) {
- throw e;
- }
- }
- }
- private static DelegateClassLoader createDelegateClassLoader(
- String nativeLibraryPath, String codeCacheDir, List dexes,
- ClassLoader original) {
- String pathBuilder = createDexPath(dexes);
- return new DelegateClassLoader(pathBuilder, new File(codeCacheDir),
- nativeLibraryPath, original);
- }
- private static String createDexPath(List dexes) {
- StringBuilder pathBuilder = new StringBuilder();
- boolean first = true;
- for (String dex : dexes) {
- if (first) {
- first = false;
- } else {
- pathBuilder.append(File.pathSeparator);
- }
- pathBuilder.append(dex);
- }
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Incremental dex path is "
- + BootstrapApplication.join('\n', dexes));
- }
- return pathBuilder.toString();
- }
- private static void setParent(ClassLoader classLoader, ClassLoader newParent) {
- try {
- Field parent = ClassLoader.class.getDeclaredField("parent");
- parent.setAccessible(true);
- parent.set(classLoader, newParent);
- } catch (IllegalArgumentException e) {
- throw new RuntimeException(e);
- } catch (IllegalAccessException e) {
- throw new RuntimeException(e);
- } catch (NoSuchFieldException e) {
- throw new RuntimeException(e);
- }
- }
- public static ClassLoader inject(ClassLoader classLoader,
- String nativeLibraryPath, String codeCacheDir, List dexes) {
- IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader(
- classLoader, nativeLibraryPath, codeCacheDir, dexes);
- setParent(classLoader, incrementalClassLoader);
- return incrementalClassLoader;
- }
- }
inject 方法是用来设置 classloader 的父子顺序的,使用 IncrementalClassLoader 来加载 dex。由于 ClassLoader 的双亲委托模式,也就是委托父类加载类,父类中找不到再在本 ClassLoader 中查找。
调用的效果图如下:
为了方便我们对委托父类加载机制的理解,我们可以做一个实验,在我们的 application 做一些 Log。
- @Override
- public void onCreate() {
- super.onCreate();
- try{
- Log.d(TAG,"###onCreate in myApplication");
- String classLoaderName = getClassLoader().getClass().getName();
- Log.d(TAG,"###onCreate in myApplication classLoaderName = "+classLoaderName);
- String parentClassLoaderName = getClassLoader().getParent().getClass().getName();
- Log.d(TAG,"###onCreate in myApplication parentClassLoaderName = "+parentClassLoaderName);
- String pParentClassLoaderName = getClassLoader().getParent().getParent().getClass().getName();
- Log.d(TAG,"###onCreate in myApplication pParentClassLoaderName = "+pParentClassLoaderName);
- }catch (Exception e){
- e.printStackTrace();
- }
- }
输出结果:
- 03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication classLoaderName = dalvik.system.PathClassLoader
- 03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication parentClassLoaderName = com.android.tools.fd.runtime.IncrementalClassLoader
- 03-20 10:43:42.475 27307-27307/mobctrl.net.testinstantrun D/MyApplication: ###onCreate in myApplication pParentClassLoaderName = java.lang.BootClassLoader
由此,我们知道,当前 PathClassLoader 委托 IncrementalClassLoader 加载 dex。
我们继续对 attachBaseContext() 继续分析:
- attachBaseContext.invoke(this.realApplication, new Object[] {
- context
- });
- 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 realClass = (Class) Class
- .forName(AppInfo.applicationClass);
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun",
- "Created delegate app class successfully : "
- + realClass + " with class loader "
- + realClass.getClassLoader());
- }
- Constructor 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();
- }
- }
该方法就是用 classes.dex 中的 AppInfo 类的 applicationClass 常量中保存的 app 真实的 application。由例子的分析我们可以知道 applicationClass 就是 com.xzh.demo.MyApplication。通过反射的方式,创建真是的 realApplication。
看完 attachBaseContext 我们继续看 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 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();
- }
- }
在 onCreate() 中我们需要注意以下方法:
monkeyPatchApplication → monkeyPatchExistingResources → Server 启动 → 调用 realApplication 的 onCreate 方法
- public static void monkeyPatchApplication(Context context,
- Application bootstrap, Application realApplication,
- String externalResourceFile) {
- try {
- Class activityThread = Class
- .forName("android.app.ActivityThread");
- Object currentActivityThread = getActivityThread(context,
- activityThread);
- Field mInitialApplication = activityThread
- .getDeclaredField("mInitialApplication");
- mInitialApplication.setAccessible(true);
- Application initialApplication = (Application) mInitialApplication
- .get(currentActivityThread);
- if ((realApplication != null) && (initialApplication == bootstrap)) {
- mInitialApplication.set(currentActivityThread, realApplication);
- }
- if (realApplication != null) {
- Field mAllApplications = activityThread
- .getDeclaredField("mAllApplications");
- mAllApplications.setAccessible(true);
- List allApplications = (List) mAllApplications
- .get(currentActivityThread);
- for (int i = 0; i < allApplications.size(); i++) {
- if (allApplications.get(i) == bootstrap) {
- allApplications.set(i, realApplication);
- }
- }
- }
- Class loadedApkClass;
- try {
- loadedApkClass = Class.forName("android.app.LoadedApk");
- } catch (ClassNotFoundException e) {
- loadedApkClass = Class
- .forName("android.app.ActivityThread$PackageInfo");
- }
- Field mApplication = loadedApkClass
- .getDeclaredField("mApplication");
- mApplication.setAccessible(true);
- Field mResDir = loadedApkClass.getDeclaredField("mResDir");
- mResDir.setAccessible(true);
- Field mLoadedApk = null;
- try {
- mLoadedApk = Application.class.getDeclaredField("mLoadedApk");
- } catch (NoSuchFieldException e) {
- }
- for (String fieldName : new String[] { "mPackages",
- "mResourcePackages" }) {
- Field field = activityThread.getDeclaredField(fieldName);
- field.setAccessible(true);
- Object value = field.get(currentActivityThread);
- for (Map.Entry> entry : ((Map>) value)
- .entrySet()) {
- Object loadedApk = ((WeakReference) entry.getValue()).get();
- if (loadedApk != null) {
- if (mApplication.get(loadedApk) == bootstrap) {
- if (realApplication != null) {
- mApplication.set(loadedApk, realApplication);
- }
- if (externalResourceFile != null) {
- mResDir.set(loadedApk, externalResourceFile);
- }
- if ((realApplication != null)
- && (mLoadedApk != null)) {
- mLoadedApk.set(realApplication, loadedApk);
- }
- }
- }
- }
- }
- } catch (Throwable e) {
- throw new IllegalStateException(e);
- }
- }
说明:该方法的作用是替换所有当前 app 的 application 为 realApplication。
替换的过程如下:
1. 替换 ActivityThread 的 mInitialApplication 为 realApplication
2. 替换 mAllApplications 中所有的 Application 为 realApplication
3. 替换 ActivityThread 的 mPackages,mResourcePackages 中的 mLoaderApk 中的 application 为 realApplication。
- public static void monkeyPatchExistingResources(Context context,
- String externalResourceFile, Collection activities) {
- if (externalResourceFile == null) {
- return;
- }
- try {
- AssetManager newAssetManager = (AssetManager) AssetManager.class
- .getConstructor(new Class[0]).newInstance(new Object[0]);
- Method mAddAssetPath = AssetManager.class.getDeclaredMethod(
- "addAssetPath", new Class[] { String.class });
- mAddAssetPath.setAccessible(true);
- if (((Integer) mAddAssetPath.invoke(newAssetManager,
- new Object[] { externalResourceFile })).intValue() == 0) {
- throw new IllegalStateException(
- "Could not create new AssetManager");
- }
- Method mEnsureStringBlocks = AssetManager.class.getDeclaredMethod(
- "ensureStringBlocks", new Class[0]);
- mEnsureStringBlocks.setAccessible(true);
- mEnsureStringBlocks.invoke(newAssetManager, new Object[0]);
- if (activities != null) {
- for (Activity activity : activities) {
- Resources resources = activity.getResources();
- try {
- Field mAssets = Resources.class
- .getDeclaredField("mAssets");
- mAssets.setAccessible(true);
- mAssets.set(resources, newAssetManager);
- } catch (Throwable ignore) {
- Field mResourcesImpl = Resources.class
- .getDeclaredField("mResourcesImpl");
- mResourcesImpl.setAccessible(true);
- Object resourceImpl = mResourcesImpl.get(resources);
- Field implAssets = resourceImpl.getClass()
- .getDeclaredField("mAssets");
- implAssets.setAccessible(true);
- implAssets.set(resourceImpl, newAssetManager);
- }
- Resources.Theme theme = activity.getTheme();
- try {
- try {
- Field ma = Resources.Theme.class
- .getDeclaredField("mAssets");
- ma.setAccessible(true);
- ma.set(theme, newAssetManager);
- } catch (NoSuchFieldException ignore) {
- Field themeField = Resources.Theme.class
- .getDeclaredField("mThemeImpl");
- themeField.setAccessible(true);
- Object impl = themeField.get(theme);
- Field ma = impl.getClass().getDeclaredField(
- "mAssets");
- ma.setAccessible(true);
- ma.set(impl, newAssetManager);
- }
- Field mt = ContextThemeWrapper.class
- .getDeclaredField("mTheme");
- mt.setAccessible(true);
- mt.set(activity, null);
- Method mtm = ContextThemeWrapper.class
- .getDeclaredMethod("initializeTheme",
- new Class[0]);
- mtm.setAccessible(true);
- mtm.invoke(activity, new Object[0]);
- Method mCreateTheme = AssetManager.class
- .getDeclaredMethod("createTheme", new Class[0]);
- mCreateTheme.setAccessible(true);
- Object internalTheme = mCreateTheme.invoke(
- newAssetManager, new Object[0]);
- Field mTheme = Resources.Theme.class
- .getDeclaredField("mTheme");
- mTheme.setAccessible(true);
- mTheme.set(theme, internalTheme);
- } catch (Throwable e) {
- Log.e("InstantRun",
- "Failed to update existing theme for activity "
- + activity, e);
- }
- pruneResourceCaches(resources);
- }
- }
- Collection> references;
- if (Build.VERSION.SDK_INT >= 19) {
- Class resourcesManagerClass = Class
- .forName("android.app.ResourcesManager");
- Method mGetInstance = resourcesManagerClass.getDeclaredMethod(
- "getInstance", new Class[0]);
- mGetInstance.setAccessible(true);
- Object resourcesManager = mGetInstance.invoke(null,
- new Object[0]);
- try {
- Field fMActiveResources = resourcesManagerClass
- .getDeclaredField("mActiveResources");
- fMActiveResources.setAccessible(true);
- ArrayMap> arrayMap = (ArrayMap) fMActiveResources
- .get(resourcesManager);
- references = arrayMap.values();
- } catch (NoSuchFieldException ignore) {
- Field mResourceReferences = resourcesManagerClass
- .getDeclaredField("mResourceReferences");
- mResourceReferences.setAccessible(true);
- references = (Collection) mResourceReferences
- .get(resourcesManager);
- }
- } else {
- Class activityThread = Class
- .forName("android.app.ActivityThread");
- Field fMActiveResources = activityThread
- .getDeclaredField("mActiveResources");
- fMActiveResources.setAccessible(true);
- Object thread = getActivityThread(context, activityThread);
- HashMap> map = (HashMap) fMActiveResources
- .get(thread);
- references = map.values();
- }
- for (WeakReference wr : references) {
- Resources resources = (Resources) wr.get();
- if (resources != null) {
- try {
- Field mAssets = Resources.class
- .getDeclaredField("mAssets");
- mAssets.setAccessible(true);
- mAssets.set(resources, newAssetManager);
- } catch (Throwable ignore) {
- Field mResourcesImpl = Resources.class
- .getDeclaredField("mResourcesImpl");
- mResourcesImpl.setAccessible(true);
- Object resourceImpl = mResourcesImpl.get(resources);
- Field implAssets = resourceImpl.getClass()
- .getDeclaredField("mAssets");
- implAssets.setAccessible(true);
- implAssets.set(resourceImpl, newAssetManager);
- }
- resources.updateConfiguration(resources.getConfiguration(),
- resources.getDisplayMetrics());
- }
- }
- } catch (Throwable e) {
- throw new IllegalStateException(e);
- }
- }
说明:该方法的作用是替换所有当前 app 的 mAssets 为 newAssetManager。
monkeyPatchExistingResources 的流程如下:
1. 如果 resource.ap_文件有改变,那么新建一个 AssetManager 对象 newAssetManager,然后用 newAssetManager 对象替换所有当前 Resource、Resource.Theme 的 mAssets 成员变量。
2. 如果当前的已经有 Activity 启动了,还需要替换所有 Activity 中 mAssets 成员变量
判断 Server 是否已经启动,如果没有启动,则启动 Server。然后调用 realApplication 的 onCreate 方法代理 realApplication 的生命周期。
接下来我们分析下 Server 负责的热部署、温部署和冷部署等问题。
首先重点关注一下 Server 的内部类 SocketServerReplyThread。
- private class SocketServerReplyThread extends Thread {
- private final LocalSocket mSocket;
- SocketServerReplyThread(LocalSocket socket) {
- this.mSocket = socket;
- }
- public void run() {
- try {
- DataInputStream input = new DataInputStream(
- this.mSocket.getInputStream());
- DataOutputStream output = new DataOutputStream(
- this.mSocket.getOutputStream());
- try {
- handle(input, output);
- } finally {
- try {
- input.close();
- } catch (IOException ignore) {
- }
- try {
- output.close();
- } catch (IOException ignore) {
- }
- }
- return;
- } catch (IOException e) {
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Fatal error receiving messages", e);
- }
- }
- }
- private void handle(DataInputStream input, DataOutputStream output)
- throws IOException {
- long magic = input.readLong();
- if (magic != 890269988L) {
- Log.w("InstantRun",
- "Unrecognized header format " + Long.toHexString(magic));
- return;
- }
- int version = input.readInt();
- output.writeInt(4);
- if (version != 4) {
- Log.w("InstantRun",
- "Mismatched protocol versions; app is using version 4 and tool is using version "
- + version);
- } else {
- int message;
- for (;;) {
- message = input.readInt();
- switch (message) {
- case 7:
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Received EOF from the IDE");
- }
- return;
- case 2:
- boolean active = Restarter
- .getForegroundActivity(Server.this.mApplication) != null;
- output.writeBoolean(active);
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun",
- "Received Ping message from the IDE; returned active = "
- + active);
- }
- break;
- case 3:
- String path = input.readUTF();
- long size = FileManager.getFileSize(path);
- output.writeLong(size);
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Received path-exists(" + path
- + ") from the " + "IDE; returned size="
- + size);
- }
- break;
- case 4:
- long begin = System.currentTimeMillis();
- path = input.readUTF();
- byte[] checksum = FileManager.getCheckSum(path);
- if (checksum != null) {
- output.writeInt(checksum.length);
- output.write(checksum);
- if (Log.isLoggable("InstantRun", 2)) {
- long end = System.currentTimeMillis();
- String hash = new BigInteger(1, checksum)
- .toString(16);
- Log.v("InstantRun", "Received checksum(" + path
- + ") from the " + "IDE: took "
- + (end - begin) + "ms to compute "
- + hash);
- }
- } else {
- output.writeInt(0);
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Received checksum(" + path
- + ") from the "
- + "IDE: returning ");
- }
- }
- break;
- case 5:
- if (!authenticate(input)) {
- return;
- }
- Activity activity = Restarter
- .getForegroundActivity(Server.this.mApplication);
- if (activity != null) {
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun",
- "Restarting activity per user request");
- }
- Restarter.restartActivityOnUiThread(activity);
- }
- break;
- case 1:
- if (!authenticate(input)) {
- return;
- }
- List changes = ApplicationPatch
- .read(input);
- if (changes != null) {
- boolean hasResources = Server.hasResources(changes);
- int updateMode = input.readInt();
- updateMode = Server.this.handlePatches(changes,
- hasResources, updateMode);
- boolean showToast = input.readBoolean();
- output.writeBoolean(true);
- Server.this.restart(updateMode, hasResources,
- showToast);
- }
- break;
- case 6:
- String text = input.readUTF();
- Activity foreground = Restarter
- .getForegroundActivity(Server.this.mApplication);
- if (foreground != null) {
- Restarter.showToast(foreground, text);
- } else if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun",
- "Couldn't show toast (no activity) : "
- + text);
- }
- break;
- }
- }
- }
- }
- }
说明:socket 开启后,开始读取数据,当读到 1 时,获取代码变化的 ApplicationPatch 列表,然后调用 handlePatches 来处理代码的变化。
- private int handlePatches(List 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;
- }
说明:本方法主要通过判断 Change 的内容,来判断采用什么模式(热部署、温部署或冷部署)
- 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 即可。
- private int handleHotSwapPatch(int updateMode, ApplicationPatch patch) {
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Received incremental code patch");
- }
- try {
- String dexFile = FileManager.writeTempDexFile(patch.getBytes());
- if (dexFile == null) {
- Log.e("InstantRun", "No file to write the code to");
- return updateMode;
- }
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Reading live code from " + dexFile);
- }
- String nativeLibraryPath = FileManager.getNativeLibraryFolder()
- .getPath();
- DexClassLoader dexClassLoader = new DexClassLoader(dexFile,
- this.mApplication.getCacheDir().getPath(),
- nativeLibraryPath, getClass().getClassLoader());
- Class aClass = Class.forName(
- "com.android.tools.fd.runtime.AppPatchesLoaderImpl", true,
- dexClassLoader);
- try {
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Got the patcher class " + aClass);
- }
- PatchesLoader loader = (PatchesLoader) aClass.newInstance();
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Got the patcher instance " + loader);
- }
- String[] getPatchedClasses = (String[]) aClass
- .getDeclaredMethod("getPatchedClasses", new Class[0])
- .invoke(loader, new Object[0]);
- if (Log.isLoggable("InstantRun", 2)) {
- Log.v("InstantRun", "Got the list of classes ");
- for (String getPatchedClass : getPatchedClasses) {
- Log.v("InstantRun", "class " + getPatchedClass);
- }
- }
- if (!loader.load()) {
- updateMode = 3;
- }
- } catch (Exception e) {
- Log.e("InstantRun", "Couldn't apply code changes", e);
- e.printStackTrace();
- updateMode = 3;
- }
- } catch (Throwable e) {
- Log.e("InstantRun", "Couldn't apply code changes", e);
- updateMode = 3;
- }
- return updateMode;
- }
说明:该方法将 patch 的 dex 文件写入到临时目录,然后使用 DexClassLoader 去加载 dex。然后反射调用 AppPatchesLoaderImpl 类的 load 方法。
需要强调的是:AppPatchesLoaderImpl 继承自抽象类 AbstractPatchesLoaderImpl,并实现了抽象方法:getPatchedClasses。而 AbstractPatchesLoaderImpl 抽象类代码如下:
- public abstract class AbstractPatchesLoaderImpl implements PatchesLoader {
- public abstract String[] getPatchedClasses();
- 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;
- }
- }
由上面的代码分析,我们对 Instant Run 的流程可以分析如下:
1,在第一次构建 apk 时,在每一个类中注入了一个 $change 的成员变量,它实现了 IncrementalChange 接口,并在每一个方法中,插入了一段类似的逻辑。
- IncrementalChange localIncrementalChange = $change;
- if (localIncrementalChange != null) {
- localIncrementalChange.access$dispatch("onCreate.(Landroid/os/Bundle;)V", new Object[] {
- this,
- ...
- });
- return;
- }
当 $change 不为空的时候,执行 IncrementalChange 方法。
2,当我们修改代码中方法的实现之后,点击 InstantRun,它会生成对应的 patch 文件来记录你修改的内容。patch 文件中的替换类是在所修改类名的后面追加 $override,并实现 IncrementalChange 接口。
3,生成 AppPatchesLoaderImpl 类,继承自 AbstractPatchesLoaderImpl,并实现 getPatchedClasses 方法,来记录哪些类被修改了。
4,调用 load 方法之后,根据 getPatchedClasses 返回的修改过的类的列表,去加载对应的
Instant Run 运行机制主要涉及到热部署、温部署和冷部署,主要是在第一次运行,app 运行时期,有代码修改时。
1. 把 Instant-Run.jar 和 instant-Run-bootstrap.jar 打包到主 dex 中
2. 替换 AndroidManifest.xml 中的 application 配置
3. 使用 asm 工具,在每个类中添加 $change,在每个方法前加逻辑
4. 把源代码编译成 dex,然后存放到压缩包 instant-run.zip 中
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 生效
在 Android 插件化、Android 热修复、apk 加壳 / 脱壳中借鉴了 Instant Run 运行机制,所以理解 Instant Run 运行机制对于向更深层次的研究是很有帮助的,对于我们自己书写框架也是有借鉴意义的。
来源: http://www.bubuko.com/infodetail-1991906.html