ClassLoader分析(Java && Android)

Java中的ClassLoader

java.lang.ClassLoader類的基本職責就是根據一個指定的類的名稱,找到或者生成其對應的字節代碼,而後從這些字節代碼中定義出一個 Java 類,即 java.lang.Class類的一個實例。除此以外,ClassLoader還負責加載 Java 應用所需的資源,如圖像文件和配置文件等,不管是 JVM 仍是 Dalvik 都是經過 ClassLoader 去加載所須要的類。html

對於任意一個類,都須要由他的類加載器和這個類自己一同肯定其在Java虛擬機的惟一性。比較兩個類是否相等,只有在這兩個類是由同一個類加載器加載的前提下才有意義,不然,即便兩個雷來源於同一個class文件,被同一個虛擬機加載,只要加載的ClassLoader不一樣,兩個類就一定不相等。java

在ClassLoader類中提供了幾個重要的方法:android

方法說明git

  • getParent(): 返回該類加載器的父類加載器。
  • loadClass(String name): 加載名稱爲 name的類,返回的結果是 java.lang.Class類的實例。
  • findClass(String name): 查找名稱爲 name的類,返回的結果是 java.lang.Class類的實例。
  • findLoadedClass(String name): 查找名稱爲 name的已經被加載過的類,返回的結果是 java.lang.Class類的實例。
  • defineClass(String name, byte[] b, int off, int len): 把字節數組 b中的內容轉換成 Java 類,返回的結果是 java.lang.Class類的實例。這個方法被聲明爲 final的。
  • resolveClass(Class c): 連接指定的 Java 類。

表示類名稱的 name參數的值是類的二進制名稱。須要注意的是內部類的表示,如 com.example.Sample$1 com.example.Sample$Inner等表示方式。算法

類加載器的結構

在Java中的類加載器主要有兩種類型的,一種是系統提供的,另一種則是由Java應用開發人員編寫的,系統主要提供的類加載器有三個:bootstrap

  • 引導類加載器(bootstrap class loader):它用來加載 Java 的核心庫,是用原生代碼來實現的,並不繼承自 java.lang.ClassLoader。
  • 擴展類加載器(extensions class loader):它用來加載 Java 的擴展庫。Java 虛擬機的實現會提供一個擴展庫目錄。該類加載器在此目錄裏面查找並加載 Java 類。
  • 系統類加載器(system class loader):它根據 Java 應用的類路徑(CLASSPATH)來加載 Java 類。通常來講,Java 應用的類都是由它來完成加載的。能夠經過 ClassLoader.getSystemClassLoader()來獲取它。

bootstrap class loader是全部的類加載器的父類,而後是extensions class loader,而後是system class loader,而後纔是開發人員自主編寫的ClassLoader,關係以下:api

每個類都有本身的ClassLoader,咱們能夠經過以下代碼來查看全部的類加載器的引用:數組

public static void main(String[] args) { 
        ClassLoader loader = Test.class.getClassLoader(); 
        while (loader != null) { 
            System.out.println(loader.toString()); 
            loader = loader.getParent(); 
        } 
    } 
	
結果:
sun.misc.Launcher$AppClassLoader@4554617c  -->SystemClassLoader
sun.misc.Launcher$ExtClassLoader@677327b6  -->ExtensionClassLoader

在結果中能夠看到兩個Loader的存在,AppClassLoader即system class loader,能夠經過 ClassLoader.getSystemClassLoader()來進行測試獲取是否爲系統類加載器。ExtClassLoader則是引導類classloader。從上面的結果能夠看出,並無獲取到ExtClassLoader的父Loader,緣由是Bootstrap Loader(啓動類加載器)是用C語言實現的,ExtClassLoader找不到一個肯定的返回父Loader的方式,因而就返回null。緩存

若是咱們調用ClassLoader來加載一個類,都會調用到loadClass(String)來進行加載,這裏就從這裏j簡單跟蹤一下ClassLoader的源碼:安全

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
        synchronized (getClassLoadingLock(name)) {
            // First, check if the class has already been loaded
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                long t0 = System.nanoTime();
                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.
                    long t1 = System.nanoTime();
                    c = findClass(name);

                    // this is the defining class loader; record the stats
                    sun.misc.PerfCounter.getParentDelegationTime().addTime(t1 - t0);
                    sun.misc.PerfCounter.getFindClassTime().addElapsedTimeFrom(t1);
                    sun.misc.PerfCounter.getFindClasses().increment();
                }
            }
            if (resolve) {
                resolveClass(c);
            }
            return c;
        }
    }

