插件化知识梳理(7) 类的动态加载入门

1、前言

插件化知识梳理(6) - Small 源码分析之 Hook 原理 这一章的学习完成以后,下一步咱们将进入插件化加载的精髓,动态加载类的学习,在此以前,咱们须要先准备一些关于类加载的知识。java

Android当中,支持动态加载的两种方式为:DexClassLoaderPathClassLoader。这二者之间的区别为:bash

  • DexClassLoader
  • 能够加载jar、apk、dex
  • 支持从SD卡目录加载。
  • PathClassLoader
  • 在许多文章中都有提到,在Dalvik虚拟机上,只能加载已经安装到系统中的Apk文件,也就是/data/app目录下的apk文件。之因此有这个限制是由于PathClassLoader会去读取data/dalvik-cache目录下通过优化后的dex文件,若是文件不存在,那么就会报错。因为手边没有机器,因此没有版本验证。
  • 而在ART虚拟机上,经过源码当中的注释,能够发现是支持的。

2、具体实例

实例的工程目录结构为: app

  • app:宿主模块
  • library:插件模块
  • libraryinterface:插件接口模块

其中,applibrary模块分别依赖于libraryinterfacelibrarylibraryinterfaceAndroid Library类型的Module,下面,咱们开始讲解整个工程的构建过程。ide

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 {

    @Override
    public int getVersion() {
        return 1;
    }
}
复制代码

接下来须要作的就是将该插件模块打包成一个jar文件,一样是在build.gradle文件中,建立一个Task任务:gradle

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文件,咱们经过解压能够看出: ui

那么咱们就须要经过 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文件:

最后,将该 jarpush到手机中的 /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;

    @Override
    protected 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 知识梳理系列:

相关文章
相关标签/搜索