Android ClassLoader机制

来源:互联网 发布:淘宝电动车价格便宜 编辑:程序博客网 时间:2024/06/15 12:20

什么是ClassLoader?

Classloader动态的装载Class文件。标准的java sdk中有一个ClassLoader类,借助这个类可以装载想要的Class文件,每个ClassLoader对象在初始化时必须制定Class文件的路径。
写程序的时候不是有import关键字可以引用制定的类吗?为何还要使用这个类加载器呢?
原因其实是这样的,使用import关键字引用的类必须符合以下两个条件
1. 类文件必须在本地,当程序运行时需要次类时,这时类装载器会自动装载该类,程序员不需要关注此过程。
2. 编译的时候必须有这个类文件,否则编译不通过。
想让程序在运行的时候动态调用怎么办呢?用import显示是不符合上面的两种要求的。此时ClassLoader就派上用场了。

Android DexClassLoader

android应用程序,本质上使用的是java开发,使用标准的java编译器编译出Class文件,和普通的java开发不同的地方是把class文件再重新打包成dex类型的文件,这种重新打包会对Class文件内部的各种函数表、变量表等进行优化,最终产生了odex文件。odex文件是一种经过android打包工具优化后的Class文件,因此加载这样特殊的Class文件就需要特殊的类装载器,所以android中提供了DexClassLoader类

public DexClassLoader (String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent)

Added in API level 3
Creates a DexClassLoader that finds interpreted and native code. Interpreted classes are found in a set of DEX files contained in Jar or APK files.
创建一个DexClassLoader用来找出指定的类和本地代码(c/c++代码)。用来解释执行在DEX文件中的class文件。

Parameters

  1. dexPath 需要装载的APK或者Jar文件的路径。包含多个路径用File.pathSeparator间隔开,在Android上默认是 “:”
  2. optimizedDirectory 优化后的dex文件存放目录,不能为null
  3. libraryPath 目标类中使用的C/C++库so文件的路径,每个目录用File.pathSeparator间隔开; 可以为null
  4. parent 该类装载器的父装载器,一般用当前执行类的装载器

类装载器DexClassLoader的具体使用
这个类的使用过程基本是这样:
1. 传递apk/jar目录,dex的解压缩目录,c/c++库的目录
2. 创建一个 DexClassLoader实例
3. 加载指定的类返回一个Class
4. 然后使用反射调用这个Class

Android ClassLoader机制

ClassLoader

Android使用的是Dalvik虚拟机装载class文件,所以classloader不同于java默认类库rt.jar包中java.lang.ClassLoader, 可以看到android中的classloader做了些修改,但是原理还是差不多的,实现了双亲委托模型

