美文网首页
笔记 classloader

笔记 classloader

作者: 细雨么么 | 来源:发表于2022-07-07 12:06 被阅读0次

    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<IOException> suppressedExceptions = new ArrayList<IOException>();
            // 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<File> 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<URL> 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.
         * <p>
         * 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<URL> getResources(String resName) throws IOException {
            return findResources(resName);
        }
    }
    

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

    BootClassLoader没加载成功,又会向下到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;
        }
    
     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<Throwable> 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

          本文链接:https://www.haomeiwen.com/subject/gvzubrtx.html