Android中插件开发篇之----类加载器

               

前言

关于插件,已经在各大平台上出现过很多,eclipse插件、chrome插件、3dmax插件,所有这些插件大概都为了在一个主程序中实现比较通用的功能,把业务相关或者让可以让用户自定义扩展的功能不附加在主程序中,主程序可在运行时安装和卸载。在android如何实现插件也已经被广泛传播,实现的原理都是实现一套插件接口,把插件实现编成apk或者dex,然后在运行时使用DexClassLoader动态加载进来,不过在这个开发过程中会遇到很多的问题,所以这一片就先不介绍如何开发插件,而是先解决一下开发过程中会遇到的问题,这里主要就是介绍DexClassLoader这个类使用的过程中出现的错误


导读

Java中的类加载器:http://blog.****.net/jiangwei0910410003/article/details/17733153

Android中的动态加载机制:http://blog.****.net/jiangwei0910410003/article/details/17679823

System.loadLibrary的执行过程:http://blog.****.net/jiangwei0910410003/article/details/41490133


一、预备知识

Android中的各种加载器介绍

插件开发的过程中DexClassLoader和PathClassLoader这两个类加载器了是很重要的,但是他们也是有区别的,而且我们也知道PathClassLoader是Android应用中的默认加载器。他们的区别是:

DexClassLoader可以加载任何路径的apk/dex/jar

PathClassLoader只能加载/data/app中的apk,也就是已经安装到手机中的apk。这个也是PathClassLoader作为默认的类加载器的原因,因为一般程序都是安装了,在打开,这时候PathClassLoader就去加载指定的apk(解压成dex,然后在优化成odex)就可以了。


我们可以看一下他们的源码:

DexClassLoader.java

