前言:研究Small插件化框架的时候,要搞懂Android Framework的类加载机制,这篇主要对android 类加载的详细解析。
1、Android类加载机制
Android系统的Dalvik虚拟机如同其他Java虚拟机一样,在运行程序时首先需要将对应的类加载到内存中。
在Java标准的虚拟机中,类加载可以从.class文件中读取,也可以是其他形式的二进制流,因此,Java程序开发常常利用这一点,在程序运行时手动加载Class,从而达到代码动态加载执行的目的。
然而Dalvik虚拟机毕竟不算是标准的Java虚拟机,因此在类加载机制上,他们是不同的。例如,Android的类加载不能直接从.class文件中读取,而是从.dex文件中读取;此外,在使用标准Java虚拟机时,我们经常自定义继承自ClassLoader的类加载器。然后通过defineClass()方法来从一个二进制流中加载Class。然而,这在Android里是行不通的,参看源码我们知道,Android中ClassLoader的defineClass()方法除了抛出一个“UnsupportedOperationException”之外,什么都没做。
• Dalvik虚拟机类加载机制
在Dalvik虚拟机里,defineClass()方法被弃用,我们如何实现动态加载类呢?
Android为我们从ClassLoader派生出了两个类:DexClassLoader和PathClassLoader,并且在ClassLoader中定义了loadClass()方法。
DexClassLoader和PathClassLoader这两个继承自BaseDexClassLoader,BaseDexClassLoader继承自ClassLoader,BaseDexClassLoader本质上是覆写了ClassLoader的findClass()方法。Android中,ClassLoader用loadClass()方法来加载我们需要的类,我们可以参看ClassLoader部分源码:
protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
//检查本ClassLoader是否已经加载
Class<?> clazz = findLoadedClass(className);
//如果本ClassLoader没有加载
if (clazz == null) {
ClassNotFoundException suppressed = null;
//从父ClassLoader中查找
try {
clazz = parent.loadClass(className, false);
} catch (ClassNotFoundException e) {
suppressed = e;
}
if (clazz == null) {
//调用findClass来加载
try {
clazz = findClass(className);
} catch (ClassNotFoundException e) {
e.addSuppressed(suppressed);
throw e;
}
}
}
return clazz;
}
DexClassLoader和PathClassLoader都属于符合双亲委派模型的类加载器(因为它们没有重载loadClass()方法)。
其特点如下:
会先查询当前ClassLoader实例是否加载过此类,有就返回
如果没有,查询Parent是否已经加载过此类,如果已经加载过,就直接返回Parent加载的类
如果parent路线上的ClassLoader都没有加载,才调用findClass执行类的加载工作
ClassLoader与PathClassLoader和DexClassLoader的类图关系如下图所示:
• BaseDexClassLoader的findClass加载类过程
findClass加载类过程示意图如下:
findClass方法类加载的过程如下:
1、生成Element数组dexElements,生成pathList
BaseDexClassLoader的构造方法如下:
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String libraryPath, ClassLoader parent) {
super(parent);
this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
}
BaseDexClassLoader中的构造方法包含4个参数,他们分别是:
(1) dexPath : 指目标类所在的APK或jar文件的路径.类装载器将从该路径中寻找指定的目标类,该类必须是APK或jar的全路径.如果要包含多个路径,路径之间必须使用特定的分割符分隔,分隔符为File.pathSeparator(“:”).
(2) optimizedDirectory : 由于dex文件被包含在APK或者Jar文件中,因此在装载目标类之前需要先从APK或Jar文件中解压出dex文件,该参数就是指定解压出的dex文件存放的路径.
(3) libraryPath : 指目标类中所使用的C/C++库存放的路径
(4) parent : 是指该装载器的父装载器
在BaseDexClassLoader中生成DexPathList类的对象pathList,在DexPathList的构造方法中调用DexPathList类中的makePathElements()方法,在makePathElements()方法中会调用splitDexPath()方法,如果dexPath包含多个路径,splitDexPath()方法能够提取出File.pathSeparator分隔的多个路径,并将多个路径放入List中返回,然后makePathElements()方法会调用loadDexFile()方法遍历List中每个包含dex的文件生成DexFile,用生成的DexFile生成Element对象,用这些Element对象构成Element数组dexElements。
DexPathList.java中的loadDexFile()方法源码如下:
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);
}
}
一个应用程序中所有类加载到内存中,就是在makePathElements执行loadDexFile()方法来实现的。
optimizedDirectory是一个内部存储路径,用来缓存我们需要加载的dex文件的。DexClassLoader可以指定自己的optimizedDirectory,所以它可以加载外部的dex,因为这个dex会被复制到内部路径的optimizedDirectory;而PathClassLoader没有optimizedDirectory,所以它只能加载内部的dex,这些大都是存在系统中已经安装过的apk里面的。
它们的不同之处总结如下是:
DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
PathClassLoader只能加载系统中已经安装过的apk,Android中大部分类的加载默认采用此类
2、pathList调用BaseDexClassLoader中的findClass()方法
调用BaseDexClassLoader的findClass()方法,在此方法,pathList调用DexPathList中的findClass()方法,BaseDexClassLoader的findClass()方法如下:
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions);
if (c == null) {
ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList);
for (Throwable t : suppressedExceptions) {
cnfe.addSuppressed(t);
}
throw cnfe;
}
return c;
}
3、调用DexPathList中的findClass()方法
pathList是DexPathList类型的一个对象,DexPathList中的findClass()方法源码如下:
public Class findClass(String name, List<Throwable> suppressed) {
for (Element element : dexElements) {
DexFile dex = element.dexFile;
if (dex != null) {
Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
if (clazz != null) {
return clazz;
}
}
}
if (dexElementsSuppressedExceptions != null) {
suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
}
return null;
}
DexPathList的findClass()方法,遍历dexElements元素的DexFile实例,也就是遍历所有加载过的dex文件,一个个调用loadClassBinaryName()方法,看能不能找到我们想要的类,如果可以,返回这个类。
DexFile.java中的loadClassBinaryName()方法:
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
return defineClass(name, loader, mCookie, suppressed);
}
private static Class defineClass(String name, ClassLoader loader, Object cookie,
List<Throwable> suppressed) {
Class result = null;
try {
result = defineClassNative(name, loader, cookie);
} catch (NoClassDefFoundError e) {
if (suppressed != null) {
suppressed.add(e);
}
} catch (ClassNotFoundException e) {
if (suppressed != null) {
suppressed.add(e);
}
}
return result;
}
private static native Class defineClassNative(String name,
ClassLoader loader, Object cookie) throws ClassNotFoundException, NoClassDefFoundError;
4、返回
将加载的类返回ClassLoader类的loadClass()方法。
总结
当加载一个类的时候,需要首先获取类加载器,调用类加载器的loadClass方法,在loadClass方法中先查询当前ClassLoader实例是否加载过此类,有就返回;如果没有,查询parent指定的类加载器是否已经加载过此类,如果已经加载过,就直接返回parent加载的类;如果parent路线上的ClassLoader都没有加载,才调用findClass执行类的加载工作。BaseDexClassLoader的findClass方法调用之前,首先在BaseDexClassLoader的构造方法中生成了DexPathList类的对象pathList,在DexPathList类的构造方法中,利用loadDexFile()方法将dex文件中的类加载到内存中,并生成对应的DexFile对象,进而生成Element对象,用这些Element对象构成Element数组dexElements。在BaseDexClassLoader的findClass方法中,调用了DexPathList类的findClass方法,在DexPathList类的findClass方法里,遍历dexElements元素的DexFile实例,一个个调用loadClassBinaryName()方法,看能不能找到我们想要的类,如果可以,返回这个类。
2、类加载器的生成
在应用程序第一次启动时,都会首先创建Application对象。会调用ActivityThread.java中的handleBindApplication()方法,handleBindApplication()方法调用getPackageInfoNoCheck()方法生成LoadedApk对象data.info,它们的源代码如下:
ActivityThread.java中的handleBindApplication()方法:
private void handleBindApplication(AppBindData data) {
LoadedApk data.info = getPackageInfoNoCheck(data.appInfo, data.compatInfo);
......
//调用LoadedApk.java中makeApplication()方法
Application app = data.info.makeApplication(data.restrictedBackupMode, null);
......
}
ActivityThread.java中getPackageInfoNoCheck()方法:
public final LoadedApk getPackageInfoNoCheck(ApplicationInfo ai, CompatibilityInfo compatInfo) {
return getPackageInfo(ai, compatInfo, null, false, true, false);
}
getPackageInfoNoCheck()方法调用了getPackageInfo()方法,getPackageInfo()方法的源代码如下:
private LoadedApk getPackageInfo(ApplicationInfo aInfo, CompatibilityInfo compatInfo,ClassLoader baseLoader,
boolean securityViolation, boolean includeCode,boolean registerPackage) {
final boolean differentUser = (UserHandle.myUserId() != UserHandle.getUserId(aInfo.uid));
synchronized (mResourcesManager) {
WeakReference<LoadedApk> ref;
if (differentUser) {
// Caching not supported across users
ref = null;
} else if (includeCode) {
//
ref = mPackages.get(aInfo.packageName);
} else {
ref = mResourcePackages.get(aInfo.packageName);
}
LoadedApk packageInfo = ref != null ? ref.get() : null;
if (packageInfo == null || (packageInfo.mResources != null
&& !packageInfo.mResources.getAssets().isUpToDate())) {
if (localLOGV) Slog.v(TAG, (includeCode ? "Loading code package "
: "Loading resource-only package ") + aInfo.packageName
+ " (in " + (mBoundApplication != null
? mBoundApplication.processName : null)
+ ")");
//生成LoadedApk类的对象packageInfo
packageInfo = new LoadedApk(this, aInfo, compatInfo, baseLoader,
securityViolation, includeCode &&
(aInfo.flags&ApplicationInfo.FLAG_HAS_CODE) != 0, registerPackage);
if (mSystemThread && "android".equals(aInfo.packageName)) {
packageInfo.installSystemApplicationInfo(aInfo,
getSystemContext().mPackageInfo.getClassLoader());
}
if (differentUser) {
// Caching not supported across users
} else if (includeCode) {
//将生成的packageInfo存入到WeakReference<LoadedApk> mPackages中
mPackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
} else {
mResourcePackages.put(aInfo.packageName,
new WeakReference<LoadedApk>(packageInfo));
}
}
return packageInfo;
}
}
第一次调用getPackageInfo()方法,WeakReference mPackages为null,利用new LoadedApk()生成LoadedApk类的对象packageInfo,并将生成的packageInfo存入到WeakReference mPackages中。当第二次调用getPackageInfo()方法时,WeakReference mPackages不为null,直接返回WeakReference mPackages中存储的LoadedApk对象。可以看到同一个应用程序中,类加载所使用的LoadedApk对象都是同一个。
生成LoadedApk对象data.info后,在ActivityThread.java中的handleBindApplication()方法中调用了makeApplication()方法生成Application类的对象,makeApplication()方法在LoadedApk.java文件中定义的,其源码为:
public Application makeApplication(boolean forceDefaultAppClass, Instrumentation instrumentation) {
try {
java.lang.ClassLoader cl = getClassLoader();
......
//调用ContextImpl类的createAppContext()方法
ContextImpl appContext = ContextImpl.createAppContext(mActivityThread, this);
app = mActivityThread.mInstrumentation.newApplication(cl, appClass, appContext);
appContext.setOuterContext(app);
} catch (Exception e) {
if (!mActivityThread.mInstrumentation.onException(app, e)) {
throw new RuntimeException(
"Unable to instantiate application " + appClass
+ ": " + e.toString(), e);
}
}
return app;
}
Instrumentation.java中newApplication()方法:
public Application newApplication(ClassLoader cl, String className, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return newApplication(cl.loadClass(className), context);
}
static public Application newApplication(Class<?> clazz, Context context)
throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
Application app = (Application)clazz.newInstance();
app.attach(context);
return app;
}
可见,调用newApplication()方法之前,第一次调用了LoadedApk的getClassLoader()方法,其源码如下:
public ClassLoader getClassLoader() {
synchronized (this) {
if (mClassLoader != null) {
return mClassLoader;
}
if (mIncludeCode && !mPackageName.equals("android")) {
......
//调用了ApplicationLoaders.java里面的getClassLoader()方法
mClassLoader = ApplicationLoaders.getDefault().getClassLoader(zip, lib,mBaseClassLoader);
StrictMode.setThreadPolicy(oldPolicy);
} else {
if (mBaseClassLoader == null) {
mClassLoader = ClassLoader.getSystemClassLoader();
} else {
mClassLoader = mBaseClassLoader;
}
}
return mClassLoader;
}
}
当第一次调用LoadedApk的getClassLoader()方法,mClassLoader==null,会调用ApplicationLoaders.java里面的getClassLoader()方法生成mClassLoader;当再次调用LoadedApk的getClassLoader()方法,mClassLoader!=null,则将第一次生成的mClassLoader直接返回。可见,getClassLoader()返回的ClassLoader也是同一个。
调用了ApplicationLoaders.java里面的getClassLoader()方法,源码如下:
public ClassLoader getClassLoader(String zip, String libPath, ClassLoader parent)
{
ClassLoader baseParent = ClassLoader.getSystemClassLoader().getParent();
synchronized (mLoaders) {
if (parent == null) {
parent = baseParent;
}
if (parent == baseParent) {
ClassLoader loader = mLoaders.get(zip);
if (loader != null) {
return loader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = new PathClassLoader(zip, libPath, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
mLoaders.put(zip, pathClassloader);
return pathClassloader;
}
Trace.traceBegin(Trace.TRACE_TAG_ACTIVITY_MANAGER, zip);
PathClassLoader pathClassloader = new PathClassLoader(zip, parent);
Trace.traceEnd(Trace.TRACE_TAG_ACTIVITY_MANAGER);
return pathClassloader;
}
}
在这个方法中调用了生成了PathClassLoader对象,根据前面的讲解,可以知道在PathClassLoader的构造方法中,调用了makePathElements()方法,完成了所有的类加载到内存中。这个PathClassLoader就是我们应用程序中类加载所用的类加载器。
总结
类加载器生成的流程图:
在应用程序启动时,会创建Application对象,在创建Application的过程中,完成了应用程序中所有类的加载到内存。当再使用这个程序中其他类的时候,再次调用ActivityThread.java中的getPackageInfo()方法,此方法不会再new一个新的LoadedApk对象,而是返回创建Application时的那个LoadedApk对象,即同一个LoadedApk对象,用这个对象调用getClassLoader获得的加载器也都是同一个,不会重复加载类,生成类的实例的形式一般为:
getClassLoader().loadClass(className).newInstance();
Android常用的类包括Activity、Service、BroadcastReceiver、Fragment,都采用了同样的方式用同一个类加载器生成类的实例。
比如:
① 使用Fragment时,在Fragment.java文件的instantiate()方法中调用了loadClass()方法。
/**
* Create a new instance of a Fragment with the given class name. This is
* the same as calling its empty constructor.
*/
public static Fragment instantiate(Context context, String fname, @Nullable Bundle args) {
try {
Class<?> clazz = sClassMap.get(fname);
if (clazz == null) {
// Class not found in the cache, see if it's real, and try to add it
clazz = context.getClassLoader().loadClass(fname);
if (!Fragment.class.isAssignableFrom(clazz)) {
throw new InstantiationException("Trying to instantiate a class "
+ fname + " that is not a Fragment", new ClassCastException());
}
sClassMap.put(fname, clazz);
}
//生成Fragment类的单例
Fragment f = (Fragment)clazz.newInstance();
if (args != null) {
args.setClassLoader(f.getClass().getClassLoader());
f.mArguments = args;
}
return f;
} catch (ClassNotFoundException e) {
} catch (java.lang.InstantiationException e) {
} catch (IllegalAccessException e) {
}
}
② 在使用BroadcastReceiver时,在ActivityThread.java的handleReceiver()方法中调用了loadClass()方法。
private void handleReceiver(ReceiverData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
String component = data.intent.getComponent().getClassName();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
IActivityManager mgr = ActivityManagerNative.getDefault();
BroadcastReceiver receiver;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
data.intent.setExtrasClassLoader(cl);
data.intent.prepareToEnterProcess();
data.setExtrasClassLoader(cl);
receiver = (BroadcastReceiver)cl.loadClass(component).newInstance();
} catch (Exception e) {
......
}
......
}
③ 在使用Service时,在ActivityThread.java的handleCreateService()方法中调用了loadClass()方法。
private void handleCreateService(CreateServiceData data) {
// If we are getting ready to gc after going to the background, well
// we are back active so skip it.
unscheduleGcIdler();
LoadedApk packageInfo = getPackageInfoNoCheck(
data.info.applicationInfo, data.compatInfo);
Service service = null;
try {
java.lang.ClassLoader cl = packageInfo.getClassLoader();
service = (Service) cl.loadClass(data.info.name).newInstance();
} catch (Exception e) {
......
}
......
}
④ 在Activity的使用过程中,在Instrumentation.java的newActivity()方法中调用了loadClass()方法。
public Activity newActivity(ClassLoader cl, String className,
Intent intent)throws InstantiationException, IllegalAccessException,
ClassNotFoundException {
return (Activity)cl.loadClass(className).newInstance();
}
这些类调用getClassLoader方法所获得的类加载器是同一个,用loadClass获取到对应的Class,采用newInstance()生成实例。