笔记 classloader

ClassLoader抽象类,主要实现:系统类加载器和自定义加载器。

ClassLoader

https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java
BootClassLoader :用于加载FrameWork层Class文件。
PathClassLoader/DexClassLoader:均继承与BaseDexClassLoader


public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

public class PathClassLoader extends BaseDexClassLoader {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }

    public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
        super((String)null, (File)null, (String)null, (ClassLoader)null);
        throw new RuntimeException("Stub!");
    }
}

唯一区别:String optimizedDirectory,在PathClassLoader直接为NULL。
BaseDexClassLoader :https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java

  public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
    }

    /**
     * @hide
     */
    public BaseDexClassLoader(String dexPath, File optimizedDirectory,
            String librarySearchPath, ClassLoader parent, boolean isTrusted) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);

        if (reporter != null) {
            reportClassLoaderChain();
        }
    }

DexPathList:https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

 DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
        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 suppressedExceptions = new ArrayList();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext, isTrusted);//这里这里去加载DexElements

        // Native libraries may exist in both the system and
        // application library paths, and we use this search order:
        //
        //   1. This class loader's library path for application libraries (librarySearchPath):
        //   1.1. Native library directories
        //   1.2. Path to libraries in apk-files
        //   2. The VM's library path from the system property for system libraries
        //      also known as java.library.path
        //
        // This order was reversed prior to Gingerbread; see http://b/2933456.
        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

makeDexElements 去加载[line:373]

  dex = loadDexFile(file, optimizedDirectory, loader, elements);
 private static DexFile loadDexFile(File file, File optimizedDirectory, ClassLoader loader,
                                       Element[] elements)
            throws IOException {
        if (optimizedDirectory == null) {
            return new DexFile(file, loader, elements);
        } else {
            String optimizedPath = optimizedPathFor(file, optimizedDirectory);
            return DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
        }
    }

DexFile:https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexFile.java

   /*
     * Private version with class loader argument.
     *
     * @param fileName
     *            the filename of the DEX file   //DEX文件
     * @param loader
     *            the class loader creating the DEX file object
     * @param elements
     *            the temporary dex path list elements from DexPathList.makeElements
     */
    DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
        mCookie = openDexFile(fileName, null, 0, loader, elements);
        mInternalCookie = mCookie;
        mFileName = fileName;
        //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName);
    }

DexFile.loadDex(file.getPath(), optimizedPath, 0, loader, elements);
最后会执行

    /**
     * Opens a DEX file from a given filename, using a specified file
     * to hold the optimized data.
     *
     * @param sourceName
     *  Jar or APK file with "classes.dex".   //jar或者APK文件,包含 dex文件
     * @param outputName
     *  File that will hold the optimized form of the DEX data.
     * @param flags
     *  Enable optional features.
     * @param loader
     *  The class loader creating the DEX file object.
     * @param elements
     *  The temporary dex path list elements from DexPathList.makeElements
     */
    private DexFile(String sourceName, String outputName, int flags, ClassLoader loader,
            DexPathList.Element[] elements) 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, loader, elements);
        mInternalCookie = mCookie;
        mFileName = sourceName;
        //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName);
    }

看注释鸭,最后openDexFile执行的是native方法。
所以如果是null的话,即PathClassLoad,只支持直接操作dex格式文件,或者已经安装的apk(因为已经安装的apk在cache中存在缓存的dex文件)。而DexClassLoader可以支持.apk、.jar和.dex文件,并且会在指定的outpath路径释放出dex文件。

class MainActivit : AppCompatActivity() {


    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_main)

        var cs=classLoader
        while (cs!=null){
            Log.d("test",cs.toString())
            cs=cs.parent
        }
        
    }
}

输出:

test: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.kalerm.oldtest-z8F5ZCS9v9fD8p1w23LCnQ==/base.apk"],nativeLibraryDirectories=[/data/app/com.kalerm.oldtest-z8F5ZCS9v9fD8p1w23LCnQ==/lib/x86, /system/lib, /vendor/lib]]]
test: java.lang.BootClassLoader@48e10c5


可以看到应用程序类是由PathClassLoader加载的。

双亲委派机制的loadclass 由ClassLoader 中的loadClass实现,

 protected Class loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // First, check if the class has already been loaded
            Class c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        c = parent.loadClass(name, false);
                    } else {
                        c = findBootstrapClassOrNull(name);
                    }
                } catch (ClassNotFoundException e) {
                    // ClassNotFoundException thrown if class not found
                    // from the non-null parent class loader
                }

                if (c == null) {
                    // If still not found, then invoke findClass in order
                    // to find the class.
                    c = findClass(name);
                }
            }
            return c;
    }

首先会 findLoadedClass,如果为空,则会使用parent 的 loadClass
打印得知,应用最后的parent的最后 是 BootClassLoader

BootClassLoader 定义在ClassLoader中:https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/ojluni/src/main/java/java/lang/ClassLoader.java