從上面的註釋均可以看明白loadClass()的做用:

  1. 調用findLoadedClass(name)來判斷該類是否已經加載過了,若是加載過了則返回Class。
  2. 若是findLoadedClass(name)等於false,則調用父類進行加載Class,直至找到對應的Class。
  3. 若是父類ClassLoader中也沒找到,則調用當前ClassLoader的findClass(name)來尋找Class。
  4. 上述過程若是找到了Class則返回,不然最終返回null。

從loadClass方法中能夠很明確的看出來ClassLoader的設計是__雙親委託模型__。,每一個ClassLoader實例都有一個父類加載器的引用(不是繼承的關係,是一個包含的關係),Bootstrap ClassLoader自己沒有父類加載器,但能夠用做其它ClassLoader實例的的父類加載器。當一個ClassLoader實例須要加載某個類時,它會試圖親自搜索某個類以前,先把這個任務委託給它的父類加載器,這個過程是由上至下依次檢查的,首先由最頂層的類加載器Bootstrap ClassLoader試圖加載,若是沒加載到,則把任務轉交給Extension ClassLoader試圖加載,若是也沒加載到,則轉交給App ClassLoader 進行加載,若是它也沒有加載獲得的話,則返回給委託的發起者,由它到指定的文件系統或網絡等URL中加載該類。若是它們都沒有加載到這個類時,則拋出ClassNotFoundException異常。不然將這個找到的類生成一個類的定義,並將它加載到內存當中,最後返回這個類在內存中的Class實例對象。

爲啥使用雙親委託模型

由於這樣能夠避免重複加載,當父親已經加載了該類的時候,就沒有必要子ClassLoader再加載一次。考慮到安全因素,咱們試想一下,若是不使用這種委託模式,那咱們就能夠隨時使用自定義的String來動態替代java核心api中定義的類型,這樣會存在很是大的安全隱患,而雙親委託的方式,就能夠避免這種狀況,由於String已經在啓動時就被引導類加載器(Bootstrcp ClassLoader)加載,因此用戶自定義的ClassLoader永遠也沒法加載一個本身寫的String,除非你改變JDK中ClassLoader搜索類的默認算法。

顯式裝載和隱式裝載

  1. 隱式裝載, 程序在運行過程當中當碰到經過new 等方式生成對象時,隱式調用類裝載器加載對應的類到jvm中。
  2. 顯式裝載, 經過class.forname()等方法,顯式加載須要的類。

關於Class.forName()和使用ClassLoader.loadClass()的區別

  • Class.forName(className)方法,其實調用的方法是Class.forName(className,true,classloader);注意看第2個boolean參數,它表示的意思,在loadClass後必須初始化。能夠查閱這篇文章,這裏類加執行初始化這一步驟會進行後,目標對象的static塊代碼執行,static參數也被初始化。

  • ClassLoader.loadClass(className)方法,其實他調用的方法是ClassLoader.loadClass(className,false);仍是注意看第2個 boolean參數,該參數表示目標對象被裝載後不進行鏈接,就天然不會去執行初始化的操做,這就意味這不會去執行該類靜態塊中間的內容。所以2者的區別就顯而易見了。

代碼操做

這裏只是作個簡單的Demo來測試,首先寫一個CLTest類:

package test;
public class CLTest{
 	public void sys(){
		System.out.println("hello classloader");
	}
}

使用javac命令生成一個對應的.class文件,並放在項目根目錄下。而後編寫本身的ClassLoader類:

package test;

import java.io.BufferedInputStream;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.IOException;

