关闭

Android ClassLoader机制

标签: androidclassloader
3257人阅读 评论(0) 收藏 举报
分类:

什么是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

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:86407次
    • 积分:1652
    • 等级:
    • 排名:千里之外
    • 原创:78篇
    • 转载:14篇
    • 译文:0篇
    • 评论:13条
    最新评论