Android中的ClassLoader

BaseDexClassLoader

Dex类加载器的基类,包含Dex类加载器之间通用功能的实现。

DexClassLoader

A class loader that loads classes from .jar and .apk files containing a classes.dex entry. This can be used to execute code not installed as part of an application.

一个可以从包含classes.dex实体的.jar或.apk文件中加载classes的类加载器。可以用于实现dex的动态加载、代码热更新等等。

This class loader requires an application-private, writable directory to cache optimized classes. Use Context.getDir(String, int) to create such a directory:

这个类加载器必须要一个app的私有、可写目录来缓存经过优化的classes(odex文件),使用Context.getDir(String, int)方法可以创建一个这样的目录。
示例: File dexOutputDir = context.getDir(“dex”, 0);

PathClassLoader

Provides a simple ClassLoader implementation that operates on a list of files and directories in the local file system, but does not attempt to load classes from the network. Android uses this class for its system class loader and for its application class loader(s).

提供一个简单的ClassLoader实现,可以操作在本地文件系统的文件列表或目录中的classes,但不可以从网络中加载classes。

DexClassLoader与PathClassLoader

DexClassLoader与PathClassLoader的区别就是DexClassLoader可以加载本地或者网络的classes,而PathClassLoader只能加载本地的classes。

DexClassLoader源码分析

