Android ClassLoader机制
2016-01-11 14:58
471 查看
什么是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
dexPath需要装载的APK或者Jar文件的路径。包含多个路径用File.pathSeparator间隔开,在Android上默认是 “:”
optimizedDirectory优化后的dex文件存放目录,不能为null
libraryPath目标类中使用的C/C++库so文件的路径,每个目录用File.pathSeparator间隔开; 可以为null
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的话,肯定找不到自定义的类的
相关文章推荐
- 使用C++实现JNI接口需要注意的事项
- Android IPC进程间通讯机制
- Android Manifest 用法
- [转载]Activity中ConfigChanges属性的用法
- Android之获取手机上的图片和视频缩略图thumbnails
- Android之使用Http协议实现文件上传功能
- Android学习笔记(二九):嵌入浏览器
- android string.xml文件中的整型和string型代替
- i-jetty环境搭配与编译
- android之定时器AlarmManager
- android wifi 无线调试
- Android Native 绘图方法
- Android java 与 javascript互访(相互调用)的方法例子
- android 代码实现控件之间的间距
- android FragmentPagerAdapter的“标准”配置
- Android"解决"onTouch和onClick的冲突问题
- android:installLocation简析
- android searchView的关闭事件
- SourceProvider.getJniDirectories