DexClassLoader 实现 Android 插件加载
Java 中的 ClassLoader:
Java 中 ClassLoader
用于动态加载 Class
到 JVM,包含 BootstrapClassLoader
(C++ 编写,用于加载系统核心类)、 ExtClassLoader
(用于加载 lib/ext/ 目录的扩展 API)、 AppClassLoader
(加载 CLASSPATH
目录下的类)。
双亲委托机制:
-
任何自定义 ClassLoader 都必须继承
ClassLoader
抽象类,并指定其 parent 加载器,默认为BootstrapClassLoader
; -
任何自定义 ClassLoader 在加载一个类之前都会先委托其 parent 去加载,只有 parent 加载失败才会自己加载;
-
这样既可以防止重复加载,又可以排除安全隐患(防止用户替换系统核心类);
-
所以一般只需要重写
findClass()
方法即可(在 parent 加载失败时调用);
-
-
双亲委托机制是在
loadClass()
方法实现的,要想避开(自己验证安全性,比如 Tomcat 的WebAppClassLoader
),必须重写loadClass()
方法;
自定义 ClassLoader 用途:
-
在执行非置信代码前先做签名认证等;
-
从网络、数据库等动态加载类;
类的卸载:
-
只有当类的实例被回收,才会被 unload,但被
BootstrapClassLoader
加载的系统类除外; -
重复加载类会报异常,只能重新定义新的 ClassLoader 再次加载;
Dalvik 的 ClassLoader:
-
Android 里
ClassLoader
的defineClass()
方法直接抛出UnsupportedOperationException
异常,必须借助DexClassLoader
和PathClassLoader
; -
DexClassLoader
和PathClassLoader
都遵循双亲委托机制,因为只重写了findClass()
方法,没有重写loadClass()
方法; -
Dalvik 虚拟机识别的是
DexFile
而不是JarFile
;且DexFile.loadClass()
方法必须通过类加载器调用,否则无效;
利用 DexClassLoader 实现 Android 插件加载:
比如我们在主应用 HostApp 中需要调用 视频插件 VideoPlayerPlugin 中的 playVideo() 方法。
给插件加入 Intent 标识:
HostApp 要查询插件信息,只能通过 PackageManager
。这里我首先想到的是直接通过其 getPackageInfo()
方法。但是试想,可能插件有很多个,而且包名不同。所以最好还是通过在插件中定义空的 Activity
并加入 Intent
标识,然后调用 queryIntentActivities()
方法去查询插件信息:
1 | <activity android:name=".plugin"> |
查询插件信息:
首先使用 PackageMananer
查询到插件的 packageName
和 ApplicationInfo
:
1 | Intent intent = new Intent("com.rincliu.videoplayerplugin"); |
上面是直接读取的第一条信息( plugins
要先判空),如果有很多种插件,或者有好几个版本,这样就需要继续读取插件的版本号等配置信息作进一步区分:
1 | Resources res = pm.getResourcesForApplication(packageName); |
使用 DexClassLoader 调用插件:
创建 DexClassLoader
对象:
1 | String dexSourceDir = app.sourceDir; |
使用反射调用插件中的方法:
1 | try { |