×

[Android 学习笔记] instant-run 源码学习 ( 3 )

96
afluy
2017.07.29 12:10* 字数 311

handleHotSwapPatch 方法的具体实现:

  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.context.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 数据保存到文件, 根据 patch
保存路径创建一个DexClassLoader 对象, 这样就可以通过该 DexClassLoader 对象获取 patch 中的类信息并实例化相应对象, 首先实例化 patch 中的 AppPatchesLoaderImpl 对象, 前面笔记中可以看到 AppPatchesLoaderImpl 是继承 AbstractPatchesLoaderImpl, AppPatchesLoaderImpl 只是复写了 getPatchedClasses 方法, 在 Demo 项目中返回 "org.demo.example.MainActivity$1", 然后调用 AppPatchesLoaderImpl 的 load 方法, 具体实现是在父类 AbstractPatchesLoaderImpl 中:

  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;
  }

首先创建具体补丁 MainActivity$override 对象, 并将该对象复制给 MainActivity 类的 $change 变量, 到此根据之前的分析, 在 MainActivity 对象中调用方法都会走到 MainActivity$override 对象的对应方法, 修改代码后不需要重新安装 APP 就能生效.

替换资源文件

上面说的都是源码补丁, 如果修改了图片或布局, 就通过创建 一个新的AssetManager 对象, 并调用其 addAssetPath 方法加载变更的资源数据, 并替换已经创建的 Activity 对象中的 resource.mAssets 变量为新的 AssetManager 对象, 然后重启 Activity 就可以生效了, 具体实现源码在 MonkeyPatcher 类中, 有兴趣的童鞋可以了解一下

Android学习笔记
Web note ad 1