Android类加载器

DVM还是ART它们加载的不再是Class文件,而是dex文件,所以Java的类加载器不再适用
系统ClassLoader包括三种分别是BootClassLoader、PathClassLoader 和 DexClassLoader。

BootClassLoader:

由Java实现,是ClassLoader的内部类,并继承自ClassLoader
BootClassLoader是一个单例类,需要注意的是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;
    }
}
DexClassLoader

DexClassLoader可以加载dex文件以及包含dex的.jar.zip 文件和apk文件,主要针对的是未安装的类;插件化和动态加载都是基于DexClassLoader来实现的

public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}
PathClassLoader:

加载系统类和应用程序的类,无法定义解压的dex文件存储路径(相当于把第二个参数固定为null的DexClassLoader)通常用来加载已安装的apk的dex文件

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super(dexPath, null, librarySearchPath, parent);
    }
}
Android加载器类图:

在这里插入图片描述

BaseDexClassLoader源码:

1.加载解析所有的类和库文件

BaseDexClassLoader的构造方法:

//dexPath:要加载的文件路径,可能是多个dex问文件的路径,多个路径间用:隔开
//optimizedDirectory:优化后的dex文件的路径
//librarySearchPath:库文件的搜索路径,一般是.so库文件
//parent:父加载器
public BaseDexClassLoader(String dexPath, File optimizedDirectory,String librarySearchPath,ClassLoader parent) {
		//连接父加载器
        super(parent);
        //创建一个DexPathList对象
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, optimizedDirectory);
}

DexPathList对象的构造过程:

class DexPathList {
	private ClassLoader definingContext;
	private Element[] dexElements;
	private Element[] nativeLibraryPathElements;		//本地方法库
	
	//构造方法主要做了这么三件事:
	//1.把dexPath拆分成多个路径,并把这些路径解析成一个Element数组
	//2.把系统本地方法库路径拆分并解析成一个List<File>
	//3.把库路径librarySearchPath拆分成多个路径,并把这些路径解析成一个Element数组
	public DexPathList(ClassLoader definingContext, String dexPath, String librarySearchPath, File optimizedDirectory) {
       	this.definingContext = definingContext;
       	ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
       	// save dexPath for BaseDexClassLoader
       	this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                 suppressedExceptions, definingContext);
 
       	this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
       	this.systemNativeLibraryDirectories =
              	splitPaths(System.getProperty("java.library.path"), true);
       	List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
       	allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
 
       	this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories,
              	suppressedExceptions,definingContext);
	}
}

Dalvik 利用 DexPathList 来查找并加载类,DexPathList的构造方法主要目的是根据几个参数构造两个 Element[]

//把系统本地方法库解析成一个FileList
private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
       List<File> result = new ArrayList<>();
       if (searchPath != null) {
           for (String path : searchPath.split(File.pathSeparator)) {
          	   //...
               result.add(new File(path));
           }
       }
       return result;
}

//作用是把dexPath列表转化成Element列表
private static Element[] makeElements(List<File> files, File optimizedDirectory,
                                          List<IOException> suppressedExceptions,
                                          boolean ignoreDexFiles,
                                          ClassLoader loader) {
       Element[] elements = new Element[files.size()];
       int elementsPos = 0;

       for (File file : files) {			//把每个dexPathFile转化成Element
           File zip = null;
           File dir = new File("");
           DexFile dex = null;
           String path = file.getPath();
           String name = file.getName();
 
           if (path.contains(zipSeparator)) {			//dexPath对应一个压缩文件
               String split[] = path.split(zipSeparator, 2);
               zip = new File(split[0]);
               dir = new File(split[1]);
           } else if (file.isDirectory()) {				//dexPath对应一个目录
               elements[elementsPos++] = new Element(file, true, null, null);
           } else if (file.isFile()) {					//dexPath对应一个普通文件
               if (!ignoreDexFiles && name.endsWith(DEX_SUFFIX)) {
                   // dexfile不在zip/jar文件中
                   dex = loadDexFile(file, optimizedDirectory, loader, elements);
               } else {
                   	//dexfile在zip/jar文件中
                   	zip = file;
                   	dex = loadDexFile(file, optimizedDirectory, loader, elements);
               	}
           } 
           if ((zip != null) || (dex != null)) {			//构造一个Element对象
               elements[elementsPos++] = new Element(dir, false, zip, dex);
           }
       }
       return elements;
}