public class Test extends ClassLoader{
   @Override
   
protected Class<?> findClass(String name) {
   Class clazz=null;
  try {
    byte[] by=toByteArray("CLTest.class");
    clazz=defineClass(name,by,0,by.length);
    
} catch (IOException e) {
    // TODO Auto-generated catch block
    e.printStackTrace();
    System.out.println(e.toString());
}

    return clazz;
}
    public static void main(String[] args) {
      Test test=new Test();
      try {       
        Class clazz= test.loadClass("test.CLTest");
        if (clazz!=null) {
          clazz.getMethod("sys").invoke(clazz.newInstance(), null);
        }
    } catch (Exception e) {
       System.out.println(e.toString());
    }
    }
    public static byte[] toByteArray(String filename) throws IOException{  
        
        File f = new File(filename);  
        if(!f.exists()){  
            throw new FileNotFoundException(filename);  
        }  
  
        ByteArrayOutputStream bos = new ByteArrayOutputStream((int)f.length());  
        BufferedInputStream in = null;  
        try{  
            in = new BufferedInputStream(new FileInputStream(f));  
            int buf_size = 1024;  
            byte[] buffer = new byte[buf_size];  
            int len = 0;  
            while(-1 != (len = in.read(buffer,0,buf_size))){  
                bos.write(buffer,0,len);  
            }  
            return bos.toByteArray();  
        }catch (IOException e) {  
            e.printStackTrace();  
            throw e;  
        }finally{  
            try{  
                in.close();  
            }catch (IOException e) {  
                e.printStackTrace();  
            }  
            bos.close();  
        }  
    }  
      
}
//輸出結果:hello classloader

Android中的ClassLoader機制

再看下android中的classloader:

ClassLoader loader = DebugActivity.class.getClassLoader();
        Log.e("dsdads","system class loader="+      ClassLoader.getSystemClassLoader().getClass().getSimpleName()+"\n");
while (loader != null) {
            Log.e("dsdads", "loader="+loader.getClass().getSimpleName()+"\n");
            loader = loader.getParent();
        }
		
		
//輸出結果
system class loader=PathClassLoader
    loader=PathClassLoader
    loader=BootClassLoader

因爲android自己虛擬機不一樣,android自身實現了一套與java基本相同的加載機制,BootClassLoader是系統啓動時建立的,至關於引導類加載器,PathClassLoader則是系統類加載器。這兩個類的關係能夠在Android源碼中直接得以體現:

private static ClassLoader createSystemClassLoader() {
        String classPath = System.getProperty("java.class.path", ".");
        String librarySearchPath = System.getProperty("java.library.path", "");
        return new PathClassLoader(classPath, librarySearchPath, BootClassLoader.getInstance());
    }
	...
    public static synchronized BootClassLoader getInstance() {
        if (instance == null) {
            instance = new BootClassLoader();
        }

        return instance;
    }

BootClassLoader是PathClassLoader的parent。

查看ClassLoader對應的實現類有:

這裏面URLClassLoader又是SecureClassLoader的子類,用於加載jar包文件,在android上是不能使用的。DexClassLoader和PathClassLoader是BaseDexClassLoader子類,用於加載Dex文件。

先看下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;
    }

這裏能夠看到Android中的ClassLoader基本遵循了Java的模式,使用雙親委託模式進行處理文件,只不過區別在於resolve變量在Android中並無處理。咱們仍是直接看BaseDexClassLoader吧:

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

BaseDexClassLoader繼承自ClassLoader,主要實現了findClass()方法,首先查看構造方法:

  • 參數一:傳入dex對應的路徑(jar or jdk),若是是多個文件則以:做爲分隔符。
  • 參數二:優化dex後存儲odex的路徑,能夠爲null
  • 參數三:native lib的路徑
  • 參數四:父類ClassLoader

使用上述的參數去實例化DexPathList類。這裏在findClass()方法中能夠看到,主要調用DexPathList方法去得到Class對象,所以咱們轉向先研究一下DexPathList類。首先查看一下構造方法:

private final Element[] dexElements;

    /** List of native library path elements. */
private final Element[] nativeLibraryPathElements;

    /** List of application native library directories. */
private final List<File> nativeLibraryDirectories;

    /** List of system native library directories. */
private final List<File> systemNativeLibraryDirectories;

