Android应用程序插件化研究之DexClassLoader

文章首发:Android应用程序插件化研究之DexClassLoader|大利猫

最近在研究Android应用的插件化开发,看了好几个相关的开源项目。插件化都是在解决以下几个问题:

  • 如何把插件apk中的代码和资源加载到当前虚拟机。
  • 如何把插件apk中的四大组件注册到进程中。
  • 如何防止插件apk中的资源和宿主apk中的资源引用冲突。

就这几个问题,我开始研究插件化开发实现的相关技术,本篇文章主要讲第一点:如何加载另一个apk中的class。我们要把一个包含class文件的jar加载到java虚拟机中,需要使用ClassLoader这个类。Android的编译系统中对class文件进行了进一步的处理:最后变成 .dex文件,.dex文件包含在apk中,google提供了一个类来加载.dex文件,这个类就是DexClassLoader,它继承自ClassLoader。本篇的重点是写一个例子来说明DexClassLoader的用法。先来熟悉下DexClassLoader。

DexClassLoader介绍

DexClassLoader是一个类加载器,可以用来从.jar和.apk文件中加载class。可以用来加载执行没用和应用程序一起安装的那部分代码。构造函数:DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)
dexPath:被解压的apk路径,不能为空。
optimizedDirectory:解压后的.dex文件的存储路径,不能为空。这个路径强烈建议使用应用程序的私有路径,不要放到sdcard上,否则代码容易被注入攻击。
libraryPath:os库的存放路径,可以为空,若有os库,必须填写。
parent:父亲加载器,一般为context.getClassLoader(),使用当前上下文的类加载器。

创建一个插件apk工程module:apkbeloaded

我把插件的包名命名为:com.dexclassdemo.liuguangli.apkbeloaded,我们创建两个类:Registry和ClassToBeImportedRegistry:

package com.dexclassdemo.liuguangli.apkbeloaded;
import java.util.ArrayList;
/**
 * Created by liuguangli on 16/2/13.
*/
public class Registry {
    public static ArrayList<Class<?>> _classes = new     ArrayList<Class<?>>();
    static{
        _classes.add(ClassToBeImported.class);
        //more classes here
    }
}

这个类中个只有一个成员变量_classes,集合引用ClassToBeImported.class。
ClassToBeImported:

package com.dexclassdemo.liuguangli.apkbeloaded;
import android.util.Log;
/**
 *Created by liuguangli on 16/2/13. 
*/
public class ClassToBeImported { 
     public static ClassLoader method(){  
       Log.v("ClassToBeImported", "called method of class " + ClassToBeImported.class.getName());
      return ClassToBeImported.class.getClassLoader(); 
    }
}

我们会演示这个方法如何在宿主中被调用的,并且我们可以跟踪这个类的类加载器。我们编译这个工程得到的ask文件为:apkbeloaded-debug.apk。

创建一个宿主apk工程

我把宿主包名命名为:dexclassloaderdemo。我们在assets目录下创建一个目录plugins,然后把apkbeloaded-debug.apk拷贝到该目录下。在MainActivity中创建一个方法为:loadDexClasses

public void loadDexClassses() { 
    if (android.os.Build.VERSION.SDK_INT < android.os.Build.VERSION_CODES.ICE_CREAM_SANDWICH) { 
    Log.v("loadDexClassses", "LoadDexClasses is only available for ICS or up"); 
    } 
    String paths[] = null; 
    try { 
         paths = getAssets().list("plugins");
     } catch (IOException e) {
         e.printStackTrace(); 
     } 
    if (paths == null) { 
        Log.v("loadlDexClasses", "There was no " + paths); return; 
    } 
    Log.v("loadDexClasses", "Dex Preparing to loadDexClasses!");
    for (String file : paths) { 
        //接下来完善 
    } 
}

我们从getAssets的plugins中读取文件列表,然后打算逐个加载(当然本例子中只有一个)。注意,我们不能直接从asserts目录中加载apk(asserts不是一个文件系统目录,只是apk的一个资源目录),先来写一个从assert目录中拷贝apk到一个文件目录下:copyAssetsApkToFile(本例中拷贝到sdcard,实际开发中最好拷贝到私有目录,防止被注入)。

public void copyAssetsApkToFile(Context context, String src, String des) {
     try { 
        InputStream is = context.getAssets().open(src); 
        FileOutputStream fos = new FileOutputStream(new File(des));
        byte[] buffer = new byte[1024]; 
        while (true) { 
            int len = is.read(buffer); 
            if (len == -1) { 
                break; 
            } 
            fos.write(buffer, 0, len); 
       } 
       is.close();
       fos.close(); 
     } catch (Exception e) 
     { 
        e.printStackTrace();
     } 
}

下面我们来完善核心代码:

for (String file : paths) { 
    File pluginDir = Environment.getExternalStorageDirectory();     
    pluginDir.mkdirs(); 
    String desDir = pluginDir.getAbsolutePath(); 
    String des = desDir + "/" + "apkbeloaded-debug.apk";
    File desFile = new File(des);
    File optimizedDirectory = this.getDir(OPT_DIR, Context.MODE_PRIVATE);
    if (!desFile.exists()){ copyAssetsApkToFile(this, "plugins/"+file, des);
    } 
    final DexClassLoader classloader = new DexClassLoader( des, optimizedDirectory.getAbsolutePath(), "data/local/tmp/natives/", ClassLoader.getSystemClassLoader()); 
    Log.v("loadDexClasses", "Searching for class : " + "com.registry.Registry");
     try {
     Class<?> classToLoad = (Class<?>) classloader.loadClass("com.dexclassdemo.liuguangli.apkbeloaded.Registry"); 
     Field classesField = classToLoad.getDeclaredField("_classes"); 
     ArrayList<Class<?>> classes = null;
     classes = (ArrayList<Class<?>>) classesField.get(null); 
    for (Class<?> cls : classes) {
     Log.v("loadDexClasses", "Class loaded " + cls.getName());
     if (cls.getName().contains("ClassToBeImported")) { 
      Method m = cls.getMethod("method"); 
      ClassLoader xb = (ClassLoader) m.invoke(null);
      if (xb.equals(ClassLoader.getSystemClassLoader())) 
       Log.v("loadDexClasses", "Same ClassLoader");
      else 
       Log.v("loadDexClasses", "Different ClassLoader");    
     Log.v("loadDexClasses", xb.toString()); 
    } 
  }
 } catch (IllegalAccessException e) { 
    e.printStackTrace(); 
 } catch (ClassNotFoundException e) { 
  e.printStackTrace(); 
 } catch (NoSuchFieldException e) { 
  e.printStackTrace(); 
 } catch (NoSuchMethodException e) { 
  e.printStackTrace(); } catch (InvocationTargetException e) { 
  e.printStackTrace(); 
 }
}

Demo源码
下载源码:https://github.com/liuguangli/DexClassLoaderDemo
下篇文章研究:如何加载插件中的资源



文/大利猫(简书作者)
原文链接:http://www.jianshu.com/p/43a8a9b932de
著作权归作者所有,转载请联系作者获得授权,并标注“简书作者”。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值