一、前言
在 插件化知识梳理(6) - Small 源码分析之 Hook 原理 这一章的学习完成之后,下一步我们将进入插件化加载的精髓,动态加载类的学习,在此之前,我们需要先准备一些关于类加载的知识。
Android
当中,支持动态加载的两种方式为:DexClassLoader
和PathClassLoader
。这两者之间的区别为:
DexClassLoader
- 可以加载
jar、apk、dex
- 支持从
SD
卡目录加载。 PathClassLoader
- 在许多文章中都有提到,在
Dalvik
虚拟机上,只能加载已经安装到系统中的Apk
文件,也就是/data/app
目录下的apk
文件。之所以有这个限制是因为PathClassLoader
会去读取data/dalvik-cache
目录下经过优化后的dex
文件,如果文件不存在,那么就会报错。由于手边没有机器,所以没有版本验证。 - 而在
ART
虚拟机上,通过源码当中的注释,可以发现是支持的。
二、具体实例
实例的工程目录结构为:
app
:宿主模块library
:插件模块libraryinterface
:插件接口模块
其中,app
和library
模块分别依赖于libraryinterface
,library
和libraryinterface
为Android Library
类型的Module
,下面,我们开始讲解整个工程的构建过程。
2.1 接口模块 libraryinterface
接口模块相当于是宿主模块和插件模块所定义的一套标准,宿主模块遵循固定的业务逻辑,而具体的实现则根据插件模块的不同而不同。 在接口模块中,我们定义一个简单的接口IPlugin.java
:
public interface IPlugin {public int getVersion();
}
复制代码
2.2 插件模块 library
首先,我们在插件模块的build.gradle
文件中,引入libraryinterface
模块
dependencies {//引入接口模块compile project (':libraryinterface')
}
复制代码
接着,我们编写一个实现类:
public class PluginImpl implements IPlugin {@Overridepublic int getVersion() {return 1;}
}
复制代码
接下来需要做的就是将该插件模块打包成一个jar
文件,同样是在build.gradle
文件中,创建一个Task
任务:
task makeJar(type: Copy) {delete 'build/libs/plugin.jar'from ('build/intermediates/bundles/release/')into ('../file/')include ('classes.jar')rename ('classes.jar','plugin.jar')
}makeJar.dependsOn(build)
复制代码
首先点击make module
:
./gradlew makeJar
复制代码
就会得到一个plugin.jar
文件,但是这个jar
文件是不能够被动态加载的,因为它内部其实是.class
文件,我们通过解压可以看出:
Android SDK
自带的dx
工具进行转换,把它转换为.dex
,转换后的文件为plugin_dex.jar
/Users/lizejun/Library/Android/sdk/build-tools/25.0.3/dx --dex --output=file/plugin_dex.jar file/plugin.jar
复制代码
将plugin_dex.jar
解压之后,可以看到它已经被转换成了.dex
文件:
jar
包push
到手机中的/sdcard/Plugin
目录下:2.3 宿主模块 app
首先,宿主模块同样需要依赖于接口模块libraryinterface
:
dependencies {//引入接口模块compile project (':libraryinterface')
}
复制代码
在代码当中,我们通过DexClassLoader/PathClassLoader
动态外部的插件plugin_dex.jar
,通过反射实例化PluginImpl
类,并调用它的getVersion()
方法进行验证:
public class MainActivity extends AppCompatActivity {private TextView mTextView;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);setContentView(R.layout.activity_main);mTextView = (TextView) findViewById(R.id.tv_plug_result);getPluginA();}private void getPluginA() {File dexOutputDir = getDir("dex1", 0);String dexPath = Environment.getExternalStorageDirectory().toString() + "/Plugin/plugin_dex.jar";DexClassLoader loader = new DexClassLoader(dexPath, dexOutputDir.getAbsolutePath(), null, getClassLoader());try {Class clz = loader.loadClass("com.demo.lizejun.library.PluginImpl");IPlugin impl = (IPlugin) clz.newInstance();int version = impl.getVersion();mTextView.setText("Version=" + version);} catch (Exception e) {e.printStackTrace();}}private void getPluginB() {String dexPath = Environment.getExternalStorageDirectory().toString() + "/Plugin/plugin_dex.jar";PathClassLoader loader = new PathClassLoader(dexPath, getClassLoader());try {Class clz = loader.loadClass("com.demo.lizejun.library.PluginImpl");IPlugin impl = (IPlugin) clz.newInstance();int version = impl.getVersion();mTextView.setText("Version=" + version);} catch (Exception e) {e.printStackTrace();}}}
复制代码
最终的结果为:
更多文章,欢迎访问我的 Android 知识梳理系列:
- Android 知识梳理目录:www.jianshu.com/p/fd82d1899…
- 个人主页:lizejun.cn
- 个人知识总结目录:lizejun.cn/categories/