...
public DexPathList(ClassLoader definingContext, String dexPath,
          String libraryPath, File optimizedDirectory) {
		...
       this.definingContext = definingContext;

       ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
       // save dexPath for BaseDexClassLoader
       this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions);//構造dex的數組

       // 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 (libraryPath):
       //   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(libraryPath, false);
       this.systemNativeLibraryDirectories =
               splitPaths(System.getProperty("java.library.path"), true);
       List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
       allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);//構造native的List

       this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null,
                                                         suppressedExceptions);//構造native的Elements數組
		...
   }

這裏一步一步看,能夠知道首先生成dexElments,先從splitPaths()方法入手:

private static List<File> splitPaths(String searchPath, boolean directoriesOnly) {
      List<File> result = new ArrayList<>();

      if (searchPath != null) {
          for (String path : searchPath.split(File.pathSeparator)) {//File.pathSeparator在Linux系統上爲:
              if (directoriesOnly) {
                  try {
                      StructStat sb = Libcore.os.stat(path);
                      if (!S_ISDIR(sb.st_mode)) {
                          continue;
                        }
                    } catch (ErrnoException ignored) {
                        continue;
                    }
                }
                result.add(new File(path));
            }
        }

        return result;
    }
	
	  private static ArrayList<File> splitPaths(String path1, String path2,
            boolean wantDirectories) {
        ArrayList<File> result = new ArrayList<File>();

        splitAndAdd(path1, wantDirectories, result);
        splitAndAdd(path2, wantDirectories, result);
        return result;
    }
	
	private static void splitAndAdd(String searchPath, boolean directoriesOnly,
            ArrayList<File> 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) {
            }
        }
    }

上述方法不難看出只是把咱們傳入的dex文件記錄到List當中。返回繼續查看makePathElements()方法:

private static Element[] makePathElements(List<File> files, File optimizedDirectory,
                                              List<IOException> suppressedExceptions) {
        List<Element> elements = new ArrayList<>();
        /*
         * Open all files and load the (direct or contained) dex files
         * up front.
         */
        for (File file : files) {
            File zip = null;
            File dir = new File("");
            DexFile dex = null;
            String path = file.getPath();
            String name = file.getName();

            if (path.contains(zipSeparator)) {
                String split[] = path.split(zipSeparator, 2);
                zip = new File(split[0]);
                dir = new File(split[1]);
            } else if (file.isDirectory()) {
                // We support directories for looking up resources and native libraries.
                // Looking up resources in directories is useful for running libcore tests.
                elements.add(new Element(file, true, null, null));
            } else if (file.isFile()) {//step this
                if (name.endsWith(DEX_SUFFIX)) {//直接爲dex文件
                    // 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 {//爲jar或者apk文件
                    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(dir, false, zip, dex));
            }
        }

        return elements.toArray(new Element[elements.size()]);
    }

這裏將傳進來的file文件調用loadDexFile()加工成DexFile,這裏假設咱們傳進來的都是Dex文件,不是apk或者jar,因此只會走step this這一步的判斷,接下來查看loadDexFile()方法:

private static DexFile loadDexFile(File file, File optimizedDirectory)
           throws IOException {
       if (optimizedDirectory == null) {//optimizedDirectory傳入是否爲空
           return new DexFile(file);
       } else {
           String optimizedPath = optimizedPathFor(file, optimizedDirectory);
           return DexFile.loadDex(file.getPath(), optimizedPath, 0);
       }
   }

這裏能夠看到若是咱們在optimizedDirectory傳空值,則直接生成了一個DexFile,在DexFile的構造方法中執行了native方法,這裏就到這裏爲止,native作的操做猜測是保存對應的Dex以及解析出Class。

回到makePathElements()方法中,loadDexFile()方法執行完畢後,向List中添加Element節點,到此makePathElements()就完成了。

關於native的list這裏就不研究了,有興趣的查看源碼吧。

DexPathList的構造方法分析好了,而後繼續回到對應的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;
    }

能夠看到又回到了DexPathList中:

public Class findClass(String name, List<Throwable> 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;
    }
	//DexFile.class
 public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
       return defineClass(name, loader, mCookie, suppressed);
   }

   private static Class defineClass(String name, ClassLoader loader, Object cookie,
                                    List<Throwable> suppressed) {
       Class result = null;
       try {
           result = defineClassNative(name, loader, cookie);//調用native方法
       } catch (NoClassDefFoundError e) {
           if (suppressed != null) {
               suppressed.add(e);
           }
       } catch (ClassNotFoundException e) {
           if (suppressed != null) {
               suppressed.add(e);
           }
       }
       return result;
   }

