深入理解AndroidInstantRun运行机制

Instant Run

创新互联专注于企业营销型网站建设、网站重做改版、琼结网站定制设计、自适应品牌网站建设、H5页面制作购物商城网站建设、集团公司官网建设、外贸网站建设、高端网站制作、响应式网页设计等建站业务,价格优惠性价比高,为琼结等各大城市提供网站开发制作服务。

Instant Run,是android studio2.0新增的一个运行机制,在你编码开发、测试或debug的时候,它都能显著减少你对当前应用的构建和部署的时间。通俗的解释就是,当你在Android Studio中改了你的代码,Instant Run可以很快的让你看到你修改的效果。而在没有Instant Run之前,你的一个小小的修改,都肯能需要几十秒甚至更长的等待才能看到修改后的效果。

传统的代码修改及编译部署流程

传统的代码修改及编译流程如下:构建整个apk → 部署app → app重启 → 重启Activity

Instant Run编译和部署流程

Instant Run构建项目的流程:构建修改的部分 → 部署修改的dex或资源 → 热部署,温部署,冷部署

热拔插,温拔插,冷拔插

热拔插:代码改变被应用、投射到APP上,不需要重启应用,不需要重建当前activity。

场景:适用于多数的简单改变(包括一些方法实现的修改,或者变量值修改)

温拔插:activity需要被重启才能看到所需更改。

场景:典型的情况是代码修改涉及到了资源文件,即resources。

冷拔插:app需要被重启(但是仍然不需要重新安装)

场景:任何涉及结构性变化的,比如:修改了继承规则、修改了方法签名等。

首次运行Instant Run,Gradle执行过程

一个新的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一些注意点

Instant Run是被Android Studio控制的。所以我们只能通过IDE来启动它,如果通过设备来启动应用,Instant Run会出现异常情况。在使用Instant Run来启动Android app的时候,应注意以下几点:

如果应用的minSdkVersion小于21,可能多数的Instant Run功能会挂掉,这里提供一个解决方法,通过product flavor建立一个minSdkVersion大于21的新分支,用来debug。

Instant Run目前只能在主进程里运行,如果应用是多进程的,类似微信,把webView抽出来单独一个进程,那热、温拔插会被降级为冷拔插。

在Windows下,Windows Defender Real-Time Protection可能会导致Instant Run挂掉,可用通过添加白名单列表解决。

暂时不支持Jack compiler,Instrumentation Tests,或者同时部署到多台设备。

结合Demo深度理解

为了方便大家的理解,我们新建一个项目,里面不写任何的逻辑功能,只对application做一个修改:

首先,我们先反编译一下APK的构成,使用的工具:d2j-dex2jar 和jd-gui。

我们要看的启动的信息就在这个instant-run.zip文件里面,解压instant-run.zip,我们会发现,我们真正的业务代码都在这里。

从instant-run文件中我们猜想是BootstrapApplication替换了我们的application,Instant-Run代码作为一个宿主程序,将app作为资源dex加载起来。

那么InstantRun是怎么把业务代码运行起来的呢?

Instant Run如何启动app

按照我们上面对instant-run运行机制的猜想,我们首先看一下appliaction的分析attachBaseContext和onCreate方法。

attachBaseContext()

 
 
 
 
  1. protected void attachBaseContext(Context context) {
  2.        if (!AppInfo.usingApkSplits) {
  3.             String apkFile = context.getApplicationInfo().sourceDir;
  4.             long apkModified = apkFile != null ? new File(apkFile).lastModified() : 0L;
  5.             createResources(apkModified);
  6.             setupClassLoaders(context, context.getCacheDir().getPath(), apkModified);
  7.        }
  8.        createRealApplication();
  9.        super.attachBaseContext(context);
  10.        if (this.realApplication != null) {
  11.             try {
  12.                  Method attachBaseContext = ContextWrapper.class.getDeclaredMethod("attachBaseContext", new Class[] { Context.class });
  13.                  attachBaseContext.setAccessible(true);
  14.                  attachBaseContext.invoke(this.realApplication, new Object[] { context });
  15.             } catch (Exception e) {
  16.                  throw new IllegalStateException(e);
  17.             }
  18.       }
  19. }

我们依次需要关注的方法有:

createResources → setupClassLoaders → createRealApplication → 调用realApplication的attachBaseContext方法

createResources()

 
 
 
 
  1. private void createResources(long apkModified) {
  2.        FileManager.checkInbox();
  3.        File file = FileManager.getExternalResourceFile();
  4.        this.externalResourcePath = (file != null ? file.getPath() : null);
  5.        if (Log.isLoggable("InstantRun", 2)) {
  6.             Log.v("InstantRun", "Resource override is " + this.externalResourcePath);
  7.        }
  8.        if (file != null) {
  9.             try {
  10.                  long resourceModified = file.lastModified();
  11.                  if (Log.isLoggable("InstantRun", 2)) {
  12.                       Log.v("InstantRun", "Resource patch last modified: " + resourceModified);
  13.                       Log.v("InstantRun", "APK last modified: " + apkModified
  14.                            + " "
  15.                            + (apkModified > resourceModified ? ">" : "<")
  16.                            + " resource patch");
  17.                  }
  18.                  if ((apkModified == 0L) || (resourceModified <= apkModified)) {
  19.                       if (Log.isLoggable("InstantRun", 2)) {
  20.                             Log.v("InstantRun", "Ignoring resource file, older than APK");
  21.                       }
  22.                       this.externalResourcePath = null;
  23.                  }
  24.           } catch (Throwable t) {
  25.                  Log.e("InstantRun", "Failed to check patch timestamps", t);
  26.           }
  27.      }

说明:该方法主要是判断资源resource.ap_是否改变,然后保存resource.ap_的路径到externalResourcePath中。

setupClassLoaders()

 
 
 
 
  1. private static void setupClassLoaders(Context context, String codeCacheDir, long apkModified) {
  2.        List dexList = FileManager.getDexList(context, apkModified);
  3.        Class server = Server.class;
  4.        Class patcher = MonkeyPatcher.class;
  5.        if (!dexList.isEmpty()) {
  6.             if (Log.isLoggable("InstantRun", 2)) {
  7.                  Log.v("InstantRun", "Bootstrapping class loader with dex list " + join('\n', dexList));
  8.             }
  9.             ClassLoader classLoader = BootstrapApplication.class.getClassLoader();
  10.             String nativeLibraryPath;
  11.             try {
  12.                   nativeLibraryPath = (String) classLoader.getClass().getMethod("getLdLibraryPath", new Class[0]).invoke(classLoader, new Object[0]);
  13.                   if (Log.isLoggable("InstantRun", 2)) {
  14.                        Log.v("InstantRun", "Native library path: " + nativeLibraryPath);
  15.                   }
  16.             } catch (Throwable t) {
  17.             Log.e("InstantRun", "Failed to determine native library path " + t.getMessage());
  18.             nativeLibraryPath = FileManager.getNativeLibraryFolder().getPath();
  19.       }
  20.       IncrementalClassLoader.inject(classLoader, nativeLibraryPath, codeCacheDir, dexList);
  21.       }

说明,该方法是初始化一个ClassLoaders并调用IncrementalClassLoader。

IncrementalClassLoader的源码如下:

  1. public class IncrementalClassLoader extends ClassLoader {
  2.       public static final boolean DEBUG_CLASS_LOADING = false;
  3.       private final DelegateClassLoader delegateClassLoader;
  4.       public IncrementalClassLoader(ClassLoader original, String nativeLibraryPath, String codeCacheDir, List dexes) {
  5.            super(original.getParent());
  6.            this.delegateClassLoader = createDelegateClassLoader(nativeLibraryPath, codeCacheDir, dexes, original);
  7.       }
  8. public Class findClass(String className) throws ClassNotFoundException {
  9.      try {
  10.           return this.delegateClassLoader.findClass(className);
  11.      } catch (ClassNotFoundException e) {
  12.           throw e;
  13.      }
  14. }
  15. private static class DelegateClassLoader extends BaseDexClassLoader {
  16.      private DelegateClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
  17.           super(dexPath, optimizedDirectory, libraryPath, parent);
  18.      }
  19.      public Class findClass(String name) throws ClassNotFoundException {
  20.           try {
  21.                 return super.findClass(name);
  22.           } catch (ClassNotFoundException e) {
  23.                 throw e;
  24.           }
  25.      }
  26. }
  27. private static DelegateClassLoader createDelegateClassLoader(String nativeLibraryPath, String codeCacheDir, List dexes,
  28. ClassLoader original) {
  29.       String pathBuilder = createDexPath(dexes);
  30.       return new DelegateClassLoader(pathBuilder, new File(codeCacheDir), nativeLibraryPath, original);
  31. }
  32. private static String createDexPath(List dexes) {
  33.       StringBuilder pathBuilder = new StringBuilder();
  34.       boolean first = true;
  35.       for (String dex : dexes) {
  36.            if (first) {
  37.                  first = false;
  38.            } else {
  39.                  pathBuilder.append(File.pathSeparator);
  40.            }
  41.            pathBuilder.append(dex);
  42.       }
  43.       if (Log.isLoggable("InstantRun", 2)) {
  44.            Log.v("InstantRun", "Incremental dex path is " + BootstrapApplication.join('\n', dexes));
  45.       }
  46.       return %

    网站标题:深入理解AndroidInstantRun运行机制
    地址分享:http://www.shufengxianlan.com/qtweb/news14/16714.html

    网站建设、网络推广公司-创新互联,是专注品牌与效果的网站制作,网络营销seo公司;服务项目有等

    广告

    声明:本网站发布的内容(图片、视频和文字)以用户投稿、用户转载内容为主,如果涉及侵权请尽快告知,我们将会在第一时间删除。文章观点不代表本网站立场,如需处理请联系客服。电话:028-86922220;邮箱:631063699@qq.com。内容未经允许不得转载,或转载时需注明来源: 创新互联