Line:1400+ 最后


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;
    }

    public BootClassLoader() {
        super(null);
    }

    @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        return Class.classForName(name, false, null);
    }

    @Override
    protected URL findResource(String name) {
        return VMClassLoader.getResource(name);
    }

    @SuppressWarnings("unused")
    @Override
    protected Enumeration findResources(String resName) throws IOException {
        return Collections.enumeration(VMClassLoader.getResources(resName));
    }

    /**
     * Returns package information for the given package. Unfortunately, the
     * Android BootClassLoader doesn't really have this information, and as a
     * non-secure ClassLoader, it isn't even required to, according to the spec.
     * Yet, we want to provide it, in order to make all those hopeful callers of
     * {@code myClass.getPackage().getName()} happy. Thus we construct a Package
     * object the first time it is being requested and fill most of the fields
     * with dummy values. The Package object is then put into the ClassLoader's
     * Package cache, so we see the same one next time. We don't create Package
     * objects for null arguments or for the default package.
     * 

* There a limited chance that we end up with multiple Package objects * representing the same package: It can happen when when a package is * scattered across different JAR files being loaded by different * ClassLoaders. Rather unlikely, and given that this whole thing is more or * less a workaround, probably not worth the effort. */ @Override protected Package getPackage(String name) { if (name != null && !name.isEmpty()) { synchronized (this) { Package pack = super.getPackage(name); if (pack == null) { pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); } return pack; } } return null; } @Override public URL getResource(String resName) { return findResource(resName); } @Override protected Class loadClass(String className, boolean resolve) throws ClassNotFoundException { Class clazz = findLoadedClass(className); if (clazz == null) { clazz = findClass(className); } return clazz; } @Override public Enumeration getResources(String resName) throws IOException { return findResources(resName); } }

上述可见BootClassLoader的loadclass 会终结递归,如果没找到,直接调用findclass。

BootClassLoader没加载成功,又会向下到BaseDexClassLoader调用findClass

 @Override
    protected Class findClass(String name) throws ClassNotFoundException {
        List suppressedExceptions = new ArrayList();
        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;
    }
 public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
        // TODO We should support giving this a library search path maybe.
        super(parent);
        this.pathList = new DexPathList(this, dexFiles);
    }

DexPathList: https://www.androidos.net.cn/android/9.0.0_r8/xref/libcore/dalvik/src/main/java/dalvik/system/DexPathList.java

 /**
     * List of dex/resource (class path) elements.
     * Should be called pathElements, but the Facebook app uses reflection
     * to modify 'dexElements' (http://b/7726934).
     */
    private Element[] dexElements;


   public Class findClass(String name, List suppressed) {
        for (Element element : dexElements) {
            Class clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

Class对象就是从Element中获得的。每一个Element就对应一个dex文件,
可以通过反射Elemet数组。把插件的element 复制到宿主中,实现插件化,动态加载类。

反射CODE

class TestInvoke {

    fun loadClass(context: Context, apkPath: String) {
        val baseDexClassLoaderClass = Class.forName("dalvik.system.BaseDexClassLoader")
        val pathListField = baseDexClassLoaderClass.getDeclaredField("pathList")
        pathListField.isAccessible = true

        val dexPathListClass = Class.forName("dalvik.system.DexPathList")
        val dexElementsField = dexPathListClass.getDeclaredField("dexElements")
        dexElementsField.isAccessible = true

        //host
        val pathClassLoaderInstance = context.classLoader as PathClassLoader
        val pathListInstance = pathListField.get(pathClassLoaderInstance)
        val dexElementsInstance = dexElementsField.get(pathListInstance) as Array<*>

        //visitor
        val dexClassLoader =
            DexClassLoader(apkPath, context.cacheDir.absolutePath, null, context.classLoader)
        val visitorPathListInstance = pathListField.get(dexClassLoader)
        val visitorElementsInstance = dexElementsField.get(visitorPathListInstance) as Array<*>


        val newHostElementsInstance =
            java.lang.reflect.Array.newInstance(
                visitorElementsInstance.javaClass.componentType,
                dexElementsInstance.size + visitorElementsInstance.size
            ) as Array<*>
        System.arraycopy(
            dexElementsInstance,
            0,
            newHostElementsInstance,
            0,
            dexElementsInstance.size
        )
        System.arraycopy(
            visitorElementsInstance,
            0,
            newHostElementsInstance,
            dexElementsInstance.size,
            visitorElementsInstance.size
        )

        dexElementsField.set(pathListInstance, newHostElementsInstance)
    }

}

测试代码:

  val path = Environment.getExternalStorageDirectory().path + "/${Environment.DIRECTORY_DCIM}"
        val file = File(path, "11111.apk")

        TestInvoke().loadClass(this, file.absolutePath)

        val noClass = Class.forName("com.kalerm.extend.ClassA")
        val con = noClass.getDeclaredConstructor(String::class.java, String::class.java)
        con.isAccessible = true
        val noClassInstance= con.newInstance("11","13")
        val method=noClass.getDeclaredMethod("do1")
        method.isAccessible=true
        val rs= method.invoke(noClassInstance)

反射ClassA 代码

class ClassA
    (
    var value1: String? = null,
    var value2: String? = null
) {

    var value3:String?=null

    override fun toString(): String {
        return "$value1  $value2"
    }

    fun do1() {

        println("this is classa  do1  ${toString()}")
    }

    fun do2() {
        println("this is classa  do2")

    }


}

execute result:

image.png

你可能感兴趣的:(笔记 classloader)