《Android---热修复简单探讨(一)》------dex类加载方式

Android Class加载机制

使用该种方式实现就是要了解Android class的加载机制。然后利用反射进行hook。
在这里插入图片描述
通过类关系可以看出主要有,PathClassLoaderDexClassLoader两个loader。

  • PathClassLoader:主要用于加载/data/app/中的apk
  • DexClassLoader:可以加载指定路径的apk,dex,jar
    从源码可以看出主要功能实现还是在BaseDexClassLoader中

BaseDexClassLoader中查找class方法

private final DexPathList pathList;
 ...
@Override
 protected Class<?> findClass(String name) throws ClassNotFoundException {
     List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
     // 该方法又调用了DexPathList中的方法
     Class c = pathList.findClass(name, suppressedExceptions);
     if (c == null) {
         ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
         for (Throwable t : suppressedExceptions) {
             cnfe.addSuppressed(t);
         }
         throw cnfe;
     }
     return c;
 }

DexPathList中查找class的代码

private Element[] dexElements;
...
public Class findClass(String name, List<Throwable> suppressed) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
            if (clazz != null) {
            	// 如果找到目标class就不会执行后面的把我们要修改的class放到前面即可完成替换
                return clazz;
            }
        }
    }
    if (dexElementsSuppressedExceptions != null) {
        suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
    }
    return null;
}

Element则主要保存dex有关的信息,可以看看系统给我们的对象里面都有哪些信息,对照着创建一个就好了。

实验查看数据

 try {
   	ClassLoader classLoader = getClassLoader();
    L.i(classLoader.getClass().getSimpleName());

    Object pathList = ReflexUtils.getProperty(classLoader, "pathList");
    L.i(pathList.toString());
} catch (Exception e) {
    e.printStackTrace();
}
04-09 18:34:13.900 I/[TS-FH]: [ onCreate ]PathClassLoader(MainActivity.java:18)
04-09 18:34:13.901 I/[TS-FH]: [ onCreate ]DexPathList[[zip file "/data/app/com.wxfjava.struggle-1/base.apk"],nativeLibraryDirectories=[/vendor/lib, /system/lib]](MainActivity.java:22)

打修复包

# --output=f.dex输出文件名称 源class,就目录下所有class文件打包
dx --dex --output=f.dex **/*.class

Android中目录说明

  • /system/app/系统自带应用存储位置
  • /data/app/用户应用目录
  • /data/data/应用数据存储目录
  • /data/dalvik-cache/dex文件存储位置

应用安装:将apk复制到/data/app/下,dex文件放到/data/dalvik-cache/目录,并在数据目录创建对应的数据信息。

代码实现

try {
      // 将修复dex复制到工作目录下
      File srcFile = new File(FIX_DEX_FILE);
      File dexFile = new File(dexPath.getAbsolutePath(), srcFile.getName());
      FileUtils.copyFile(srcFile, dexFile);

      ClassLoader pathClassLoader = mContext.getClassLoader();
      Object pathList = ReflexUtils.getProperty(pathClassLoader, "pathList");
      Object dexElements = ReflexUtils.getProperty(pathList, "dexElements");
      int length = Array.getLength(dexElements);

      DexClassLoader dexClassLoader = new DexClassLoader(dexFile.getAbsolutePath(), foptPath, null, pathClassLoader);
      Object fixPathList = ReflexUtils.getProperty(dexClassLoader, "pathList");
      Object fixDexElements = ReflexUtils.getProperty(fixPathList, "dexElements");
      int fixLength = Array.getLength(fixDexElements);

      Object newDexElements = Array.newInstance(Array.get(dexElements, 0).getClass(), length + fixLength);
      System.arraycopy(fixDexElements, 0, newDexElements, 0, fixLength);
      L.i("l:" + length + ",fixl:" + fixLength);
      System.arraycopy(dexElements, 0, newDexElements, fixLength, length);

      boolean result = ReflexUtils.setProperty(pathList, "dexElements", newDexElements);
      L.i(result + "-" + pathList.toString());
} catch (Exception e) {
      L.e(e);
}

后记

这种方式只能在有问题的class加载之前进行替换修复才会有效。如果已经加载了有问题的class,进行替换是无效的,所以修复的代码要放到Application中进行执行。
要实现实时修复,就要用到Andfix的实现方式了。直接修改问题方法的内存地址指向完成实时修复。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章