Android插件化探索(一)类加载器DexClassLoader

本文部分内容参考自《Android内核剖析》

基本概念

在Java环境中,有个概念叫做“类加载器”(ClassLoader),其作用是动态装载Class文件。标准的Java SDK中有一个ClassLoader类,借助它可以装载想要的Class文件,每个ClassLoader对象在初始化时必须指定Class文件的路径

没有使用过ClassLoader的读者可能会问:“在过去的程序开发中,当我们需要某个类时,只需使用import关键字包含该类就可以了,为什么还要类加载器呢?”简单的讲,import中所引用的类文件有两个特点:

  • 必须存在于本地,当程序运行时需要该类时,内部类装载器会自动装载该类,这对程序员来讲是透明的,即程序员感知不到这一过程。
  • 编译时必须在现场,否则编译不过会因为找不到引用文件而正常编译。

但在有些情况下,所需的类却不能满足以上两个条件。比如当该类时从远程下载并在本地执行时,典型的例子就是通过浏览器中的AppleLet执行的Java程序,这些要执行的程序是在服务器端。另一种情况是,要引用的Class文件不方便在编译时直接参与,而只能运行时动态调用。举例来讲,在Android Framework中,所包含的Class文件是一些通用的类文件,但对于一些设备商而言,他们需要扩充Framework,扩充的具体工作包括两点:

  • 需要增加一些额外的类文件,这些类文件提供厂商自定义的功能,这些文件一般以独立的Jar包存在。
  • 需要修改Framework中的已有类文件,比如WindowManagerServcie类,在该类中添加使用自定义Jar包中的代码。使用自定义Jar包的常用方法是使用import关键字包含的自定义的类,但为了保持和原生Framework的兼容性、对于原生Framework最少化修改,可以使类装载器动态装载自定义Jar包。

这就是使用ClassLoader的原因。

在一般情况下,应用程序不需要创建一个全新的ClassLoader对象,而是使用当前环境已经存在的ClassLoader。因为Javad的Runtime环境在初始化时,其内部会创建一个ClassLoader对象用于加载Runtime所需的各种Java类。

每个ClassLoader必须有一个父ClassLoader,在装载Class文件时,子ClassLoader会先请求父ClassLoader加载该Class文件,只有当其父ClassLoader找不到该Class文件时,子ClassLoader才会继续装载该类,这是一种安全机制。关系ClassLoader的内部过程,大家可以参考《Inside the Java Virtual Machine》一书,作者为Bill Venners,相关链接如下:

http://www.artima.com/insidejvm/ed2/index.html

对于Android的应用程序,本质上虽然也是用Java开发,并且使用标准的Java编译器编译出Class文件,但最终的APK文件中包含的却是dex类型的文件。dex文件是将所需的所有Class文件重新打包,打包的规则不是简单的压缩,而是完全对Class文件内部的各种函数表、变量表等进行优化,并产生一个新的文件,这就是dex文件。由于dex文件是一种经过优化的Class文件,因此要加载这样特殊的Class文件就需要特殊的类装载器,这就是DexClassLoader,Android SDK中提供的DexClassLoader类就是出于这个目的。

初始API

//DexClassLoader的构造方法
DexClassLoader (String dexPath, 
                String optimizedDirectory, 
                String libraryPath, 
                ClassLoader parent)
  • dexPath: 指目标类所在的jar/apk文件路径, 多个路径使用 File.pathSeparator分隔, Android里面默认为 “:”
  • optimizedDirectory: 解压出的dex文件的存放路径,以免被注入攻击,不可存放在外置存储。
    下面来看DexClassLoader的使用方法。
  • libraryPath :目标类中的C/C++库存放路径。
  • parent: 父类装载器

使用方法

DexClassLoader的使用方法一般有两种:
1. 从已安装的apk中读取dex
2. 从apk文件中读取dex

假如有两个APK,一个是宿主APK,叫作HOST,一个是插件APK,叫作Plugin。Plugin中有一个类叫PluginClass,代码如下:

public class PluginClass {
   
    public PluginClass() {
        Log.d("JG","初始化PluginClass");
    }

    public int function(int a, int b){
        return a+b;  
    }  
}  

现在如果想调用插件APK中PluginClass内的方法,应该怎么办?

从已安装的apk中读取dex

先来看第一种方法,这种方法必须建一个Activity,在清单文件中配置Action.

    <activity android:name=".MainActivity">
            <intent-filter>
                <action android:name="com.maplejaw.plugin"/>
            </intent-filter>
   </activity>

然后在宿主APK中如下使用

  /**
     * 这种方式用于从已安装的apk中读取,必须要有一个activity,且需要配置ACTION
     */
  private void useDexClassLoader(){
        //创建一个意图,用来找到指定的apk
        Intent intent = new Intent("com.maplejaw.plugin");
        //获得包管理器
        PackageManager pm = getPackageManager();
        List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);
        if(resolveinfoes.size()==0){
            return;
        }
        //获得指定的activity的信息
        ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;

        //获得包名
        String packageName = actInfo.packageName;
        //获得apk的目录或者jar的目录
        String apkPath = actInfo.applicationInfo.sourceDir;
        //dex解压后的目录,注意,这个用宿主程序的目录,android中只允许程序读取写自己
        //目录下的文件
        String dexOutputDir = getApplicationInfo().dataDir;

        //native代码的目录
  • 6
    点赞
  • 14
    收藏
    觉得还不错? 一键收藏
  • 5
    评论
评论 5
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值