0%

Android插件化-开篇

Android插件化-开篇

Android插件化从技术上来说就是如何启动未安装的apk(主要是四大组件)里面的类,主要问题涉及如何加载类、如何加载资源、如何管理组件生命周期。

1.类加载 (四大组件)
2.资源的加载
3.如何管理组件生命周期

资源的加载

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
private AssetManager createAssetManager(String apkPath) {
AssetManager am = null;
try {
//反射构造 AssetManager
am = AssetManager.class.newInstance();
Method method = AssetManager.class.getDeclaredMethod("addAssetPath",String.class);
//通过反射 将 apk 的目录添加到 AssetManager 的资源路径下
method.invoke(am,apkPath);
return am;

} catch (InstantiationException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
}
return am;
}

Resouces的访问

1
2
3
4
5
6
7
8
9
/**
* 通过 AssetManager 和设备配置来构造 Resouces
* @param assetManager
* @return
*/
private Resources createResource(AssetManager assetManager) {
Resources resources = mContext.getResources();
return new Resources(assetManager,resources.getDisplayMetrics(),resources.getConfiguration());
}

如何管理组件生命周期

ClassLoader-类加载

Java - ClassLoader

  • Java中默认有三种ClassLoader。分别是:
    • BootStrap ClassLoader:启动类加载器,最顶层的加载器。主要负责加载JDK中的核心类。在JVM启动后也随着启动,并构造Ext ClassLoader和App ClassLoader。
    • Extension ClassLoader:扩展类加载器,负责加载Java的扩展类库。
    • App ClassLoader:系统类加载器,负责加载应用程序的所有jar和class文件。
    • 自定义ClassLoader:需要继承自ClassLoader类。

ClassLoader职能

ClassLoader默认使用双亲委托模型来搜索类。
每个ClassLoader都有一个父类的引用。当ClassLoader需要加载某个类时,先判断是否加载过,如果加载过就返回Class对象。否则交给他的父类去加载,继续判断是否加载过。这样 层层判断,就到了最顶层的BootStrap ClassLoader来试图加载。如果连最顶层的Bootstrap ClassLoader都没加载过,那就加载。如果加载失败,就转交给子ClassLoader,层层加载,直到最底层。如果还不能加载的话那就只能抛出异常了。

  • 通过这种双亲委托模型,好处是:
    • 更高效,父类加载一次就可以避免了子类多次重复加载
    • 更安全,避免了外界伪造java核心类。

Android - ClassLoader

android从5.0开始使用art虚拟机,这种虚拟机在程序运行时也需要ClassLoader将类加载到内存中,但是与java不同的是,java虚拟机通过读取class字节码来加载,但是art则是通过dex字节码来加载。
这是一种优化,可以合并多个class文件为一个classes.dex文件。

  • android一共有三种类加载器:
    • BootClassLoader:父类构造器
    • PathClassLoader:一般是加载指定路径/data/app中的apk,也就是安装到手机中的apk。所以一般作为默认的加载器。
    • DexClassLoader:从包含classes.dex的jar或者apk中,加载类的加载器,可用于动态加载。
      看PathClassLoader和DexClassLoader源码,都是继承自BaseDexClassLoader。
      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      14
      15
      16
      17
      18
      public DexClassLoader(String dexPath, String optimizedDirectory,
      String librarySearchPath, ClassLoader parent) {
      super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
      }
      public class PathClassLoader extends BaseDexClassLoader {
      public PathClassLoader(String dexPath, ClassLoader parent) {
      super(dexPath, null, null, parent); //见下文
      }
      }
      public class BaseDexClassLoader extends ClassLoader {
      private final DexPathList pathList;
      public BaseDexClassLoader(String dexPath, File optimizedDirectory,
      String libraryPath, ClassLoader parent) {
      super(parent); //见下文
      //收集dex文件和Native动态库【见小节3.2】
      this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
      }
      }
      我们可以看到PathClassLoader的两个参数都为null,表明只能接受固定的dex文件,而这个文件是只能在安装后出现的。而DexClassLoader中optimizedDirectory,和librarySearchPath都是可以自己定义的,说明我们可以传入一个jar或者apk包,保证解压缩后是一个dex文件就可以操作了。因此,我们通常使用DexClassLoader来进行插件化和热修复。

ClassLoader

可以看到,BaseDexClassLoader有一个相当重要的过程就是初始化DexPathList。初始化DexPathList的过程主要是收集dexElements和nativeLibraryPathElements。一个Classloader可以包含多个dex文件,每个dex文件被封装到一个Element对象。这element对象在初始化和热修复逻辑中是相当重要的。当查找某个类时,会遍历dexElements,如果找到就返回,否则继续遍历。所以当多个dex中有相同的类,只会加载前面的dex中的类。下面是这段逻辑的具体实现

1
2
3
4
5
6
7
8
9
10
11
12
13
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) {
return clazz;
}
}
}
return null;
}

总结
我们先是讲解了Java中类加载的双亲委托机制,然后介绍了Android中的几种ClassLoader,从源码角度介绍了两种ClassLoader加载机制的不同。以后的插件化实践中,我们会经常用到DexClassLoader。