Dalvik仮想マシンがクラスをロードするメカニズム


作者:郭孝星微博:郭孝星の新浪微博メールアドレス:[email protected]ブログ:http://blog.csdn.net/allenwells github:https://github.com/AllenWell
Androidのクラスロードメカニズムを紹介する前に、Javaのクラスロードメカニズムを理解する必要があります.
【Javaセキュリティ技術探索の道シリーズ:J 2 SEセキュリティアーキテクチャ】その5:類ローダ
Dalvik仮想マシンといえば、まずJava仮想マシンを思い浮かべるかもしれませんが、Java言語の発展に伴って、私たちもずっと接触しています.では、両者にはどんな違いがありますか.
  • Java仮想マシンスタックベース、Dalvik仮想マシンレジスタベース.
  • Java仮想マシンはJavaバイトコードを実行し、Java仮想マシンはDexバイトコードを実行します.

  • この記事では、Dalvik仮想マシンのクラスのロードメカニズムについて主に説明していますので、他の違いは展開されません.私の他の記事を参照してください.ここでは、Dalvik仮想マシンとJava仮想マシンのクラスのロードメカニズムの違いについて説明します.
    Dalvik仮想マシンは、他のJava仮想マシンと同様に、プログラムを実行するときにまず対応するクラスをメモリにロードする必要があります.Java標準の仮想マシンでは、クラスロードはclassファイルから読み込むことも、他の形式のバイナリストリームであることもできます.そのため、プログラム実行時にClassを手動でロードすることで、コードの動的ロード実行の目的を達成することがよくあります.
    しかし、Dalvik仮想マシンは標準的なJava仮想マシンではありません.そのため、クラス・ロード・メカニズムでは、Dalvik仮想マシンとJava仮想マシンには多くの違いがあります.たとえば、標準Java仮想マシンを使用する場合、ClassLoaderから継承されたクラス・ローダをカスタマイズすることがよくあります.次に、defineClassメソッドを使用して、1つのバイナリストリームからClassをロードします.しかし、これはDalvik仮想マシンでは通用しません.
    Dalvik仮想マシンクラスのロード構造
    Dalvik仮想マシンクラスのロードプロセスを下図に示します.
    Dalvik虚拟机加载类的机制_第1张图片
    1.1クラスローダ
    1.1.1システムクラスローダ
    例を挙げる
    Context.class.getClassLoader();

    上記のコードから得られた結果は,システムクラスのローダがBootClassLoaderであることを示している.
    ClassLoader.getSystemClassLoader().getParent();

    上記のコードは、システム・ローダの親ローダが
    1.1.2アプリケーション・ローダ
    例を挙げる
    getClassLoader();

    上記のコードの結果は、アプリケーションのローダがPathClassLoaderであることを示しています.
    getClassLoader().getParent();

    上記のコードから得られた結果は、アプリケーションの自宅で空腹の親ローダを起動していることがBootClassLoaderであることを示しています.
    二Dalvik仮想マシンクラスローダソース分析
    Androidのクラスローダには主に2つのPathClassLoaderとDexClassLoaderがあります.このうちPathClassLoaderはデフォルトのクラスローダです.次に、両者の違いとつながりについて説明します.
  • PathClassLoader:キャッシュされたDEXが存在するため、DEXまたは既にインストールされているAPKのロードをサポートします.
  • DexClassLoader:APK、DEX、JARのロードはサポートされていますが、SDカードからのロードも可能です.

  • DexClassLoaderとPathClassLoaderは、親の委任モデルに適合するクラス・ローダに属します(loadClassメソッドを再ロードしていないため).すなわち、クラスをロードする前に、自分と自分以上のクラスローダがこのクラスをロードしたかどうかを確認します.すでにロードされている場合は、ロードを繰り返すことなく、直接返します.
    PathClassLoaderまたはDexClassLoaderはBaseDexClassLoaderに継承され、BaseDexClassLoaderは魚ClassLoaderに継承されます.次に、各ローダのソースコード実装をクラスのロードフローで分析します.詳細なソースコードは下の付録に記載されています.
    クラスをロードするには、まずクラスローダインスタンスを初期化する必要があります.DexClassLoaderを例に挙げます.その構築方法は次のとおりです.
        public DexClassLoader(String dexPath, String optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(dexPath, new File(optimizedDirectory), libraryPath, parent);
        }

    この関数のパラメータの意味は次のとおりです.
  • String dexPath:APK、DEX、およびJARのパスをロードします.このクラスはAndroidダイナミックロードDEX/JARに使用できます.
  • String optimizedDirectory:DEXの出力パスです.
  • String libraryPath:DEXをロードするときに使用するlibライブラリです.libraryPathには、一般的に/vendor/libおよび/system/libが含まれます.
  • ClassLoader parent:DEXClassLoaderが指定した親ローダ
  • DexClassLoaderについては、そのコンストラクション関数のほかに、ソースコードの注釈には以下の3つの点が記載されています.
  • このクラスのローダがロードするファイルは.JArまたは.apkファイル、そしてこれ.JArまたは.apkにはclassesが含まれています.dexというエントリファイルは、主にインストールされていない実行可能なファイルを実行するために使用されます.
  • このクラス・ローダには、アプリケーションに属するプライベートなディレクトリが必要です.可能なディレクトリは、独自のキャッシュ最適化ディレクトリとして使用されます.実は、このディレクトリは、以下のように、このコンストラクション関数の2番目のパラメータとして使用されます.どのように実現するかについては、コメントにも答えが表示されています.
  • 上記第2の点で述べたキャッシュディレクトリを外部ストレージとしないでください.外部ストレージはコード注入の攻撃を受けやすいからです.

  • DexClassLoaderのコンストラクション関数では、DexClassLoaderのコンストラクション関数が親クラスのコンストラクション関数を呼び出して初期化されることがわかります.DexClassLoaderの親はBaseDexXClassLoaderです.引き続き、BaseDexClassLoaderのコンストラクション関数を見てみましょう.
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }

    BaseDexClassLoaderのコンストラクション関数を実行すると、親ClassLoaderのコンストラクションメソッドが呼び出されることがわかります.
        /**
         * Constructs a new instance of this class with the system class loader as
         * its parent.
         */
        protected ClassLoader() {
            this(getSystemClassLoader(), false);
        }
    
        /**
         * Constructs a new instance of this class with the specified class loader
         * as its parent.
         *
         * @param parentLoader
         *            The {@code ClassLoader} to use as the new class loader's
         *            parent.
         */
        protected ClassLoader(ClassLoader parentLoader) {
            this(parentLoader, false);
        }
    
        /*
         * constructor for the BootClassLoader which needs parent to be null.
         */
        ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
            if (parentLoader == null && !nullAllowed) {
                throw new NullPointerException("parentLoader == null && !nullAllowed");
            }
            parent = parentLoader;
        }

    ClassLoaderのコンストラクション関数のソースコードを使用すると、BaseDexClassLoaderのparentLoaderオブジェクトが階層的に渡され、parentオブジェクトに渡されます.parentオブジェクトはClassLoaderクラスのプライベート変数です.次のようになります.
      /**
         * The parent ClassLoader.
         */
        private ClassLoader parent;

    このステップが完了すると、BaseDexClassLoaderのコンストラクション関数は、DEX文関連リソースファイルを記述するエントリリストであるDexPathListオブジェクトを初期化します.
    ふろく
    付録一:【Lollipop 5.1.1_r 6】ClassLoaderソース
    /*
     * Licensed to the Apache Software Foundation (ASF) under one or more
     * contributor license agreements.  See the NOTICE file distributed with
     * this work for additional information regarding copyright ownership.
     * The ASF licenses this file to You 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.
     */
    /*
     * 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 java.lang;
    
    import dalvik.system.PathClassLoader;
    import java.io.IOException;
    import java.io.InputStream;
    import java.net.URL;
    import java.nio.ByteBuffer;
    import java.security.ProtectionDomain;
    import java.util.Collection;
    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.HashMap;
    import java.util.List;
    import java.util.Map;
    
    /**
     * Loads classes and resources from a repository. One or more class loaders are
     * installed at runtime. These are consulted whenever the runtime system needs a
     * specific class that is not yet available in-memory. Typically, class loaders
     * are grouped into a tree where child class loaders delegate all requests to
     * parent class loaders. Only if the parent class loader cannot satisfy the
     * request, the child class loader itself tries to handle it.
     * 

    * {@code ClassLoader} is an abstract class that implements the common * infrastructure required by all class loaders. Android provides several * concrete implementations of the class, with * {@link dalvik.system.PathClassLoader} being the one typically used. Other * applications may implement subclasses of {@code ClassLoader} to provide * special ways for loading classes. *

    * @see Class */
    public abstract class ClassLoader { /** * The 'System' ClassLoader - the one that is responsible for loading * classes from the classpath. It is not equal to the bootstrap class loader - * that one handles the built-in classes. * * Because of a potential class initialization race between ClassLoader and * java.lang.System, reproducible when using JDWP with "suspend=y", we defer * creation of the system class loader until first use. We use a static * inner class to get synchronization at init time without having to sync on * every access. * * @see #getSystemClassLoader() */ static private class SystemClassLoader { public static ClassLoader loader = ClassLoader.createSystemClassLoader(); } /** * The parent ClassLoader. */ private ClassLoader parent; /** * The packages known to the class loader. */ private Map packages = new HashMap(); /** * To avoid unloading individual classes, {@link java.lang.reflect.Proxy} * only generates one class for each set of interfaces. This maps sets of * interfaces to the proxy class that implements all of them. It is declared * here so that these generated classes can be unloaded with their class * loader. * * @hide */ public final Map>, Class>> proxyCache = new HashMap>, Class>>(); /** * Create the system class loader. Note this is NOT the bootstrap class * loader (which is managed by the VM). We use a null value for the parent * to indicate that the bootstrap loader is our parent. */ private static ClassLoader createSystemClassLoader() { String classPath = System.getProperty("java.class.path", "."); // String[] paths = classPath.split(":"); // URL[] urls = new URL[paths.length]; // for (int i = 0; i < paths.length; i++) { // try { // urls[i] = new URL("file://" + paths[i]); // } // catch (Exception ex) { // ex.printStackTrace(); // } // } // // return new java.net.URLClassLoader(urls, null); // TODO Make this a java.net.URLClassLoader once we have those? return new PathClassLoader(classPath, BootClassLoader.getInstance()); } /** * Returns the system class loader. This is the parent for new * {@code ClassLoader} instances and is typically the class loader used to * start the application. */ public static ClassLoader getSystemClassLoader() { return SystemClassLoader.loader; } /** * Finds the URL of the resource with the specified name. The system class * loader's resource lookup algorithm is used to find the resource. * * @return the {@code URL} object for the requested resource or {@code null} * if the resource can not be found. * @param resName * the name of the resource to find. * @see Class#getResource */ public static URL getSystemResource(String resName) { return SystemClassLoader.loader.getResource(resName); } /** * Returns an enumeration of URLs for the resource with the specified name. * The system class loader's resource lookup algorithm is used to find the * resource. * * @return an enumeration of {@code URL} objects containing the requested * resources. * @param resName * the name of the resource to find. * @throws IOException * if an I/O error occurs. */ public static Enumeration getSystemResources(String resName) throws IOException { return SystemClassLoader.loader.getResources(resName); } /** * Returns a stream for the resource with the specified name. The system * class loader's resource lookup algorithm is used to find the resource. * Basically, the contents of the java.class.path are searched in order, * looking for a path which matches the specified resource. * * @return a stream for the resource or {@code null}. * @param resName * the name of the resource to find. * @see Class#getResourceAsStream */ public static InputStream getSystemResourceAsStream(String resName) { return SystemClassLoader.loader.getResourceAsStream(resName); } /** * Constructs a new instance of this class with the system class loader as * its parent. */ protected ClassLoader() { this(getSystemClassLoader(), false); } /** * Constructs a new instance of this class with the specified class loader * as its parent. * * @param parentLoader * The {@code ClassLoader} to use as the new class loader's * parent. */ protected ClassLoader(ClassLoader parentLoader) { this(parentLoader, false); } /* * constructor for the BootClassLoader which needs parent to be null. */ ClassLoader(ClassLoader parentLoader, boolean nullAllowed) { if (parentLoader == null && !nullAllowed) { throw new NullPointerException("parentLoader == null && !nullAllowed"); } parent = parentLoader; } /** * Constructs a new class from an array of bytes containing a class * definition in class file format. * * @param classRep * the memory image of a class file. * @param offset * the offset into {@code classRep}. * @param length * the length of the class file. * @return the {@code Class} object created from the specified subset of * data in {@code classRep}. * @throws ClassFormatError * if {@code classRep} does not contain a valid class. * @throws IndexOutOfBoundsException * if {@code offset < 0}, {@code length < 0} or if * {@code offset + length} is greater than the length of * {@code classRep}. * @deprecated Use {@link #defineClass(String, byte[], int, int)} */ @Deprecated protected final Class> defineClass(byte[] classRep, int offset, int length) throws ClassFormatError { throw new UnsupportedOperationException("can't load this type of class file"); } /** * Constructs a new class from an array of bytes containing a class * definition in class file format. * * @param className * the expected name of the new class, may be {@code null} if not * known. * @param classRep * the memory image of a class file. * @param offset * the offset into {@code classRep}. * @param length * the length of the class file. * @return the {@code Class} object created from the specified subset of * data in {@code classRep}. * @throws ClassFormatError * if {@code classRep} does not contain a valid class. * @throws IndexOutOfBoundsException * if {@code offset < 0}, {@code length < 0} or if * {@code offset + length} is greater than the length of * {@code classRep}. */ protected final Class> defineClass(String className, byte[] classRep, int offset, int length) throws ClassFormatError { throw new UnsupportedOperationException("can't load this type of class file"); } /** * Constructs a new class from an array of bytes containing a class * definition in class file format and assigns the specified protection * domain to the new class. If the provided protection domain is * {@code null} then a default protection domain is assigned to the class. * * @param className * the expected name of the new class, may be {@code null} if not * known. * @param classRep * the memory image of a class file. * @param offset * the offset into {@code classRep}. * @param length * the length of the class file. * @param protectionDomain * the protection domain to assign to the loaded class, may be * {@code null}. * @return the {@code Class} object created from the specified subset of * data in {@code classRep}. * @throws ClassFormatError * if {@code classRep} does not contain a valid class. * @throws IndexOutOfBoundsException * if {@code offset < 0}, {@code length < 0} or if * {@code offset + length} is greater than the length of * {@code classRep}. * @throws NoClassDefFoundError * if {@code className} is not equal to the name of the class * contained in {@code classRep}. */ protected final Class> defineClass(String className, byte[] classRep, int offset, int length, ProtectionDomain protectionDomain) throws java.lang.ClassFormatError { throw new UnsupportedOperationException("can't load this type of class file"); } /** * Defines a new class with the specified name, byte code from the byte * buffer and the optional protection domain. If the provided protection * domain is {@code null} then a default protection domain is assigned to * the class. * * @param name * the expected name of the new class, may be {@code null} if not * known. * @param b * the byte buffer containing the byte code of the new class. * @param protectionDomain * the protection domain to assign to the loaded class, may be * {@code null}. * @return the {@code Class} object created from the data in {@code b}. * @throws ClassFormatError * if {@code b} does not contain a valid class. * @throws NoClassDefFoundError * if {@code className} is not equal to the name of the class * contained in {@code b}. */ protected final Class> defineClass(String name, ByteBuffer b, ProtectionDomain protectionDomain) throws ClassFormatError { byte[] temp = new byte[b.remaining()]; b.get(temp); return defineClass(name, temp, 0, temp.length, protectionDomain); } /** * Overridden by subclasses, throws a {@code ClassNotFoundException} by * default. This method is called by {@code loadClass} after the parent * {@code ClassLoader} has failed to find a loaded class of the same name. * * @param className * the name of the class to look for. * @return the {@code Class} object that is found. * @throws ClassNotFoundException * if the class cannot be found. */ protected Class> findClass(String className) throws ClassNotFoundException { throw new ClassNotFoundException(className); } /** * Returns the class with the specified name if it has already been loaded * by the VM or {@code null} if it has not yet been loaded. * * @param className * the name of the class to look for. * @return the {@code Class} object or {@code null} if the requested class * has not been loaded. */ protected final Class> findLoadedClass(String className) { ClassLoader loader; if (this == BootClassLoader.getInstance()) loader = null; else loader = this; return VMClassLoader.findLoadedClass(loader, className); } /** * Finds the class with the specified name, loading it using the system * class loader if necessary. * * @param className * the name of the class to look for. * @return the {@code Class} object with the requested {@code className}. * @throws ClassNotFoundException * if the class can not be found. */ protected final Class> findSystemClass(String className) throws ClassNotFoundException { return Class.forName(className, false, getSystemClassLoader()); } /** * Returns this class loader's parent. * * @return this class loader's parent or {@code null}. */ public final ClassLoader getParent() { return parent; } /** * Returns the URL of the resource with the specified name. This * implementation first tries to use the parent class loader to find the * resource; if this fails then {@link #findResource(String)} is called to * find the requested resource. * * @param resName * the name of the resource to find. * @return the {@code URL} object for the requested resource or {@code null} * if the resource can not be found * @see Class#getResource */ public URL getResource(String resName) { URL resource = parent.getResource(resName); if (resource == null) { resource = findResource(resName); } return resource; } /** * Returns an enumeration of URLs for the resource with the specified name. * This implementation first uses this class loader's parent to find the * resource, then it calls {@link #findResources(String)} to get additional * URLs. The returned enumeration contains the {@code URL} objects of both * find operations. * * @return an enumeration of {@code URL} objects for the requested resource. * @param resName * the name of the resource to find. * @throws IOException * if an I/O error occurs. */ @SuppressWarnings("unchecked") public Enumeration getResources(String resName) throws IOException { Enumeration first = parent.getResources(resName); Enumeration second = findResources(resName); return new TwoEnumerationsInOne(first, second); } /** * Returns a stream for the resource with the specified name. See * {@link #getResource(String)} for a description of the lookup algorithm * used to find the resource. * * @return a stream for the resource or {@code null} if the resource can not be found * @param resName * the name of the resource to find. * @see Class#getResourceAsStream */ public InputStream getResourceAsStream(String resName) { try { URL url = getResource(resName); if (url != null) { return url.openStream(); } } catch (IOException ex) { // Don't want to see the exception. } return null; } /** * Loads the class with the specified name. Invoking this method is * equivalent to calling {@code loadClass(className, false)}. *

    * Note: In the Android reference implementation, the * second parameter of {@link #loadClass(String, boolean)} is ignored * anyway. *

    * * @return the {@code Class} object. * @param className * the name of the class to look for. * @throws ClassNotFoundException * if the class can not be found. */
    public Class> loadClass(String className) throws ClassNotFoundException { return loadClass(className, false); } /** * Loads the class with the specified name, optionally linking it after * loading. The following steps are performed: *
      *
    1. Call {@link #findLoadedClass(String)} to determine if the requested * class has already been loaded.
    2. *
    3. If the class has not yet been loaded: Invoke this method on the * parent class loader.
    4. *
    5. If the class has still not been loaded: Call * {@link #findClass(String)} to find the class.
    6. *
    *

    * Note: In the Android reference implementation, the * {@code resolve} parameter is ignored; classes are never linked. *

    * * @return the {@code Class} object. * @param className * the name of the class to look for. * @param resolve * Indicates if the class should be resolved after loading. This * parameter is ignored on the Android reference implementation; * classes are not resolved. * @throws ClassNotFoundException * if the class can not be found. */
    protected Class> loadClass(String className, boolean resolve) throws ClassNotFoundException { Class> clazz = findLoadedClass(className); if (clazz == null) { ClassNotFoundException suppressed = null; try { clazz = parent.loadClass(className, false); } catch (ClassNotFoundException e) { suppressed = e; } if (clazz == null) { try { clazz = findClass(className); } catch (ClassNotFoundException e) { e.addSuppressed(suppressed); throw e; } } } return clazz; } /** * Forces a class to be linked (initialized). If the class has already been * linked this operation has no effect. *

    * Note: In the Android reference implementation, this * method has no effect. *

    * * @param clazz * the class to link. */
    protected final void resolveClass(Class> clazz) { // no-op, doesn't make sense on android. } /** * Finds the URL of the resource with the specified name. This * implementation just returns {@code null}; it should be overridden in * subclasses. * * @param resName * the name of the resource to find. * @return the {@code URL} object for the requested resource. */ protected URL findResource(String resName) { return null; } /** * Finds an enumeration of URLs for the resource with the specified name. * This implementation just returns an empty {@code Enumeration}; it should * be overridden in subclasses. * * @param resName * the name of the resource to find. * @return an enumeration of {@code URL} objects for the requested resource. * @throws IOException * if an I/O error occurs. */ @SuppressWarnings( { "unchecked", "unused" }) protected Enumeration findResources(String resName) throws IOException { return Collections.emptyEnumeration(); } /** * Returns the absolute path of the native library with the specified name, * or {@code null}. If this method returns {@code null} then the virtual * machine searches the directories specified by the system property * "java.library.path". *

    * This implementation always returns {@code null}. *

    * * @param libName * the name of the library to find. * @return the absolute path of the library. */
    protected String findLibrary(String libName) { return null; } /** * Returns the package with the specified name. Package information is * searched in this class loader. * * @param name * the name of the package to find. * @return the package with the requested name; {@code null} if the package * can not be found. */ protected Package getPackage(String name) { synchronized (packages) { return packages.get(name); } } /** * Returns all the packages known to this class loader. * * @return an array with all packages known to this class loader. */ protected Package[] getPackages() { synchronized (packages) { Collection col = packages.values(); Package[] result = new Package[col.size()]; col.toArray(result); return result; } } /** * Defines and returns a new {@code Package} using the specified * information. If {@code sealBase} is {@code null}, the package is left * unsealed. Otherwise, the package is sealed using this URL. * * @param name * the name of the package. * @param specTitle * the title of the specification. * @param specVersion * the version of the specification. * @param specVendor * the vendor of the specification. * @param implTitle * the implementation title. * @param implVersion * the implementation version. * @param implVendor * the specification vendor. * @param sealBase * the URL used to seal this package or {@code null} to leave the * package unsealed. * @return the {@code Package} object that has been created. * @throws IllegalArgumentException * if a package with the specified name already exists. */ protected Package definePackage(String name, String specTitle, String specVersion, String specVendor, String implTitle, String implVersion, String implVendor, URL sealBase) throws IllegalArgumentException { synchronized (packages) { if (packages.containsKey(name)) { throw new IllegalArgumentException("Package " + name + " already defined"); } Package newPackage = new Package(name, specTitle, specVersion, specVendor, implTitle, implVersion, implVendor, sealBase); packages.put(name, newPackage); return newPackage; } } /** * Sets the signers of the specified class. This implementation does * nothing. * * @param c * the {@code Class} object for which to set the signers. * @param signers * the signers for {@code c}. */ protected final void setSigners(Class> c, Object[] signers) { } /** * Sets the assertion status of the class with the specified name. *

    * Note: This method does nothing in the Android reference * implementation. *

    * * @param cname * the name of the class for which to set the assertion status. * @param enable * the new assertion status. */
    public void setClassAssertionStatus(String cname, boolean enable) { } /** * Sets the assertion status of the package with the specified name. *

    * Note: This method does nothing in the Android reference * implementation. *

    * * @param pname * the name of the package for which to set the assertion status. * @param enable * the new assertion status. */
    public void setPackageAssertionStatus(String pname, boolean enable) { } /** * Sets the default assertion status for this class loader. *

    * Note: This method does nothing in the Android reference * implementation. *

    * * @param enable * the new assertion status. */
    public void setDefaultAssertionStatus(boolean enable) { } /** * Sets the default assertion status for this class loader to {@code false} * and removes any package default and class assertion status settings. *

    * Note: This method does nothing in the Android reference * implementation. *

    */
    public void clearAssertionStatus() { } } /* * Provides a helper class that combines two existing URL enumerations into one. * It is required for the getResources() methods. Items are fetched from the * first enumeration until it's empty, then from the second one. */ class TwoEnumerationsInOne implements Enumeration { private final Enumeration first; private final Enumeration second; public TwoEnumerationsInOne(Enumeration first, Enumeration second) { this.first = first; this.second = second; } @Override public boolean hasMoreElements() { return first.hasMoreElements() || second.hasMoreElements(); } @Override public URL nextElement() { if (first.hasMoreElements()) { return first.nextElement(); } else { return second.nextElement(); } } } /** * Provides an explicit representation of the boot class loader. It sits at the * head of the class loader chain and delegates requests to the VM's internal * class loading mechanism. */ 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, true); } @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); } } /** * TODO Open issues - Missing / empty methods - Signer stuff - Protection * domains - Assertions */

    付録二:【Lollipop 5.1.1_r 6】BaseDexClassLoaderソースコード
    /*
     * Copyright (C) 2011 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.net.URL;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    
    /**
     * Base class for common functionality between various dex-based
     * {@link ClassLoader} implementations.
     */
    public class BaseDexClassLoader extends ClassLoader {
        private final DexPathList pathList;
    
        /**
         * Constructs an instance.
         *
         * @param dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android
         * @param optimizedDirectory directory where optimized dex files
         * should be written; may be {@code null}
         * @param libraryPath the list of directories containing native
         * libraries, delimited by {@code File.pathSeparator}; may be
         * {@code null}
         * @param parent the parent class loader
         */
        public BaseDexClassLoader(String dexPath, File optimizedDirectory,
                String libraryPath, ClassLoader parent) {
            super(parent);
            this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
        }
    
        @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;
        }
    
        @Override
        protected URL findResource(String name) {
            return pathList.findResource(name);
        }
    
        @Override
        protected Enumeration findResources(String name) {
            return pathList.findResources(name);
        }
    
        @Override
        public String findLibrary(String name) {
            return pathList.findLibrary(name);
        }
    
        /**
         * Returns package information for the given package.
         * Unfortunately, instances of this class don't really have this
         * information, and as a non-secure {@code 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 {@code Package} object the first time it is being requested
         * and fill most of the fields with dummy values. The {@code
         * Package} object is then put into the {@code ClassLoader}'s
         * package cache, so we see the same one next time. We don't
         * create {@code Package} objects for {@code null} arguments or
         * for the default package.
         *
         * 

    There is a limited chance that we end up with multiple * {@code Package} objects representing the same package: It can * happen when when a package is scattered across different JAR * files which were loaded by different {@code ClassLoader} * instances. This is rather unlikely, and given that this whole * thing is more or less a workaround, probably not worth the * effort to address. * * @param name the name of the class * @return the package information for the class, or {@code null} * if there is no package information available for it */

    @Override protected synchronized Package getPackage(String name) { if (name != null && !name.isEmpty()) { Package pack = super.getPackage(name); if (pack == null) { pack = definePackage(name, "Unknown", "0.0", "Unknown", "Unknown", "0.0", "Unknown", null); } return pack; } return null; } /** * @hide */ public String getLdLibraryPath() { StringBuilder result = new StringBuilder(); for (File directory : pathList.getNativeLibraryDirectories()) { if (result.length() > 0) { result.append(':'); } result.append(directory); } return result.toString(); } @Override public String toString() { return getClass().getName() + "[" + pathList + "]"; } }

    付録3:【Lollipop 5.1.1_r 6】PathClassLoaderソース
    /*
     * 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;
    
    /**
     * 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 BaseDexClassLoader {
        /**
         * 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 dexPath the list of jar/apk files containing classes and
         * resources, delimited by {@code File.pathSeparator}, which
         * defaults to {@code ":"} on Android
         * @param parent the parent class loader
         */
        public PathClassLoader(String dexPath, ClassLoader parent) {
            super(dexPath, null, 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:
         *
         * 
      *
    • JAR/ZIP/APK files, possibly containing a "classes.dex" file as * well as arbitrary resources. *
    • Raw ".dex" files (not inside a zip file). *
    * * The entries of the second list should be directories containing * native library files. * * @param dexPath the list of jar/apk files containing classes and * resources, delimited by {@code File.pathSeparator}, which * defaults to {@code ":"} on Android * @param libraryPath the list of directories containing native * libraries, delimited by {@code File.pathSeparator}; may be * {@code null} * @param parent the parent class loader */ public PathClassLoader(String dexPath, String libraryPath, ClassLoader parent) { super(dexPath, null, libraryPath, parent); } }

    付録四:【Lollipop 5.1.1_r 6】DexClassLoaderソース
    /*
     * 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;
    
    /**
     * A class loader that loads classes from {@code .jar} and {@code .apk} files
     * containing a {@code classes.dex} entry. This can be used to execute code not
     * installed as part of an application.
     *
     * 

    This class loader requires an application-private, writable directory to * cache optimized classes. Use {@code Context.getCodeCacheDir()} to create * such a directory:

       {@code
     *   File dexOutputDir = context.getCodeCacheDir();
     * }
    *
    * Do not cache optimized classes on external storage.
    * External storage does not provide access controls necessary to protect your
    * application from code injection attacks.
    */
    public class DexClassLoader extends BaseDexClassLoader {
    /**
    * 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
    * {@code path.separator} system property, which defaults to {@code :}.
    *
    * @param dexPath the list of jar/apk files containing classes and
    * resources, delimited by {@code File.pathSeparator}, which
    * defaults to {@code ":"} on Android
    * @param optimizedDirectory directory where optimized dex files
    * should be written; must not be {@code null}
    * @param libraryPath the list of directories containing native
    * libraries, delimited by {@code File.pathSeparator}; may be
    * {@code null}
    * @param parent the parent class loader
    */
    public DexClassLoader(String dexPath, String optimizedDirectory,
    String libraryPath, ClassLoader parent) {
    super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
    }
    :【Lollipop 5.1.1_r 6】DexFileソースコード
    /*
     * 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 android.system.ErrnoException;
    import android.system.StructStat;
    import java.io.File;
    import java.io.FileNotFoundException;
    import java.io.IOException;
    import java.util.ArrayList;
    import java.util.Enumeration;
    import java.util.List;
    import libcore.io.Libcore;
    
    /**
     * Manipulates DEX files. The class is similar in principle to
     * {@link java.util.zip.ZipFile}. It is used primarily by class loaders.
     * 

    * Note we don't directly open and read the DEX file here. They're memory-mapped * read-only by the VM. */

    public final class DexFile { private long mCookie; private final String mFileName; private final CloseGuard guard = CloseGuard.get(); /** * Opens a DEX file from a given File object. This will usually be a ZIP/JAR * file with a "classes.dex" inside. * * The VM will generate the name of the corresponding file in * /data/dalvik-cache and open it, possibly creating or updating * it first if system permissions allow. Don't pass in the name of * a file in /data/dalvik-cache, as the named file is expected to be * in its original (pre-dexopt) state. * * @param file * the File object referencing the actual DEX file * * @throws IOException * if an I/O error occurs, such as the file not being found or * access rights missing for opening it */ public DexFile(File file) throws IOException { this(file.getPath()); } /** * Opens a DEX file from a given filename. This will usually be a ZIP/JAR * file with a "classes.dex" inside. * * The VM will generate the name of the corresponding file in * /data/dalvik-cache and open it, possibly creating or updating * it first if system permissions allow. Don't pass in the name of * a file in /data/dalvik-cache, as the named file is expected to be * in its original (pre-dexopt) state. * * @param fileName * the filename of the DEX file * * @throws IOException * if an I/O error occurs, such as the file not being found or * access rights missing for opening it */ public DexFile(String fileName) throws IOException { mCookie = openDexFile(fileName, null, 0); mFileName = fileName; guard.open("close"); //System.out.println("DEX FILE cookie is " + mCookie + " fileName=" + fileName); } /** * 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". * @param outputName * File that will hold the optimized form of the DEX data. * @param flags * Enable optional features. */ 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"); //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); } /** * Open a DEX file, specifying the file in which the optimized DEX * data should be written. If the optimized form exists and appears * to be current, it will be used; if not, the VM will attempt to * regenerate it. * * This is intended for use by applications that wish to download * and execute DEX files outside the usual application installation * mechanism. This function should not be called directly by an * application; instead, use a class loader such as * dalvik.system.DexClassLoader. * * @param sourcePathName * Jar or APK file with "classes.dex". (May expand this to include * "raw DEX" in the future.) * @param outputPathName * File that will hold the optimized form of the DEX data. * @param flags * Enable optional features. (Currently none defined.) * @return * A new or previously-opened DexFile. * @throws IOException * If unable to open the source or output file. */ static public DexFile loadDex(String sourcePathName, String outputPathName, int flags) throws IOException { /* * TODO: we may want to cache previously-opened DexFile objects. * The cache would be synchronized with close(). This would help * us avoid mapping the same DEX more than once when an app * decided to open it multiple times. In practice this may not * be a real issue. */ return new DexFile(sourcePathName, outputPathName, flags); } /** * Gets the name of the (already opened) DEX file. * * @return the file name */ public String getName() { return mFileName; } @Override public String toString() { return getName(); } /** * Closes the DEX file. *

    * This may not be able to release any resources. If classes from this * DEX file are still resident, the DEX file can't be unmapped. * * @throws IOException * if an I/O error occurs during closing the file, which * normally should not happen */

    public void close() throws IOException { if (mCookie != 0) { guard.close(); closeDexFile(mCookie); mCookie = 0; } } /** * Loads a class. Returns the class on success, or a {@code null} reference * on failure. *

    * If you are not calling this from a class loader, this is most likely not * going to do what you want. Use {@link Class#forName(String)} instead. *

    * The method does not throw {@link ClassNotFoundException} if the class * isn't found because it isn't reasonable to throw exceptions wildly every * time a class is not found in the first DEX file we look at. * * @param name * the class name, which should look like "java/lang/String" * * @param loader * the class loader that tries to load the class (in most cases * the caller of the method * * @return the {@link Class} object representing the class, or {@code null} * if the class cannot be loaded */

    public Class loadClass(String name, ClassLoader loader) { String slashName = name.replace('.', '/'); return loadClassBinaryName(slashName, loader, null); } /** * See {@link #loadClass(String, ClassLoader)}. * * This takes a "binary" class name to better match ClassLoader semantics. * * @hide */ public Class loadClassBinaryName(String name, ClassLoader loader, List suppressed) { return defineClass(name, loader, mCookie, suppressed); } private static Class defineClass(String name, ClassLoader loader, long cookie, List suppressed) { Class result = null; try { result = defineClassNative(name, loader, cookie); } catch (NoClassDefFoundError e) { if (suppressed != null) { suppressed.add(e); } } catch (ClassNotFoundException e) { if (suppressed != null) { suppressed.add(e); } } return result; } /** * Enumerate the names of the classes in this DEX file. * * @return an enumeration of names of classes contained in the DEX file, in * the usual internal form (like "java/lang/String"). */ public Enumeration entries() { return new DFEnum(this); } /* * Helper class. */ private class DFEnum implements Enumeration<String> { private int mIndex; private String[] mNameList; DFEnum(DexFile df) { mIndex = 0; mNameList = getClassNameList(mCookie); } public boolean hasMoreElements() { return (mIndex < mNameList.length); } public String nextElement() { return mNameList[mIndex++]; } } /** * Called when the class is finalized. Makes sure the DEX file is closed. * * @throws IOException * if an I/O error occurs during closing the file, which * normally should not happen */ @Override protected void finalize() throws Throwable { try { if (guard != null) { guard.warnIfOpen(); } close(); } finally { super.finalize(); } } /* * Open a DEX file. The value returned is a magic VM cookie. On * failure, an IOException is thrown. */ private static long openDexFile(String sourceName, String outputName, int flags) throws IOException { // Use absolute paths to enable the use of relative paths when testing on host. return openDexFileNative(new File(sourceName).getAbsolutePath(), (outputName == null) ? null : new File(outputName).getAbsolutePath(), flags); } private static native void closeDexFile(long cookie); private static native Class defineClassNative(String name, ClassLoader loader, long cookie) throws ClassNotFoundException, NoClassDefFoundError; private static native String[] getClassNameList(long cookie); /* * Open a DEX file. The value returned is a magic VM cookie. On * failure, an IOException is thrown. */ private static native long openDexFileNative(String sourceName, String outputName, int flags); /** * Returns true if the VM believes that the apk/jar file is out of date * and should be passed through "dexopt" again. * * @param fileName the absolute path to the apk/jar file to examine. * @return true if dexopt should be called on the file, false otherwise. * @throws java.io.FileNotFoundException if fileName is not readable, * not a file, or not present. * @throws java.io.IOException if fileName is not a valid apk/jar file or * if problems occur while parsing it. * @throws java.lang.NullPointerException if fileName is null. * @throws dalvik.system.StaleDexCacheError if the optimized dex file * is stale but exists on a read-only partition. */ public static native boolean isDexOptNeeded(String fileName) throws FileNotFoundException, IOException; /** * See {@link #isDexOptNeededInternal(String, String, String, boolean)}. * * @hide */ public static final byte UP_TO_DATE = 0; /** * See {@link #isDexOptNeededInternal(String, String, String, boolean)}. * * @hide */ public static final byte PATCHOAT_NEEDED = 1; /** * See {@link #isDexOptNeededInternal(String, String, String, boolean)}. * * @hide */ public static final byte DEXOPT_NEEDED = 2; /** * Returns UP_TO_DATE if the VM believes that the apk/jar file * is up to date, PATCHOAT_NEEDED if it believes that the file is up * to date but it must be relocated to match the base address offset, * and DEXOPT_NEEDED if it believes that it is out of date and should * be passed through "dexopt" again. * * @param fileName the absolute path to the apk/jar file to examine. * @return DEXOPT_NEEDED if dexopt should be called on the file, * PATCHOAT_NEEDED if we need to run "patchoat" on it and * UP_TO_DATE otherwise. * @throws java.io.FileNotFoundException if fileName is not readable, * not a file, or not present. * @throws java.io.IOException if fileName is not a valid apk/jar file or * if problems occur while parsing it. * @throws java.lang.NullPointerException if fileName is null. * @throws dalvik.system.StaleDexCacheError if the optimized dex file * is stale but exists on a read-only partition. * * @hide */ public static native byte isDexOptNeededInternal(String fileName, String pkgname, String instructionSet, boolean defer) throws FileNotFoundException, IOException; }

    :【Lollipop 5.1.1_r 6】DexPathListソースコード
    /*
     * Copyright (C) 2011 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 android.system.ErrnoException;
    import android.system.StructStat;
    import java.io.File;
    import java.io.IOException;
    import java.net.MalformedURLException;
    import java.net.URL;
    import java.util.ArrayList;
    import java.util.Arrays;
    import java.util.Collections;
    import java.util.Enumeration;
    import java.util.List;
    import java.util.zip.ZipFile;
    import libcore.io.IoUtils;
    import libcore.io.Libcore;
    import static android.system.OsConstants.*;
    
    /**
     * A pair of lists of entries, associated with a {@code ClassLoader}.
     * One of the lists is a dex/resource path — typically referred
     * to as a "class path" — list, and the other names directories
     * containing native code libraries. Class path entries may be any of:
     * a {@code .jar} or {@code .zip} file containing an optional
     * top-level {@code classes.dex} file as well as arbitrary resources,
     * or a plain {@code .dex} file (with no possibility of associated
     * resources).
     *
     * 

    This class also contains methods to use these lists to look up * classes and resources.

    */
    /*package*/ final class DexPathList { private static final String DEX_SUFFIX = ".dex"; /** class definition context */ private final ClassLoader definingContext; /** * List of dex/resource (class path) elements. * Should be called pathElements, but the Facebook app uses reflection * to modify 'dexElements' (http://b/7726934). */ private final Element[] dexElements; /** List of native library directories. */ private final File[] nativeLibraryDirectories; /** * Exceptions thrown during creation of the dexElements list. */ private final IOException[] dexElementsSuppressedExceptions; /** * Constructs an instance. * * @param definingContext the context in which any as-yet unresolved * classes should be defined * @param dexPath list of dex/resource path elements, separated by * {@code File.pathSeparator} * @param libraryPath list of native library directory path elements, * separated by {@code File.pathSeparator} * @param optimizedDirectory directory where optimized {@code .dex} files * should be found and written to, or {@code null} to use the default * system directory for same */ 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 suppressedExceptions = new ArrayList(); 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); } @Override public String toString() { return "DexPathList[" + Arrays.toString(dexElements) + ",nativeLibraryDirectories=" + Arrays.toString(nativeLibraryDirectories) + "]"; } /** * For BaseDexClassLoader.getLdLibraryPath. */ public File[] getNativeLibraryDirectories() { return nativeLibraryDirectories; } /** * Splits the given dex path string into elements using the path * separator, pruning out any elements that do not refer to existing * and readable files. (That is, directories are not included in the * result.) */ private static ArrayList splitDexPath(String path) { return splitPaths(path, null, false); } /** * Splits the given library directory path string into elements * using the path separator ({@code File.pathSeparator}, which * defaults to {@code ":"} on Android, appending on the elements * from the system library path, and pruning out any elements that * do not refer to existing and readable directories. */ private static File[] splitLibraryPath(String path) { // 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 // 2. the VM's library path from the system property for system libraries // // This order was reversed prior to Gingerbread; see http://b/2933456. ArrayList result = splitPaths(path, System.getProperty("java.library.path"), true); return result.toArray(new File[result.size()]); } /** * Splits the given path strings into file elements using the path * separator, combining the results and filtering out elements * that don't exist, aren't readable, or aren't either a regular * file or a directory (as specified). Either string may be empty * or {@code null}, in which case it is ignored. If both strings * are empty or {@code null}, or all elements get pruned out, then * this returns a zero-element list. */ private static ArrayList splitPaths(String path1, String path2, boolean wantDirectories) { ArrayList result = new ArrayList(); splitAndAdd(path1, wantDirectories, result); splitAndAdd(path2, wantDirectories, result); return result; } /** * Helper for {@link #splitPaths}, which does the actual splitting * and filtering and adding to a result. */ private static void splitAndAdd(String searchPath, boolean directoriesOnly, ArrayList resultList) { if (searchPath == null) { return; } for (String path : searchPath.split(":")) { try { StructStat sb = Libcore.os.stat(path); if (!directoriesOnly || S_ISDIR(sb.st_mode)) { resultList.add(new File(path)); } } catch (ErrnoException ignored) { } } } /** * Makes an array of dex/resource path elements, one per element of * the given array. */ private static Element[] makeDexElements(ArrayList files, File optimizedDirectory, ArrayList suppressedExceptions) { ArrayList elements = new ArrayList(); /* * 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) { /* * IOException might get thrown "legitimately" by the DexFile constructor if * the zip file turns out to be resource-only (that is, no classes.dex file * in it). * Let dex == null and hang on to the exception to add to the tea-leaves for * when findClass returns null. */ 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()]); } /** * Constructs a {@code DexFile} instance, as appropriate depending * on whether {@code optimizedDirectory} is {@code null}. */ 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); } } /** * Converts a dex/jar file path and an output directory to an * output file path for an associated optimized dex file. */ private static String optimizedPathFor(File path, File optimizedDirectory) { /* * Get the filename component of the path, and replace the * suffix with ".dex" if that's not already the suffix. * * We don't want to use ".odex", because the build system uses * that for files that are paired with resource-only jar * files. If the VM can assume that there's no classes.dex in * the matching jar, it doesn't need to open the jar to check * for updated dependencies, providing a slight performance * boost at startup. The use of ".dex" here matches the use on * files in /data/dalvik-cache. */ 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(); } /** * Finds the named class in one of the dex files pointed at by * this instance. This will find the one in the earliest listed * path element. If the class is found but has not yet been * defined, then this method will define it in the defining * context that this instance was constructed with. * * @param name of class to find * @param suppressed exceptions encountered whilst finding the class * @return the named class or {@code null} if the class is not * found in any of the dex files */ public Class findClass(String name, List suppressed) { for (Element element : dexElements) { DexFile dex = element.dexFile; if (dex != null) { Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); if (clazz != null) { return clazz; } } } if (dexElementsSuppressedExceptions != null) { suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); } return null; } /** * Finds the named resource in one of the zip/jar files pointed at * by this instance. This will find the one in the earliest listed * path element. * * @return a URL to the named resource or {@code null} if the * resource is not found in any of the zip/jar files */ public URL findResource(String name) { for (Element element : dexElements) { URL url = element.findResource(name); if (url != null) { return url; } } return null; } /** * Finds all the resources with the given name, returning an * enumeration of them. If there are no resources with the given * name, then this method returns an empty enumeration. */ public Enumeration findResources(String name) { ArrayList result = new ArrayList(); for (Element element : dexElements) { URL url = element.findResource(name); if (url != null) { result.add(url); } } return Collections.enumeration(result); } /** * Finds the named native code library on any of the library * directories pointed at by this instance. This will find the * one in the earliest listed directory, ignoring any that are not * readable regular files. * * @return the complete path to the library or {@code null} if no * library was found */ public String findLibrary(String libraryName) { String fileName = System.mapLibraryName(libraryName); for (File directory : nativeLibraryDirectories) { String path = new File(directory, fileName).getPath(); if (IoUtils.canOpenReadOnly(path)) { return path; } } return null; } /** * Element of the dex/resource file path */ /*package*/ static class Element { private final File file; private final boolean isDirectory; private final File zip; private final DexFile dexFile; private ZipFile zipFile; private boolean initialized; public Element(File file, boolean isDirectory, File zip, DexFile dexFile) { this.file = file; this.isDirectory = isDirectory; this.zip = zip; this.dexFile = dexFile; } @Override public String toString() { if (isDirectory) { return "directory \"" + file + "\""; } else if (zip != null) { return "zip file \"" + zip + "\""; } else { return "dex file \"" + dexFile + "\""; } } public synchronized void maybeInit() { if (initialized) { return; } initialized = true; if (isDirectory || zip == null) { return; } try { zipFile = new ZipFile(zip); } catch (IOException ioe) { /* * Note: ZipException (a subclass of IOException) * might get thrown by the ZipFile constructor * (e.g. if the file isn't actually a zip/jar * file). */ System.logE("Unable to open zip file: " + file, ioe); zipFile = null; } } public URL findResource(String name) { maybeInit(); // We support directories so we can run tests and/or legacy code // that uses Class.getResource. if (isDirectory) { File resourceFile = new File(file, name); if (resourceFile.exists()) { try { return resourceFile.toURI().toURL(); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } } } if (zipFile == null || zipFile.getEntry(name) == null) { /* * Either this element has no zip/jar file (first * clause), or the zip/jar file doesn't have an entry * for the given name (second clause). */ return null; } try { /* * File.toURL() is compliant with RFC 1738 in * always creating absolute path names. If we * construct the URL by concatenating strings, we * might end up with illegal URLs for relative * names. */ return new URL("jar:" + file.toURL() + "!/" + name); } catch (MalformedURLException ex) { throw new RuntimeException(ex); } } } }