以下是DexClassLoader的构造函数,源码是来自Android2.3的系统源码,Dex文件的加载就是在构造函数中实现的。接收3个参数dexPath、dexOutputDir、libPath、parent。

  • dexPath:dex文件路径列表,多个路径使用”:”分隔
  • dexOutputDir:经过优化的dex文件(odex)文件输出目录
  • libPath:动态库路径(将被添加到app动态库搜索路径列表中)
  • parent:这个一个ClassLoader,这个参数的主要作用是保留java中ClassLoader的委托机制(优先父类加载器加载classes,由上而下的加载机制,防止重复加载类字节码)。
    /** * Creates a {@code DexClassLoader} that finds interpreted and native * code. Interpreted classes are found in a set of DEX files contained * in Jar or APK files. * * The path lists are separated using the character specified by * the "path.separator" system property, which defaults to ":". * * @param dexPath * the list of jar/apk files containing classes and resources * @param dexOutputDir * directory where optimized DEX files should be written * @param libPath * the list of directories containing native libraries; may be null * @param parent * the parent class loader */    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,        ClassLoader parent) {        super(parent);        if (dexPath == null || dexOutputDir == null)            throw new NullPointerException();        mRawDexPath = dexPath;        mDexOutputPath = dexOutputDir;        mRawLibPath = libPath;        String[] dexPathList = mRawDexPath.split(":");        int length = dexPathList.length;        //System.out.println("DexClassLoader: " + dexPathList);        mFiles = new File[length];        mZips = new ZipFile[length];        mDexs = new DexFile[length];        /* open all Zip and DEX files up front */        for (int i = 0; i < length; i++) {            //System.out.println("My path is: " + dexPathList[i]);            File pathFile = new File(dexPathList[i]);            mFiles[i] = pathFile;            if (pathFile.isFile()) {                try {                    mZips[i] = new ZipFile(pathFile);                } catch (IOException ioex) {                    // expecting IOException and ZipException                    System.out.println("Failed opening '" + pathFile                        + "': " + ioex);                    //ioex.printStackTrace();                }                /* we need both DEX and Zip, because dex has no resources */                try {                    String outputName =                        generateOutputName(dexPathList[i], mDexOutputPath);                    mDexs[i] = DexFile.loadDex(dexPathList[i], outputName, 0);                } catch (IOException ioex) {                    // might be a resource-only zip                    System.out.println("Failed loadDex '" + pathFile                        + "': " + ioex);                }            } else {                if (VERBOSE_DEBUG)                    System.out.println("Not found: " + pathFile.getPath());            }        }        /* * Prep for native library loading. */        String pathList = System.getProperty("java.library.path", ".");        String pathSep = System.getProperty("path.separator", ":");        String fileSep = System.getProperty("file.separator", "/");        if (mRawLibPath != null) {            if (pathList.length() > 0) {                pathList += pathSep + mRawLibPath;            }            else {                pathList = mRawLibPath;            }        }        mLibPaths = pathList.split(pathSep);        length = mLibPaths.length;        // Add a '/' to the end so we don't have to do the property lookup        // and concatenation later.        for (int i = 0; i < length; i++) {            if (!mLibPaths[i].endsWith(fileSep))                mLibPaths[i] += fileSep;            if (VERBOSE_DEBUG)                System.out.println("Native lib path " +i+ ": " + mLibPaths[i]);        }    }

以上代码大概可以总结为以下几个步骤:
1. super(parent),调用父类构造函数,关联父类ClassLoader。
2. 对dexPath进行分割,得到dex文件路径列表dexPathList。
3. 迭代dexPathList,调用DexFile的静态方法loadDex加载dex文件。
4. 通过System.getProperty(“java.library.path”, “.”)获取app动态库搜索路径列表,并把libPath添加其后。

使用DexClassLoader加载dex文件

把jar转换为dex

关于dex的详细解释可以参考这篇文章
http://blog.csdn.net/androidsecurity/article/details/9428861

1.首先我编写了一个Test类,注意我这里让Test继承自Date,并导出为jar2dex.jar

package linchaolong.jar2dex.test;import java.util.Date;public class Test extends Date{    @Override    public String toString() {        return "linchaolong";    }}

dex2jar里一个脚本d2j-jar2dex可以将jar转换为dex

dex2jar github地址:https://github.com/pxb1988/dex2jar

dex2jar下载地址:http://yun.baidu.com/s/1bnAkIb9

2.把jar拷贝到dexjar解压目录下执行命令:d2j-jar2dex xxx.jar -o xxx.dex(把xxx.jar转换为xxx.dex)

实现dex文件的加密解密

dex文件的加密解密算法使用C/C++实现,使用NDK编译成动态库,通过jni调用加密解密算法对dex文件实现加密、解密。

以下是java层中加密、解密算法的接口

package linchaolong.utils;/** * 数据加密解密工具 * * @author linchaolong * */public class DataProtector {    static{        // 加载动态库,数据的加密解密算法实现在动态库中        System.loadLibrary("dataProtector");      }    /** * 加密数据 * * @param buff 数据 * @param size 数据大小 * @return 加密后的数据 */    public native static byte[] encrypt(byte[] buff, int size);    /** * 解密数据 * * @param buff 数据 * @param size 数据大小 * @return 解密后的数据 */    public native static byte[] decrypt(byte[] buff, int size);}

解密dex文件实现(注意:这里的解密实现是比较简单的,实际应用中应该做代码混淆或放到C/C++中实现,而且注意解密文件的保护)

package linchaolong.utils;import java.io.File;import java.io.FileInputStream;import java.io.FileNotFoundException;import java.io.FileOutputStream;import java.io.IOException;import java.io.InputStream;import android.util.Log;/** * Dex文件保护工具类 * * @author linchaolong * */public class DexProtector {    private static final int BUFF_SIZE = 1024*1024;    private static final String TAG = "DexProtector";    /** * 解密Dex文件 * * @param in 加密文件输入流 * @param outFile 输出解密文件 * @return 是否解密成功 */    public static boolean decryptDex(InputStream in, File outFile){        // 加密dex文件所在目录        File outDir = outFile.getParentFile();        // 如果目录不存在则创建        if (!outDir.exists() && outDir.isDirectory()) {            outDir.mkdirs();        }        try {            if (outFile.exists()) {                outFile.delete();            }            FileOutputStream out = new FileOutputStream(outFile);            byte[] buff = new byte[BUFF_SIZE];            int len = 0;            while((len = in.read(buff)) != -1){                // 调用native方法解密dex文件数据                byte[] decryptBuff = DataProtector.decrypt(buff, len);                out.write(decryptBuff, 0, len);            }            // 释放资源            in.close();            out.close();            return true;        } catch (FileNotFoundException e) {            e.printStackTrace();        } catch (IOException e) {            e.printStackTrace();        }        return false;    }    public static boolean decryptDex(File encryptFile, File outFile){        if (!encryptFile.exists()) {            Log.e(TAG, "加密文件 '" + encryptFile.getPath() + " ''不存在");            return false;        }        try {            return decryptDex(new FileInputStream(encryptFile), outFile);        } catch (FileNotFoundException e) {            e.printStackTrace();        }        return false;    }}

实现dex文件的动态加载

1.新建一个Application,并重写onCreate方法,用于在应用启动的时候解密并加载dex文件。

我这里把加密的test.dex放到了assets目录下,并命名为encrypt.dex

以下是Application的源码:

package linchaolong.dexproctor;import java.util.Date;import linchaolong.utils.DataProtector;import linchaolong.utils.DexProtector;import android.app.Application;import android.content.res.AssetManager;import android.util.Log;import dalvik.system.DexClassLoader;public class DexApplicatoin extends Application{    private static final String TAG = "DexApplicatoin";    @Override    public void onCreate() {        testDexLoader();        super.onCreate();    }    /** * 动态加载dex实现 */    private void testDexLoader() {        try {            // 解密dex文件的File对象            File decryptFile = new File(getDir("dex",MODE_PRIVATE), "test.dex");            // 经过优化的dex输出目录            File odexDir = getDir("odex_dir", MODE_PRIVATE);            try {                // 读取assets目录下的encrypt.dex并解密                InputStream encryptDexIn = getAssets().open("encrypt.dex");                DexProtector.decryptDex(encryptDexIn, decryptFile);            } catch (IOException e1) {                e1.printStackTrace();                return;            }            // 创建类加载器,加载解密后的dex文件            ClassLoader dexClassLoader = new DexClassLoader(decryptFile.getPath(), odexDir.getPath(), null, getClassLoader());            // 加载Test类            String className = "linchaolong.jar2dex.test.Test";            Class<?> testClass = dexClassLoader.loadClass(className);            if (testClass == null) {                Log.e(TAG,"ClassNotFoundException : can not found class " + className);            }else{                try {                    // 创建Test对象,Test继承自Date,这里用Date引用Test对象                    Date testObj = (Date) testClass.newInstance();                    if (testObj == null) {                        Log.e(TAG,"testObj is null ");                    }else{                        // 调用Test对象的toStirng方法                        Log.e("Test", "testObj.toString() = " + testObj.toString());                     }                }catch(Exception e) {                    e.printStackTrace();                }            }        } catch (ClassNotFoundException e) {            e.printStackTrace();        }    }}

2.在AndroidManifest.xml中配置Application。

运行结果:

最终打印结果与Test中toString方法实现一致,表示dex解密并加载成功了

项目地址:https://coding.net/u/linchaolong/p/DexProtector/git

更多相关文章

  1. Android的NDK开发(5)————Android(安卓)JNI层实现文件的read
  2. Android动态加载及hook资料汇总
  3. android 字符串加解密算法
  4. Android拍照、录像、录音代码范例
  5. Windows环境下搭建Cocos2dx的android开发环境
  6. 【Android】hwbinder的selinux配置
  7. Android之ViewFlipper的使用与ListView的分页加载
  8. 【Android(安卓)应用开发】 Android(安卓)APK 反编译 混淆 重编
  9. android adb shell 命令大全

随机推荐

  1. android布局中的基本属性:
  2. Android制作并替换系统开机动画bootanima
  3. android UI进阶之弹窗的使用(2)--实现通讯
  4. [置顶] Android常用适配器控件
  5. android监听软键盘+吐槽!
  6. Android输入子系统之InputReader读取键盘
  7. 由编译一个android开源项目, 学习如何解
  8. Android进行设备管理(针对企业开发)
  9. Android(安卓)监听短信2种方式:Broadcast
  10. -----------Android(安卓)Support v4、v7