/* * Copyright (C) 2008 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package dalvik.system;import java.io.File;import java.io.IOException;import java.net.MalformedURLException;import java.net.URL;import java.util.zip.ZipFile;/** * Provides a simple {@link ClassLoader} implementation that operates on a * list of jar/apk files with classes.dex entries.  The directory that * holds the optimized form of the files is specified explicitly.  This * can be used to execute code not installed as part of an application. * * The best place to put the optimized DEX files is in app-specific * storage, so that removal of the app will automatically remove the * optimized DEX files.  If other storage is used (e.g. /sdcard), the * app may not have an opportunity to remove them. */public class DexClassLoader extends ClassLoader {    private static final boolean VERBOSE_DEBUG = false;    /* constructor args, held for init */    private final String mRawDexPath;    private final String mRawLibPath;    private final String mDexOutputPath;    /*     * Parallel arrays for jar/apk files.     *     * (could stuff these into an object and have a single array;     * improves clarity but adds overhead)     */    private final File[] mFiles;         // source file Files, for rsrc URLs    private final ZipFile[] mZips;       // source zip files, with resources    private final DexFile[] mDexs;       // opened, prepped DEX files    /**     * Native library path.     */    private final String[] mLibPaths;    /**     * Creates a {@code DexClassLoader} that finds interpreted and native     * code.  Interpreted classes are found in a set of DEX files contained     * in Jar or APK files.     *     * The path lists are separated using the character specified by     * the "path.separator" system property, which defaults to ":".     *     * @param dexPath     *  the list of jar/apk files containing classes and resources     * @param dexOutputDir     *  directory where optimized DEX files should be written     * @param libPath     *  the list of directories containing native libraries; may be null     * @param parent     *  the parent class loader     */    public DexClassLoader(String dexPath, String dexOutputDir, String libPath,        ClassLoader parent) {        super(parent);......
我们看到,他是继承了ClassLoader类的,ClassLoader是类加载器的鼻祖类。同时我们也会发现DexClassLoader只有一个构造函数,而且这个构造函数是:dexPath、dexOutDir、libPath、parent

dexPath:是加载apk/dex/jar的路径

dexOutDir:是dex的输出路径(因为加载apk/jar的时候会解压除dex文件,这个路径就是保存dex文件的)

libPath:是加载的时候需要用到的lib库,这个一般不用

parent:给DexClassLoader指定父加载器


我们在来看一下PathClassLoader的源码

PathClassLoader.java

/* * Copyright (C) 2007 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * *      http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */package dalvik.system;import java.io.ByteArrayOutputStream;import java.io.File;import java.io.FileNotFoundException;import java.io.IOException;import java.io.InputStream;import java.io.RandomAccessFile;import java.net.MalformedURLException;import java.net.URL;import java.util.ArrayList;import java.util.Enumeration;import java.util.List;import java.util.NoSuchElementException;import java.util.zip.ZipEntry;import java.util.zip.ZipFile;/** * Provides a simple {@link ClassLoader} implementation that operates on a list * of files and directories in the local file system, but does not attempt to * load classes from the network. Android uses this class for its system class * loader and for its application class loader(s). */public class PathClassLoader extends ClassLoader {    private final String path;    private final String libPath;    /*     * Parallel arrays for jar/apk files.     *     * (could stuff these into an object and have a single array;     * improves clarity but adds overhead)     */    private final String[] mPaths;    private final File[] mFiles;    private final ZipFile[] mZips;    private final DexFile[] mDexs;    /**     * Native library path.     */    private final List<String> libraryPathElements;    /**     * Creates a {@code PathClassLoader} that operates on a given list of files     * and directories. This method is equivalent to calling     * {@link #PathClassLoader(String, String, ClassLoader)} with a     * {@code null} value for the second argument (see description there).     *     * @param path     *            the list of files and directories     *     * @param parent     *            the parent class loader     */    public PathClassLoader(String path, ClassLoader parent) {        this(path, null, parent);    }    /**     * Creates a {@code PathClassLoader} that operates on two given     * lists of files and directories. The entries of the first list     * should be one of the following:     *     * <ul>     * <li>Directories containing classes or resources.     * <li>JAR/ZIP/APK files, possibly containing a "classes.dex" file.     * <li>"classes.dex" files.     * </ul>     *     * The entries of the second list should be directories containing     * native library files. Both lists are separated using the     * character specified by the "path.separator" system property,     * which, on Android, defaults to ":".     *     * @param path     *            the list of files and directories containing classes and     *            resources     *     * @param libPath     *            the list of directories containing native libraries     *     * @param parent     *            the parent class loader     */    public PathClassLoader(String path, String libPath, ClassLoader parent) {        super(parent);....
看到了PathClassLoader类也是继承了ClassLoader的,但是他的构造函数和DexClassLoader有点区别就是,少了一个dexOutDir,这个原因也是很简单,因为PathClassLoader是加载/data/app中的apk,而这部分的apk都会解压释放dex到指定的目录:

/data/dalvik-cache

Android中插件开发篇之----类加载器

这个释放解压操作是系统做的。所以PathClassLoader可以不需要这个参数的。


上面看了他们两的区别,下面在来看一下Android中的各种类加载器分别加载哪些类:

package com.example.androiddemo;import android.app.Activity;import android.content.Context;import android.os.Bundle;import android.util.Log;import android.widget.ListView;public class MainActivity extends Activity @Override protected void onCreate(Bundle savedInstanceState) {  super.onCreate(savedInstanceState);  setContentView(R.layout.activity_main);    Log.i("DEMO", "Context的类加载加载器:"+Context.class.getClassLoader());  Log.i("DEMO", "ListView的类加载器:"+ListView.class.getClassLoader());  Log.i("DEMO", "应用程序默认加载器:"+getClassLoader());  Log.i("DEMO", "系统类加载器:"+ClassLoader.getSystemClassLoader());  Log.i("DEMO", "系统类加载器和Context的类加载器是否相等:"+(Context.class.getClassLoader()==ClassLoader.getSystemClassLoader()));  Log.i("DEMO", "系统类加载器和应用程序默认加载器是否相等:"+(getClassLoader()==ClassLoader.getSystemClassLoader()));    Log.i("DEMO","打印应用程序默认加载器的委派机制:");  ClassLoader classLoader = getClassLoader();  while(classLoader != null){   Log.i("DEMO", "类加载器:"+classLoader);   classLoader = classLoader.getParent();  }    Log.i("DEMO","打印系统加载器的委派机制:");  classLoader = ClassLoader.getSystemClassLoader();  while(classLoader != null){   Log.i("DEMO", "类加载器:"+classLoader);   classLoader = classLoader.getParent();  }   }}

运行结果:

Android中插件开发篇之----类加载器

依次来看一下

1) 系统类的加载器

Log.i("DEMO", "Context的类加载加载器:"+Context.class.getClassLoader());Log.i("DEMO", "ListView的类加载器:"+ListView.class.getClassLoader());
从结果看到他们的加载器是:BootClassLoader,关于他源码我没有找到,只找到了class文件(用jd-gui查看):

Android中插件开发篇之----类加载器

看到他也是继承了ClassLoader类。


2) 应用程序的默认加载器

Log.i("DEMO", "应用程序默认加载器:"+getClassLoader());
运行结果:

Android中插件开发篇之----类加载器

默认类加载器是PathClassLoader,同时可以看到加载的apk路径,libPath(一般包括/vendor/lib和/system/lib)


3) 系统类加载器

Log.i("DEMO", "系统类加载器:"+ClassLoader.getSystemClassLoader());
运行结果:

Android中插件开发篇之----类加载器

系统类加载器其实还是PathClassLoader,只是加载的apk路径不是/data/app/xxx.apk了,而是系统apk的路径:/system/app/xxx.apk


4) 默认加载器的委派机制关系

Log.i("DEMO","打印应用程序默认加载器的委派机制:");ClassLoader classLoader = getClassLoader();while(classLoader != null){ Log.i("DEMO", "类加载器:"+classLoader); classLoader = classLoader.getParent();}
打印结果:

Android中插件开发篇之----类加载器

默认加载器PathClassLoader的父亲是BootClassLoader

5) 系统加载器的委派机制关系

Log.i("DEMO","打印系统加载器的委派机制:");classLoader = ClassLoader.getSystemClassLoader();while(classLoader != null){ Log.i("DEMO", "类加载器:"+classLoader); classLoader = classLoader.getParent();}
运行结果: