[TOC]

Android ClassLoader浅析
Android中的dex、apk、ClassLoader详解

  1. 程序在运行时把对应的类加载到内存中,在Android上来说就是把Dex文件中的类加载到内存。
  2. 双亲委派机制
public abstract class ClassLoader {    private ClassLoader(Void unused, ClassLoader parent) {        this.parent = parent;    }        protected ClassLoader(ClassLoader parent) {        this(checkCreateClassLoader(), parent);    }        protected ClassLoader() {        this(checkCreateClassLoader(), getSystemClassLoader());    }        public static ClassLoader getSystemClassLoader() {        return SystemClassLoader.loader;    }        static private class SystemClassLoader {        public static ClassLoader loader = ClassLoader.createSystemClassLoader();    }        private static ClassLoader createSystemClassLoader() {        String classPath = System.getProperty("java.class.path", ".");        String librarySearchPath = System.getProperty("java.library.path", "");        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());    }        public Class<?> loadClass(String name) throws ClassNotFoundException {        return loadClass(name, false);    }        protected Class<?> loadClass(String name, boolean resolve)        throws ClassNotFoundException    {            // First, check if the class has already been loaded            Class<?> c = findLoadedClass(name);            if (c == null) {                try {                    if (parent != null) {                        c = parent.loadClass(name, false);                    } else {                        c = findBootstrapClassOrNull(name);                    }                } catch (ClassNotFoundException e) {                    // ClassNotFoundException thrown if class not found                    // from the non-null parent class loader                }                if (c == null) {                    // If still not found, then invoke findClass in order                    // to find the class.                    c = findClass(name);                }            }            return c;    }        protected Class<?> findClass(String name) throws ClassNotFoundException {        throw new ClassNotFoundException(name);    }}

1. ClassLoader 继承关系

继承关系
[图片上传失败...(image-d446ce-1549854936566)]

