Android源码学习——ClassLoader(1)
来源:互联网 发布:sql developer使用 编辑:程序博客网 时间:2024/05/19 14:02
本文学习的源码参考AndroidXRef,版本为Lollipop 5.1.0_r1。
类加载与动态加载
Java代码是通过Class来实现的,程序运行时虚拟机首先将对应的类加载到内存中,然后才能进行对象实例化以及后续工作,而这个加载过程就是通过ClassLoader来实现的,也即是类加载。
同时,在Java虚拟机中,我们可以自定义继承自ClassLoader的类加载器,通过defineClass方法动态加载一个Class,实现控制程序的类加载过程,提高程序的灵活性。
Android的Dalvik/ART虚拟机与Java虚拟机一样,采用类加载机制,也具有动态加载技术,但是他们之间还是存在一些差异的。
Android的类加载机制
不管是类加载还是动态加载,它的基础都是ClassLoader,也即专门用来处理类加载工作的,也称为类加载器。而且,一个应用不仅仅只有一个类加载器。
实际上,Android系统启动时,会创建一个Boot类型的ClassLoader实例,用于加载一些系统Framework层级需要的类,而Android应用里也需要用到一些系统的类,所以APP启动的时候也会把这个Boot类型的ClassLoader传进来。另外,APP也有自己实现的类,这些类保存在APK的dex文件里面,所以APP启动的时候,也会创建一个自己的ClassLoader实例,用于加载自己dex文件中的类。
可以通过下面的代码查看当前程序的类加载器:
// 获取当前程序的类加载器 ClassLoader classLoader = getClassLoader(); if (classLoader != null){ Log.i(TAG, "[onCreate] classLoader " + i + " : " + classLoader.toString()); // 获取当前类加载器的父类加载器 while (classLoader.getParent()!=null){ classLoader = classLoader.getParent(); Log.i(TAG,"[onCreate] classLoader " + i + " : " + classLoader.toString()); } }
Android中的ClassLoader是一个抽象类,实际开发过程中,我们一般是使用他的两个具体的子类DexClassLoader、PathClassLoader这些类加载器来加载类的。他们的主要差别在于:
- PathClassLoader只能加载系统中已经安装过的apk;
- DexClassLoader可以加载jar/apk/dex,可以从SD卡中加载未安装的apk;
看具体代码:
public class PathClassLoader extends BaseDexClassLoader { public PathClassLoader(String dexPath, ClassLoader parent) { super(dexPath, null, null, parent); } public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); }}
public class DexClassLoader extends BaseDexClassLoader { public DexClassLoader(String dexPath, String optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, new File(optimizedDirectory), libraryPath, parent); }}
这两个类加载器都只是简单的封装了BaseDexClassLoader,具体的实现还是调了父类里的方法。
但是可以看到,PathClassLoader的第二个参数optimizedDirectory只能是null。
继续看BaseDexClassLoader里面的实现:
public class BaseDexClassLoader extends ClassLoader { private final DexPathList pathList; public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(parent); this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); } ......}
创建了一个DexPathList实例。
继续看DexPathList的构造函数的实现:
public DexPathList(ClassLoader definingContext, String dexPath, String libraryPath, File optimizedDirectory) { if (definingContext == null) { throw new NullPointerException("definingContext == null"); } if (dexPath == null) { throw new NullPointerException("dexPath == null"); } if (optimizedDirectory != null) { if (!optimizedDirectory.exists()) { throw new IllegalArgumentException( "optimizedDirectory doesn't exist: " + optimizedDirectory); } if (!(optimizedDirectory.canRead() && optimizedDirectory.canWrite())) { throw new IllegalArgumentException( "optimizedDirectory not readable/writable: " + optimizedDirectory); } } this.definingContext = definingContext; ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions); if (suppressedExceptions.size() > 0) { this.dexElementsSuppressedExceptions = suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); } else { dexElementsSuppressedExceptions = null; } this.nativeLibraryDirectories = splitLibraryPath(libraryPath); }
先进行了一些验证,然后调用了makeDexElements函数。
private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, ArrayList<IOException> suppressedExceptions) { ArrayList<Element> elements = new ArrayList<Element>(); /* * Open all files and load the (direct or contained) dex files up front. */ for (File file : files) { File zip = null; DexFile dex = null; String name = file.getName(); if (file.isDirectory()) { // We support directories for looking up resources. // This is only useful for running libcore tests. elements.add(new Element(file, true, null, null)); } else if (file.isFile()){ if (name.endsWith(DEX_SUFFIX)) { // Raw dex file (not inside a zip/jar). try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException ex) { System.logE("Unable to load dex file: " + file, ex); } } else { zip = file; try { dex = loadDexFile(file, optimizedDirectory); } catch (IOException suppressed) { suppressedExceptions.add(suppressed); } } } else { System.logW("ClassLoader referenced unknown path: " + file); } if ((zip != null) || (dex != null)) { elements.add(new Element(file, false, zip, dex)); } } return elements.toArray(new Element[elements.size()]); }
这段代码是为了生成一个元素为dex或resource的数组,如果传入参数是一个目录则直接new Element(file, true, null, null)
添加到数组里面;
如果传入参数是一个文件,如.dex、.zip、.jar或.apk,会先执行loadDexFile再new Element(file, false, zip, dex)
添加到数组中。
其中,不是直接给出.dex文件的情况下,会将file赋值给zip。
看下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); } }
终于看到optimizedDirectory带来的差异了。
如果optimizedDirectory为null,也即PathClassLoader的情况,程序会直接调DexFile的构造函数创建一个DexFile对象;
而DexClassLoader则先去调了一个optimizedPathFor,再去调DexFile的loadDex方法。
看下这个里面做了什么:
private static String optimizedPathFor(File path, File optimizedDirectory) { String fileName = path.getName(); if (!fileName.endsWith(DEX_SUFFIX)) { int lastDot = fileName.lastIndexOf("."); if (lastDot < 0) { fileName += DEX_SUFFIX; } else { StringBuilder sb = new StringBuilder(lastDot + 4); sb.append(fileName, 0, lastDot); sb.append(DEX_SUFFIX); fileName = sb.toString(); } } File result = new File(optimizedDirectory, fileName); return result.getPath(); }
解析path中dex的文件名,在optimizedDirectory位置新建了一个文件,返回文件路径。
static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { return new DexFile(sourcePathName, outputPathName, flags); }
调用DexFile的另一个构造函数,返回DexFile对象。
看下这两个构造函数的区别:
public DexFile(String fileName) throws IOException { mCookie = openDexFile(fileName, null, 0); mFileName = fileName; guard.open("close"); } private DexFile(String sourceName, String outputName, int flags) throws IOException { if (outputName != null) { try { String parent = new File(outputName).getParent(); if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { throw new IllegalArgumentException("Optimized data directory " + parent + " is not owned by the current user. Shared storage cannot protect" + " your application from code injection attacks."); } } catch (ErrnoException ignored) { // assume we'll fail with a more contextual error later } } mCookie = openDexFile(sourceName, outputName, flags); mFileName = sourceName; guard.open("close"); }
如果是DexClassLoader,会先检查输出路径outputName父目录的所有者。但是无论是哪种ClassLoader,最终都会调用openDexFile。
看下openDexFile的实现:
private static long openDexFile(String sourceName, String outputName, int flags) throws IOException { return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags); }
而openDexFile会直接返回openDexFileNative,而openDexFile是一个native函数,后面再继续分析。
- Android源码学习——ClassLoader(1)
- Android源码学习——ClassLoader(2)
- Android 插件化基础——ClassLoader 源码解析
- tomcat源码阅读(三)——ClassLoader背景知识
- java源码学习5-ClassLoader
- 通过Tomcat源码学习ClassLoader
- JVM学习笔记(1)——类加载器ClassLoader
- Android ClassLoader之getSystemResourceAsStream源码分析
- tomcat源码阅读(二)——ClassLoader及catalina启动
- ClassLoader源码
- Android ClassLoader工作原理学习记录(一)
- Android ClassLoader工作原理学习记录(二)
- Android源码学习——linker(1)
- Android解析ClassLoader(二)Android中的ClassLoader
- Tomcat源码阅读#1:classloader初始化
- 浅读Tomcat源码(五)---classLoader
- Android源码学习1
- JVM学习笔记5—类加载器(classloader)
- you need edit config ld.so.conf after you installed C driver for MongoDB
- HDU 2027
- 冒泡排序与快排算法
- Ajax的数据解析
- oracle用户表空间导出导入脚本,以及windows下定时备份脚本
- Android源码学习——ClassLoader(1)
- [微信小程序]脱坑指南
- python求解对给定字符串,求解所有子序列是否为回文序列
- C++常用的查找方法
- 安卓 导入配置 Android Import the configuration
- Tomcat和Java Virtual Machine的性能调优总结
- AndroiStudio Error:Execution failed for task ':app:process_360DebugManifest 报错
- semver
- Bad Cowtractors