public abstract class ClassLoader {    static private class SystemClassLoader {        public static ClassLoader loader = ClassLoader.createSystemClassLoader();    }    private ClassLoader parent;    private static ClassLoader createSystemClassLoader() {        String classPath = System.getProperty("java.class.path", "."); //adb shell中执行getprop java.class.path命令查看,此时发现没有值        return new PathClassLoader(classPath, BootClassLoader.getInstance()); //getSystemClassLoader加载器是pathclassloader,它的parent是BootClassLoader,但是DexPathList[[directory "."]..    }    public static ClassLoader getSystemClassLoader() {        return SystemClassLoader.loader; //返回系统默认类加载器    }    ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {        if (parentLoader == null && !nullAllowed) {            throw new NullPointerException("parentLoader == null && !nullAllowed");        }        parent = parentLoader;    }    //自定义classloader需要重载该方法    protected Class<?> findClass(String className) throws ClassNotFoundException {        throw new ClassNotFoundException(className);    }    protected final Class<?> findLoadedClass(String className) {        ClassLoader loader;        if (this == BootClassLoader.getInstance()) //如果该classloader是BootClassLoader类型            loader = null;        else            loader = this;        return VMClassLoader.findLoadedClass(loader, className); //调用本地c/c++方法    }    //可以看到android系统其实也实现了双亲委托模型,只是跟java的双亲委托模型有点不同而已,虚拟机不同嘛    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {        Class<?> clazz = findLoadedClass(className); //检查是否已经加载过        if (clazz == null) {            ClassNotFoundException suppressed = null;            try {                clazz = parent.loadClass(className, false); //使用parent去查找            } catch (ClassNotFoundException e) {                suppressed = e;            }            if (clazz == null) {                try {                    clazz = findClass(className); //调用findclass                } catch (ClassNotFoundException e) {                    e.addSuppressed(suppressed);                    throw e;                }            }        }        return clazz;    }    //可以看到loadClass的resolve参数是没用的    protected final void resolveClass(Class<?> clazz) {    }}//BootClassLoader单例模型class BootClassLoader extends ClassLoader {    private static BootClassLoader instance;    @FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")    public static synchronized BootClassLoader getInstance() {        if (instance == null) {            instance = new BootClassLoader();        }        return instance;    }    public BootClassLoader() {        super(null, true);    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        return Class.classForName(name, false, null);    }}

BaseDexClassLoader

PathClassLoader和DexClassLoader的基类,重载了findClass方法,直接交给DexPathList处理

BaseDexClassLoader类分析

public class BaseDexClassLoader extends ClassLoader {    /** originally specified path (just used for {@code toString()}) */    private final String originalPath;    /** structured lists of path elements */    private final DexPathList pathList;    public BaseDexClassLoader(String dexPath, File optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(parent);        this.originalPath = dexPath;        this.pathList =            new DexPathList(this, dexPath, libraryPath, optimizedDirectory);    }    @Override    protected Class<?> findClass(String name) throws ClassNotFoundException {        Class clazz = pathList.findClass(name); //实际上是通过DexPathList来查找类的        if (clazz == null) {            throw new ClassNotFoundException(name);        }        return clazz;    }    @Override    protected URL findResource(String name) {        return pathList.findResource(name);    }    @Override    protected Enumeration<URL> findResources(String name) {        return pathList.findResources(name);    }    @Override    public String findLibrary(String name) {        return pathList.findLibrary(name);    }    @Override    protected synchronized Package getPackage(String name) {        if (name != null && !name.isEmpty()) {            Package pack = super.getPackage(name);            if (pack == null) {                pack = definePackage(name, "Unknown", "0.0", "Unknown",                        "Unknown", "0.0", "Unknown", null);            }            return pack;        }        return null;    }    @Override    public String toString() {        return getClass().getName() + "[" + originalPath + "]";    }}

DexClassLoader和PathClassLoader类

都继承自BaseDexClassLoader,只是有不同的构造函数,唯一的区别PathClassLoader就是optimizedDirectory参数为null,很好理解嘛,PathClassLoader加载的是data/app/…安装目录下的dex,但是
DexClassLoader加载外部未安装的dex/apk/jar/zip等,所以需要把最后的odex文件放在optimizedDirectory目录,所以不能为null

public class DexClassLoader extends BaseDexClassLoader {    public DexClassLoader(String dexPath, String optimizedDirectory,            String libraryPath, ClassLoader parent) {        super(dexPath, new File(optimizedDirectory), libraryPath, parent);    }}public class PathClassLoader extends BaseDexClassLoader {    public PathClassLoader(String dexPath, ClassLoader parent) { //PathClassLoader        super(dexPath, null, null, parent);    }    public PathClassLoader(String dexPath, String libraryPath,            ClassLoader parent) {        super(dexPath, null, libraryPath, parent);    }}

DexPathList类分析

/*package*/ final class DexPathList {    //支持的文件格式    private static final String DEX_SUFFIX = ".dex";    private static final String JAR_SUFFIX = ".jar";    private static final String ZIP_SUFFIX = ".zip";    private static final String APK_SUFFIX = ".apk";    /** class definition context */    private final ClassLoader definingContext; //持有的classloader引用    /** list of dex/resource (class path) elements */    private final Element[] dexElements; //elements集合    /** list of native library directory elements */    private final File[] nativeLibraryDirectories; //本地库so文件目录    public DexPathList(ClassLoader definingContext, String dexPath,            String libraryPath, File optimizedDirectory) {        ......        this.definingContext = definingContext;        this.dexElements =            makeDexElements(splitDexPath(dexPath), optimizedDirectory); //调用makeDexElements方法,splitDexPath(dexPath)方法返回dexPath路径下所有files的集合        this.nativeLibraryDirectories = splitLibraryPath(libraryPath);    }    //返回Element[]数组,每个Element就是一个jar,dex,apk,zip文件,如果是这几种文件格式那么就会被添加到dexElements中去了    private static Element[] makeDexElements(ArrayList<File> files,            File optimizedDirectory) {        ArrayList<Element> elements = new ArrayList<Element>();        for (File file : files) {            ZipFile zip = null;            DexFile dex = null;            String name = file.getName();            if (name.endsWith(DEX_SUFFIX)) { //如果是dex后缀名                // Raw dex file (not inside a zip/jar).                try {                    dex = loadDexFile(file, optimizedDirectory); //调用loadDexFile方法返回dex                } catch (IOException ex) {                    System.logE("Unable to load dex file: " + file, ex);                }            } else if (name.endsWith(APK_SUFFIX) || name.endsWith(JAR_SUFFIX)                    || name.endsWith(ZIP_SUFFIX)) { //如果是apk,jar,zip后缀名                try {                    zip = new ZipFile(file);                } catch (IOException ex) {                    System.logE("Unable to open zip file: " + file, ex);                }                try {                    dex = loadDexFile(file, optimizedDirectory); //调用loadDexFile方法返回dex                } catch (IOException ignored) {                }            } else {                System.logW("Unknown file type for: " + file);            }            if ((zip != null) || (dex != null)) {                elements.add(new Element(file, zip, dex)); //添加一个Element,注意这里的参数,file,zip,dex            }        }        return elements.toArray(new Element[elements.size()]);    }    private static DexFile loadDexFile(File file, File optimizedDirectory)            throws IOException {        if (optimizedDirectory == null) {            return new DexFile(file);        } else {            String optimizedPath = optimizedPathFor(file, optimizedDirectory);            return DexFile.loadDex(file.getPath(), optimizedPath, 0); //实际调用的是DexFile的loadDex静态方法        }    }    //关键方法,查找类会对循环对所有的dex文件进行查找,知道找到第一个符合条件的停止    public Class findClass(String name) {        for (Element element : dexElements) {            DexFile dex = element.dexFile;            if (dex != null) {                Class clazz = dex.loadClassBinaryName(name, definingContext); //实际调用的是DexFile的loadClassBinaryName方法                if (clazz != null) {                    return clazz;                }            }        }        return null;    }    /*package*/ static class Element {        public final File file;        public final ZipFile zipFile;        public final DexFile dexFile;        public Element(File file, ZipFile zipFile, DexFile dexFile) {            this.file = file;            this.zipFile = zipFile;            this.dexFile = dexFile;        }    }}

DexFile类分析

    private DexFile(String sourceName, String outputName, int flags) throws IOException {        mCookie = openDexFile(sourceName, outputName, flags);        mFileName = sourceName;        guard.open("close");        //System.out.println("DEX FILE cookie is " + mCookie);    }    static public DexFile loadDex(String sourcePathName, String outputPathName,        int flags) throws IOException {        return new DexFile(sourcePathName, outputPathName, flags);    }    public Class loadClass(String name, ClassLoader loader) {        String slashName = name.replace('.', '/');        return loadClassBinaryName(slashName, loader);    }    public Class loadClassBinaryName(String name, ClassLoader loader) {        return defineClass(name, loader, mCookie); //调用defineClass本地方法    }    private native static Class defineClass(String name, ClassLoader loader, int cookie);

代码示例

示例1-不同类加载器

    @OnClick(R.id.startPlugina)    void onStartPluginA() {        /*Intent intent = new Intent(this, ProxyActivity.class);        intent.putExtra(ProxyActivity.EXTRA_DEX_PATH, "/mnt/sdcard/DynamicLoadHost/dlapp-a.apk");        startActivity(intent);*/        Log.i("LiaBin", "Context的类加载加载器:" + Context.class.getClassLoader());//系统库的class,所以是BootClassLoader        Log.i("LiaBin", "String的类加载加载器:" + String.class.getClassLoader());//系统库的class,所以是BootClassLoader,不同于java吧,java应用程序的话打印的是null        Log.i("LiaBin", "MainActivity的类加载器:" + MainActivity.class.getClassLoader());//本地的class,所以是PathClassLoader        Log.i("LiaBin", "StringRequest的类加载器:" + StringRequest.class.getClassLoader());//第三方库class,所以也是PathClassLoader。。v7,v4,recycleview中的也是第三方库哟        Log.i("LiaBin", "应用程序默认加载器:" + getClassLoader()); //这才是默认的加载器DexPathList[[zip file "/data/app/demo.lbb.mytest-1.apk"]..        Log.i("LiaBin", "系统类加载器:" + ClassLoader.getSystemClassLoader()); //上面代码可以知道此时是PathClassLoader,同时DexPathList[[directory "."]..        Log.i("LiaBin", "系统类加载器和应用程序默认加载器是否相等:" + (getClassLoader() == ClassLoader.getSystemClassLoader())); //打印false        Log.i("LiaBin", "自定义类和第三方类库使用的classloader和应用程序默认加载器是否相等:" + (MainActivity.class.getClassLoader() == getClassLoader())); 打印true        Log.i("LiaBin", "打印应用程序默认加载器的委派机制:"); //parent是BootClassLoader        ClassLoader classLoader = getClassLoader();        while (classLoader != null) {            Log.i("LiaBin", "类加载器:" + classLoader);            classLoader = classLoader.getParent();        }        Log.i("LiaBin", "打印系统加载器的委派机制:"); //parent是BootClassLoader        classLoader = ClassLoader.getSystemClassLoader();        while (classLoader != null) {            Log.i("LiaBin", "类加载器:" + classLoader);            classLoader = classLoader.getParent();        }        try {            ClassLoader.getSystemClassLoader().loadClass("demo.lbb.mytest.MainActivity"); //肯定是查找不到MainActivity的,因为SystemClassLoader的DexPathList为.,然后parent:BootClassLoader也找不到,因为MainActivity是自定义的嘛,如果此时换成""android.app.Activity",那就能找到了,因为parent:BootClassLoader加载了系统库的class            Log.i("LiaBin", "系统默认加载器找到了MainActivity");        } catch (ClassNotFoundException e) {            e.printStackTrace();            Log.i("LiaBin", "系统默认加载器没找到MainActivity"); //打印此处        }        try {            getClassLoader().loadClass("demo.lbb.mytest.MainActivity");            Log.i("LiaBin", "应用程序默认加载器找到了MainActivity"); //打印此处        } catch (ClassNotFoundException e) {            e.printStackTrace();            Log.i("LiaBin", "应用程序默认加载器没找到MainActivity");        }    }

打印结果:
I/LiaBin: Context的类加载加载器:java.lang.BootClassLoader@41cb45f0
I/LiaBin: String的类加载加载器:java.lang.BootClassLoader@41cb45f0
I/LiaBin: MainActivity的类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: StringRequest的类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: 应用程序默认加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: 系统类加载器:dalvik.system.PathClassLoader[DexPathList[[directory “.”],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
I/LiaBin: 系统类加载器和应用程序默认加载器是否相等:false
I/LiaBin: 自定义类和第三方类库使用的classloader和应用程序默认加载器是否相等:true
I/LiaBin: 打印应用程序默认加载器的委派机制:
I/LiaBin: 类加载器:dalvik.system.PathClassLoader[DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”],nativeLibraryDirectories=[/data/app-lib/demo.lbb.mytest-1, /vendor/lib, /system/lib]]]
I/LiaBin: 类加载器:java.lang.BootClassLoader@41cb45f0
I/LiaBin: 打印系统加载器的委派机制:
I/LiaBin: 类加载器:dalvik.system.PathClassLoader[DexPathList[[directory “.”],nativeLibraryDirectories=[/vendor/lib, /system/lib]]]
I/LiaBin: 类加载器:java.lang.BootClassLoader@41cb45f0
I/LiaBin: 系统默认加载器没找到MainActivity
I/LiaBin: 应用程序默认加载器找到了MainActivity

简单看一下getClassLoader()方法的实现,我们知道实际上是ContextImpl

    @Override    public ClassLoader getClassLoader() {        return mPackageInfo != null ?                mPackageInfo.getClassLoader() : ClassLoader.getSystemClassLoader();    }

mPackageInfo是个LoadedApk类型对象

    /**     * Sets application info about the system package.     */    void installSystemApplicationInfo(ApplicationInfo info, ClassLoader classLoader) {        assert info.packageName.equals("android");        mApplicationInfo = info;        mClassLoader = classLoader;    }

installSystemApplicationInfo在哪里调用就不清楚了,但是肯定不是ClassLoader.getSystemClassLoader()啦

示例2 –双亲委托模型

先看一下项目结构:

此时dlapp作为plugin.apk放在/mnt/sdcard/DynamicLoadHost/目录下
可以看到app项目有一个类lbb.test.dlapp.MainActivity,dlapp也有一个类lbb.test.dlapp.MainActivity,这两者类的路径完全相同,所以可以同时被加载吗?
答案是当然可以的,因为虚拟机中全限定名以及加载此类的ClassLoader来共同确定。也就是说即使两个类的全限定名是相同的,但是因为不同的 ClassLoader加载了此类,那么在JVM中它是不同的类

app中的MainActivity类

package lbb.test.dlapp;public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main2);    }    public void printStr() {        Log.d("LiaBin", "str from main----");    }}

dlapp中的类

package lbb.test.dlapp;public class MainActivity extends Activity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);    }    public void printStr() {        Log.d("LiaBin", "str from plugin----");    }}

然后把dlapp编译成的plugin.apk放在/mnt/sdcard/DynamicLoadHost/目录下作为插件,app项目动态加载该插件

app项目的入口activity,此时也是MainActivity类,但是包名不一样了

package demo.lbb.mytest;public class MainActivity extends BaseActivity {    @Override    protected void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setContentView(R.layout.activity_main);        ButterKnife.bind(this);    }    @OnClick(R.id.startPlugin)    void onStartPlugin() {        String className = "lbb.test.dlapp.MainActivity";        String mDexPath = "/mnt/sdcard/DynamicLoadHost/plugin.apk";        File dexOutputDir = this.getDir("dex", 0);        final String dexOutputPath = dexOutputDir.getAbsolutePath(); //data/data/..../app_dex目录下会有优化过后的dex文件        ClassLoader localClassLoader = ClassLoader.getSystemClassLoader(); //拿到系统默认加载器        //ClassLoader localClassLoader = getClassLoader(); //拿到应用程序默认加载器        DexClassLoader dexClassLoader = new DexClassLoader(mDexPath,                dexOutputPath, null, localClassLoader); // 因为是动态加载外部的,未安装,所以需要使用DexClassLoader加载器,同时此时设置parent        try {            Class<?> localClass = dexClassLoader.loadClass(className);            Constructor<?> localConstructor = localClass                    .getConstructor(new Class[]{});            Object instance = localConstructor.newInstance(new Object[]{});            Log.d("LiaBin", "onStartPlugin instance = " + instance);            Method printStr = localClass.getMethod("printStr", new Class[]{});            printStr.setAccessible(true);            printStr.invoke(instance, new Object[]{});        } catch (Exception e) {            e.printStackTrace();            Log.d("LiaBin", "startPlugin 发生异常");        }    }    @OnClick(R.id.startPluginmain)    void onStartPluginMain() {        ClassLoader localClassLoader = getClassLoader(); //本项目中的lbb.test.dlapp.MainActivity直接使用应用程序加载器加载就行了,因为已经安装了在data/app/**目录下了        try {            Class<?> localClass = localClassLoader.loadClass("lbb.test.dlapp.MainActivity");            Constructor<?> localConstructor = localClass                    .getConstructor(new Class[]{});            Object instance = localConstructor.newInstance(new Object[]{});            Log.d("LiaBin", "onStartPluginMain instance = " + instance);            Method printStr = localClass.getMethod("printStr", new Class[]{});            printStr.setAccessible(true);            printStr.invoke(instance, new Object[]{});        } catch (Exception e) {            e.printStackTrace();            Log.d("LiaBin", "onStartPluginMain 发生异常");        }    }}

情况1:ClassLoader localClassLoader = ClassLoader.getSystemClassLoader(); //拿到系统默认加载器
打印结果:
D/LiaBin: onStartPlugin instance = lbb.test.dlapp.MainActivity@42e105f8
D/LiaBin: str from plugin—-
D/LiaBin: onStartPluginMain instance = lbb.test.dlapp.MainActivity@42e14320
D/LiaBin: str from main—-

情况2:ClassLoader localClassLoader = getClassLoader(); //拿到应用程序默认加载器
D/LiaBin: onStartPlugin instance = lbb.test.dlapp.MainActivity@42d8edb8
D/LiaBin: str from main—-
D/LiaBin: onStartPluginMain instance = lbb.test.dlapp.MainActivity@42d92b50
D/LiaBin: str from main—-

看到区别了吗?onStartPlugin方法打印的结果不一样了
情况1,onStartPlugin方法调用的是dlapp中的mainactivity,因为parent是ClassLoader.getSystemClassLoader()系统默认加载器,此时parent中找不到mainactivity,所以最终会调用DexClassLoader的findclass方法

情况2,onStartPlugin方法调用的是app中的mainactivity,为什么呢?因为parent是getClassLoader()应用程序加载器,此时parent中有该mainactivity,所以

getClassLoader()); //应用程序DexPathList[[zip file “/data/app/demo.lbb.mytest-1.apk”]..

ClassLoader.getSystemClassLoader()); //系统默认加载器,此时也是PathClassLoader,但是DexPathList[[directory “.”]..所以如果有classloader把它当作parent的话,肯定找不到自定义的类的

2 0