  • PathClassLoader只能加载已经安装到Android系统中的apk文件(/data/app目录),是Android默认使用的类加载器
  • DexClassLoader可以加载任意目录下的dex/jar/apk/zip文件,比PathClassLoader更灵活,是实现热修复的重点

源码查看(基于API28 9.0)
在8.0以上,optimizedDirectory参数传的都是null,也就是说PathClassLoaderDexClassLoader没差别,DexClassLoader可以加载未安装的apk,PathClassLoader也可以。

/** * Provides a simple {@link ClassLoader} implementation that operates on a list * of files and directories in the local file system, but does not attempt to * load classes from the network. Android uses this class for its system class * loader and for its application class loader(s). *///public class PathClassLoader extends BaseDexClassLoader {        public PathClassLoader(String dexPath, ClassLoader parent) {        super(dexPath, null, null, parent);    }    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent){        super(dexPath, null, librarySearchPath, parent);   }}
/** * A class loader that loads classes from {@code .jar} and {@code .apk} files * containing a {@code classes.dex} entry. This can be used to execute code not * installed as part of an application.*///public class DexClassLoader extends BaseDexClassLoader {    public DexClassLoader(String dexPath, String optimizedDirectory,                          String librarySearchPath, ClassLoader parent) {        super(dexPath, null, librarySearchPath, parent);    }}

具体实现还得看BaseDexClassLoader的构造方法。

public class BaseDexClassLoader extends ClassLoader {    private final DexPathList pathList;   public BaseDexClassLoader(String dexPath, File optimizedDirectory,                          String librarySearchPath, ClassLoader parent) {        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);    }    // 参数dexPath:待加载的apk/dex/jar文件路径;    // 参数optimizedDirectory:dex的输出路径,将apk/dex/jar解压出dex文件,复制到指定路径,用于dalvik运行    // 参数librarySearchPath:加载时候需要用到的lib库,这个一般不用,可以传入Null    // 参数parent:指定父加载器    public BaseDexClassLoader(String dexPath, File optimizedDirectory,                              String librarySearchPath, ClassLoader parent, boolean isTrusted) {        super(parent);        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);        ...    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        ...        Class c = pathList.findClass(name, suppressedExceptions);        ...        return c;    }    @Override    protected URL findResource(String name) {        return pathList.findResource(name);    }        @Override    public String findLibrary(String name) {        return pathList.findLibrary(name);    }}

构造方法中创建了一个DexPathList对象,而BaseDexClassLoader中的各个findxxx()调用的是DexPathList对象.findxxx()

DexPathList的构造方法和findxxx()方法。

final class DexPathList {        private Element[] dexElements;        DexPathList(ClassLoader definingContext, String dexPath,            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {        ...        // 加载dexPath路径下的dex和resource        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions, definingContext, isTrusted);        ...    }        private static Element[] makeDexElements(List files, File optimizedDirectory,        List suppressedExceptions, ClassLoader loader, boolean isTrusted) {           Element[] elements = new Element[files.size()];      int elementsPos = 0;      for (File file : files) {          if (file.isDirectory()) {              elements[elementsPos++] = new Element(file);          } else if (file.isFile()) {              String name = file.getName();              DexFile dex = null;              if (name.endsWith(DEX_SUFFIX)) {                  // Raw dex file (not inside a zip/jar).                  try {                      dex = loadDexFile(file, optimizedDirectory, loader, elements);                      if (dex != null) {                          elements[elementsPos++] = new Element(dex, null);                      }                  } catch (IOException suppressed) {                      System.logE("Unable to load dex file: " + file, suppressed);                      suppressedExceptions.add(suppressed);                  }              } else {                  try {                      dex = loadDexFile(file, optimizedDirectory, loader, elements);                  } catch (IOException suppressed) {                      suppressedExceptions.add(suppressed);                  }                  if (dex == null) {                      elements[elementsPos++] = new Element(file);                  } else {                      elements[elementsPos++] = new Element(dex, file);                  }              }          } else {              System.logW("ClassLoader referenced unknown path: " + file);          }      }      return elements;    }}

挨个文件中寻找Class对象

 public Class<?> findClass(String name, List suppressed) {        for (Element element : dexElements) {            Class<?> clazz = element.findClass(name, definingContext, suppressed);            if (clazz != null) {                return clazz;            }        }        if (dexElementsSuppressedExceptions != null) {            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));        }        return null;    }

2. 热更新实现原理

源码

如果是.dex后缀的文件,会直接放到dexElements数组中,否则就从这个文件中寻找dex文件,找到后放到dexElements数组。
ClassLoader#loadClass就是遍历dexElements数组,从dex文件中找到要找的class文件。

那我们是不是在这个数组前安插一个自己的数组,是不是就可以了?

做法:
拿到我当前应用的dexElements数组,然后拿到已修复的dex或apk文件的dexElements数组,把已修复的数组放到当前应用的数组前面。

public class HotfixApplication extends Application {    @Override    protected void attachBaseContext(Context base) {        super.attachBaseContext(base);        File apk = new File(getCacheDir() + "/hotfix.dex");        if (apk.exists()) {            try {            //1. 获取当前应用的dexElements数组                ClassLoader classLoader = getClassLoader();                Class loaderClass = BaseDexClassLoader.class;                Field pathListField = loaderClass.getDeclaredField("pathList");                pathListField.setAccessible(true);                Object pathListObject = pathListField.get(classLoader);                Class pathListClass = pathListObject.getClass();                Field dexElementsField = pathListClass.getDeclaredField("dexElements");                dexElementsField.setAccessible(true);                Object dexElementsObject = dexElementsField.get(pathListObject);            //2. 获取已修复dex或apk的dexElements数组                PathClassLoader newClassLoader = new PathClassLoader(apk.getPath(), null);                Object newPathListObject = pathListField.get(newClassLoader);                Object newDexElementsObject = dexElementsField.get(newPathListObject);            //3. 数组合并,把修复的数组放在自己的数组前面                int oldLength = Array.getLength(dexElementsObject);                int newLength = Array.getLength(newDexElementsObject);                Object concatDexElementsObject = Array.newInstance(dexElementsObject.getClass().getComponentType(), oldLength + newLength);                for (int i = 0; i < newLength; i++) {                    Array.set(concatDexElementsObject, i, Array.get(newDexElementsObject, i));                }                for (int i = 0; i < oldLength; i++) {                    Array.set(concatDexElementsObject, newLength + i, Array.get(dexElementsObject, i));                }                dexElementsField.set(pathListObject, concatDexElementsObject);            } catch (NoSuchFieldException e) {                e.printStackTrace();            } catch (IllegalAccessException e) {                e.printStackTrace();            }        }    }}

3. 插件化原理

利用ClassLoader在新的apk中loadClass,利用反射获取新的apk中的class

3.1 问题

Q: 怎么启动插件apk中的Activity?
A: 由于启动Activity系统会校验清单文件,如果没有该Activity会异常。我们可以创建一个代理的Activity,启动这个代理Activity,然后在代理Activity中执行真实对象的方法(这个真实对象需要有和Activity一样的方法)。

Q: 怎样获取插件apk中的Resource文件
A: 我们要知道,Resource也是利用AssetManager获取的,所以我们要自定义一个AssetManager,安放到Resource对象中。

public class ProxyActivity extends Activity {    Object realActivity;//    realActivity = ???;    @Override    protected void onCreate(@Nullable Bundle savedInstanceState) {        super.onCreate(savedInstanceState);//        realActivity.onCreate(savedInstanceState);    }    @Override    public AssetManager getAssets() {        try {            Class assetManagerClass = AssetManager.class;            AssetManager assetManager = (AssetManager) assetManagerClass.newInstance();            Method addAssetPath = assetManagerClass.getDeclaredMethod("adAssetPath", String.class);            addAssetPath.invoke(assetManager, "apkPath");            return assetManager;        } catch (IllegalAccessException e) {            e.printStackTrace();        } catch (InstantiationException e) {            e.printStackTrace();        } catch (NoSuchMethodException e) {            e.printStackTrace();        } catch (InvocationTargetException e) {            e.printStackTrace();        }        return super.getAssets();    }    @Override    public Resources getResources() {        return new Resources(getAssets(), getResources().getDisplayMetrics(), getResources().getConfiguration());    }}

更多相关文章

  1. 一款常用的 Squid 日志分析工具
  2. GitHub 标星 8K+!一款开源替代 ls 的工具你值得拥有!
  3. RHEL 6 下 DHCP+TFTP+FTP+PXE+Kickstart 实现无人值守安装
  4. Linux 环境下实战 Rsync 备份工具及配置 rsync+inotify 实时同步
  5. android 向服务器上传
  6. GLES2.0 on Android(安卓)emulator
  7. android studio 导入 融云问题之一 兼容4.0一下版本
  8. android Java代码设置textview的字体资源颜色
  9. Android安全风险检测项

随机推荐

  1. 通过JavaScript或PHP检测Android设备
  2. 界面编程之基本界面组件(7)ImageView(图像视
  3. Android(安卓)jni 常用方法备忘
  4. Android编译过程详解
  5. Android百度地图开发(一)环境搭建
  6. 1.1 创建android工程
  7. Android(安卓)View系列 - 坐标系
  8. ANDROID的MANIFEST.XML文件字段解析
  9. 视频专辑: 善知堂android 4.0.3 就业视频
  10. 知识储备:Android系统架构