总结:BaseDexClassLoader在构造方法中把路径中给出的文件解析成了Element数组

2.BaseDexClassLoader如何根据类名寻找并加载特定的类型:

BaseDexClassLoader类加载器通过findClass找到所需要的类:

//class = BaseDexClassLoader
protected Class<?> findClass(String name) throws ClassNotFoundException {
       List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
       Class c = pathList.findClass(name, suppressedExceptions);
       //空校验略
       return c;
}

BaseDexClassLoader寻找类的过程委托给DexPathList对象:

//class = DexPathList
public Class findClass(String name, List<Throwable> suppressed) {
	   //遍历Element数组,通过在这个数组头部插入其他Element对象可以改变类的解析结果,这是热修复的实现方法之一
       for (Element element : dexElements) {
           DexFile dex = element.dexFile;
           Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed);
           if (clazz != null) {
               return clazz;
           }
       }
       return null;
}

DexPathList又把寻找类的任务委托给DexFile:

//class = DexFile
private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
          DexPathList.Element[] elements) throws IOException {
          
       String parent = new File(outputName).getParent();
       //把dex文件加载到内存中
       mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
}

public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
	   //最后寻找类是交给native层完成的
       Class result = defineClassNative(name, loader, cookie, dexFile);
       return result;
}
如何动态加载Jar文件:

使用DexClassLoader方式加载类:

		//dex压缩文件的路径(可以是apk,jar,zip格式)
		String dexPath = Environment.getExternalStorageDirectory().toString() + File.separator + "Dynamic.apk";
		//dex解压释放后的目录
		String dexOutputDirs = Environment.getExternalStorageDirectory().toString();
		//定义DexClassLoader
    	//第一个参数:是dex压缩文件的路径
    	//第二个参数:是dex解压缩后存放的目录
    	//第三个参数:是C/C++依赖的本地库文件目录,可以为null
    	//第四个参数:是上一级的类加载器
    	DexClassLoader cl = new DexClassLoader(dexPath,dexOutputDirs,null,getClassLoader());
        try {
        	//使用DexClassLoader加载类
        	Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
            lib = (IDynamic)libProviderClazz.newInstance();
        } catch (Exception exception) {
            exception.printStackTrace();
        }

使用PathClassLoader方法加载类:

        //创建一个意图,用来找到指定的apk:这里的"com.dynamic.impl是指定apk中在AndroidMainfest.xml文件中定义的<action name="com.dynamic.impl"/>  
        Intent intent = new Intent("com.dynamic.impl", null);  
        //获得包管理器  
        PackageManager pm = getPackageManager();  
        List<ResolveInfo> resolveinfoes =  pm.queryIntentActivities(intent, 0);  
        //获得指定的activity的信息  
        ActivityInfo actInfo = resolveinfoes.get(0).activityInfo;  
        //获得apk的目录或者jar的目录  
        String apkPath = actInfo.applicationInfo.sourceDir;  
        //native代码的目录  
        String libPath = actInfo.applicationInfo.nativeLibraryDir;  
        //创建类加载器,把dex加载到虚拟机中  
        //第一个参数:是指定apk安装的路径,这个路径要注意只能是通过actInfo.applicationInfo.sourceDir来获取
        //第二个参数:是C/C++依赖的本地库文件目录,可以为null
        //第三个参数:是上一级的类加载器
        PathClassLoader pcl = new PathClassLoader(apkPath,libPath,this.getClassLoader());
        try {
        	//Class libProviderClazz = cl.loadClass("com.dynamic.impl.Dynamic");
        	//使用PathClassLoader加载类
            Class libProviderClazz = pcl.loadClass("com.dynamic.impl.Dynamic");
            lib = (IDynamic)libProviderClazz.newInstance();
        } catch (Exception exception) {
            exception.printStackTrace();
        }
总结:

DexClassLoader和PathClassLoader实际上只有构造方法,加载的过程都是完全使用BaseDexClassLoader的逻辑,BaseDexClassLoader加载类实际上是委托给DexFile的

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

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值