這裏最終經過DexFile.defineClass()方法獲取到Class對象,native怎麼調用的咱們不關心,咱們能夠知道在DexPathList初始化的時候會在native記錄下dex中的Class,而後在調用BaseDexClassLoader.findClass()時候會從native取到對應的Class就行ok了。

研究完BaseDexClassLoader後,基本上PathClassLoader和DexClassLoader就差很少了,PathClassLoader繼承自BaseDexClassLoader,簡單實現了兩個構造方法:

/**
 * 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 {
    public PathClassLoader(String dexPath, ClassLoader parent) {
        super(dexPath, null, null, parent);
    }
    public PathClassLoader(String dexPath, String libraryPath,
            ClassLoader parent) {
        super(dexPath, null, libraryPath, parent);
    }
}

對於DexClassLoader而言,也實現了一個構造方法:

**
 * 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.
 *
 * <p>This class loader requires an application-private, writable directory to
 * cache optimized classes. Use {@code Context.getDir(String, int)} to create
 * such a directory: <pre>   {@code
 *   File dexOutputDir = context.getDir("dex", 0);
 * }</pre>
 *
 * <p><strong>Do not cache optimized classes on external storage.</strong>
 * External storage does not provide access controls necessary to protect your
 * application from code injection attacks.
 */
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

能夠看到兩個不一樣的ClassLoader惟一的區別在於傳入的第二個參數是否爲空,而上述分析BaseDexClassLoader也是假設optimizedDirectory的File==null的狀況,即分析了自己PathClassLoader的狀況,那看來仍是得繼續分析一下optimizedDirectory的File!=null的狀況:

//DexPathList.java
 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) {
        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();
    }
	
	   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);
    }

上述代碼能夠看到,經過optimizedDirectory將全部的dex保存到了咱們指定的optimizedDirectory目錄下,而後再生成一個DexFile。

整體來看來個ClassLoader的區別就在於去哪緩存咱們須要加載的dex文件,若是使用PathClassLoader則使用系統默認的文件夾,若是使用DexClassLoader則是使用咱們自定義的文件夾,DexClassLoader能夠指定本身的optimizedDirectory,因此它能夠加載外部的dex,由於這個dex會被複制到內部路徑的optimizedDirectory;而PathClassLoader沒有optimizedDirectory,因此它只能加載內部的dex,這些大都是存在系統中已經安裝過的apk裏面的。

代碼操做

首先寫個類:

package com.lin;

public class Heelo {

    public String getHello() {
        return "Hello Dex";
    }
}

把這個類打包成jar,在eclipse中可使用export->Java->JAR File來導出jar包,而後經過dx命令把jar轉換成dex,接下來就直接在代碼中獲取類便可:

private void testDex() {
        File file = new File(getPath() + File.separator + "sayhello_dex.jar");
        if (!file.exists()) {
            Log.e("dasd", "dasd");
        }
        DexClassLoader classLoader = new DexClassLoader(file.getAbsolutePath(), getCacheDir().getAbsolutePath(),
                null, getClassLoader());
        try {
            Class clazz = classLoader.loadClass("com.lin.Heelo");
           String text= (String) clazz.getMethod("getHello").invoke(clazz.newInstance(),null);
            Log.e("1111", text);

        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        } catch (NoSuchMethodException e) {
            e.printStackTrace();
        } catch (IllegalAccessException e) {
            e.printStackTrace();
        } catch (InstantiationException e) {
            e.printStackTrace();
        } catch (InvocationTargetException e) {
            e.printStackTrace();
        }
    }

//輸出結果:
 E/1111: Hello Dex

資料參考:

https://www.ibm.com/developerworks/cn/java/j-lo-classloader/index.html

https://jaeger.itscoder.com/android/2016/08/27/android-classloader.html

http://www.hollischuang.com/archives/199

http://www.importnew.com/21189.html

相關文章
相關標籤/搜索