您的位置:首页 > 移动开发 > Android开发

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的话,肯定找不到自定义的类的
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  android classloader