Android类加载机制学习

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()生成实例。

  • 3
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 3
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 3
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值