自拍偷在线精品自拍偷,亚洲欧美中文日韩v在线观看不卡

Android動態(tài)加載之ClassLoader加載和插件熱修復(fù)的機制原理詳解

移動開發(fā) Android
ClassLoader類加載,是動態(tài)加載機制及現(xiàn)在火熱的插件化機制中很基礎(chǔ)但同時又很重要的知識點;今天我們就來講解下.

[[430663]]

前言

深入理解Android中的類加載器

ClassLoader類加載,是動態(tài)加載機制及現(xiàn)在火熱的插件化機制中很基礎(chǔ)但同時又很重要的知識點;

今天我們就來講解下

一、ClassLoader介紹

1、Android中的ClassLoader

  • Java中的 ClassLoader可以加載 jar 文件和Class文件(本質(zhì)時加載Class文件)。在Android中,它們加載到是dex文件;
  • Android中的ClassLoader類型分別是系統(tǒng)類加載器和自定義加載器。其中系統(tǒng)類加載器主要包括3種,分別是 BootClassLoader 、PathClassLoader 和 DexClassLoader;
  • BootClassLoader: Dalvik/ART虛擬機用于加載Android系統(tǒng)類的Loader,應(yīng)用層通過獲取父ClassLoader的最終項;
  • PathClassLoader: 我們知道,打包APK后實際上是把java文件都生成dex文件,而這個Loader就是在應(yīng)用啟動時,加載已安裝APK的dex文件;
  • DexClassLoader: 常見的動態(tài)加載機制都用這個類,傳入指定路徑加載指定dex文件;
  • PathClassLoader和DexClasLoader都是繼承自 dalviksystem.BaseDexClassLoader,它們的類加載邏輯全部寫在BaseDexClassLoader中;

 

 

2、加載原理

ClassLoader使用的是雙親委托機制。雙親委派模型,旨在于讓頂級父類加載器先加載類,若不成功,則一層層往下加載,最終到當(dāng)前加載器。這樣做的目的是保持類加載系統(tǒng)的穩(wěn)定性,不會出現(xiàn)不同加載器加載同一個類時,出現(xiàn)多個類實例;

 

  1. protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { 
  2.  
  3.         Class<?> clazz = findLoadedClass(className); 
  4.         if (clazz == null) { 
  5.             ClassNotFoundException suppressed = null
  6.             try { 
  7.                 clazz = parent.loadClass(className, false); 
  8.             } catch (ClassNotFoundException e) { 
  9.                 suppressed = e; 
  10.             } 
  11.             if (clazz == null) { 
  12.                 try { 
  13.                     clazz = findClass(className); 
  14.                 } catch (ClassNotFoundException e) { 
  15.                     e.addSuppressed(suppressed); 
  16.                     throw e; 
  17.                 } 
  18.             } 
  19.         } 
  20.         return clazz; 
  21.     } 
  • 會先查詢當(dāng)前ClassLoader實例是否加載過此類,有就返回;
  • 如果沒有。查詢Parent是否已經(jīng)加載過此類,如果已經(jīng)加載過,就直接返回Parent加載的類;
  • 如果繼承路線上的ClassLoader都沒有加載,才由Child執(zhí)行類的加載工作;
  • 這樣做有個明顯的特點,如果一個類被位于樹根的ClassLoader加載過,那么在以后整個系統(tǒng)的生命周期內(nèi),
  • 這個類永遠不會被重新加載;
  • 如果希望通過動態(tài)加載的方式,加載一個新版本的dex文件,使用里面的新類替換原有的舊類,從而修復(fù)原有類的BUG,那么必須保證在加載新類的時候,舊類還沒有被加載,因為如果已經(jīng)加載過舊類,那么ClassLoader會一直優(yōu)先使用舊類;

二、ClassLoader源碼分析

1、PathClassLoader

Android主要關(guān)心的是PathClassLoader和DexClassLoader;

PathClassLoader用來操作本地文件系統(tǒng)中的文件和目錄的集合。并不會加載來源于網(wǎng)絡(luò)中的類。Android采用這個類加載器一般是用于加載系統(tǒng)類和它自己的應(yīng)用類。這個應(yīng)用類放置在data/data/包名下;

看一下PathClassLoader的源碼,只有2個構(gòu)造方法:

 

  1. package dalvik.system; 
  2. public class PathClassLoader extends BaseDexClassLoader { 
  3.     public PathClassLoader(String dexPath, ClassLoader parent) { 
  4.         super(dexPath, nullnull, parent); 
  5.     } 
  6.     public PathClassLoader(String dexPath, String libraryPath, 
  7.             ClassLoader parent) { 
  8.         super(dexPath, null, libraryPath, parent); 
  9.     } 

2、DexClassLoader

  • DexClassLoader可以加載一個未安裝的APK,也可以加載其它包含dex文件的JAR/ZIP類型的文件。DexClassLoader需要一個對應(yīng)用私有且可讀寫的文件夾來緩存優(yōu)化后的class文件;
  • 而且一定要注意不要把優(yōu)化后的文件存放到外部存儲上,避免使自己的應(yīng)用遭受代碼注入攻擊;

 

  1. package dalvik.system; 
  2. import java.io.File; 
  3. public class DexClassLoader extends BaseDexClassLoader { 
  4.     public DexClassLoader(String dexPath, String optimizedDirectory, 
  5.             String libraryPath, ClassLoader parent) { 
  6.         super(dexPath, new File(optimizedDirectory), libraryPath, parent); 
  7.     } 
  • PathClassLoader和DexClassLoader除了構(gòu)造方法傳參不同,其它的邏輯都是一樣的;
  • 要注意的是DexClassLoader構(gòu)造方法第2個參數(shù)指的是dex優(yōu)化緩存路徑,這個值是不能為空的;
  • 而PathClassLoader對應(yīng)的dex優(yōu)化緩存路徑為null是因為Android系統(tǒng)自己決定了緩存路徑;
  • Android中具體負責(zé)類加載的并不是哪個ClassLoader,而是通過DexFile的defineClassNative()方法來加載的;

3、BaseDexClassLoader

接下來我們看一下BaseDexClassLoader這個類:

BaseDexClassLoader的構(gòu)造方法有四個參數(shù):

  • dexPath,指的是在Androdi包含類和資源的jar/apk類型的文件集合,指的是包含dex文件。多個文件用“:”分隔開,用代碼就是File.pathSeparator;
  • optimizedDirectory,指的是odex優(yōu)化文件存放的路徑,可以為null,那么就采用默認的系統(tǒng)路徑;
  • libraryPath,指的是native庫文件存放目錄,也是以“:”分隔;
  • parent,parent類加載器;可以看到,在BaseDexClassLoader類中初始化了DexPathList這個類的對象。這個類的作用是存放指明包含dex文件、native庫和優(yōu)化目錄;

 

  1. # dalvik.system.BaseDexClassLoader 
  2.     public BaseDexClassLoader(String dexPath, File optimizedDirectory, 
  3.             String libraryPath, ClassLoader parent) { 
  4.         super(parent); 
  5.         this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory); 
  6.     } 

4、DexPathList

  • dalvik.system.DexPathList封裝了dex路徑,是一個final類,而且訪問權(quán)限是包權(quán)限,也就是說外界不可繼承,也不可訪問這個類;
  • BaseDexClassLoader在其構(gòu)造方法中初始化了DexPathList對象,我們來看一下DexPathList的源碼,我們需要重點關(guān)注一下它的成員變量dexElements,它是一個Element[]數(shù)組,是包含dex的文件集合;
  • Element是DexPathList的一個靜態(tài)內(nèi)部類。DexPathList的構(gòu)造方法有4個參數(shù)。從其構(gòu)造方法中也可以看到傳遞過來的classLoade對象和dexPath不能為null,否則就拋出空指針異常;# dalvik.system.DexPathList

  1. private final Element[] dexElements; 
  2. public DexPathList(ClassLoader definingContext, String dexPath, 
  3.         String libraryPath, File optimizedDirectory) { 
  4.     if (definingContext == null) { 
  5.         throw new NullPointerException("definingContext == null"); 
  6.     } 
  7.     if (dexPath == null) { 
  8.         throw new NullPointerException("dexPath == null"); 
  9.     } 
  10.     if (optimizedDirectory != null) { 
  11.         if (!optimizedDirectory.exists())  { 
  12.             throw new IllegalArgumentException( 
  13.                     "optimizedDirectory doesn't exist: " 
  14.                     + optimizedDirectory); 
  15.         } 
  16.         // 如果文件不是可讀可寫的也會拋出異常 
  17.         if (!(optimizedDirectory.canRead() 
  18.                         && optimizedDirectory.canWrite())) { 
  19.             throw new IllegalArgumentException( 
  20.                     "optimizedDirectory not readable/writable: " 
  21.                     + optimizedDirectory); 
  22.         } 
  23.     } 
  24.     this.definingContext = definingContext; 
  25.     ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>(); 
  26.     // 通過makeDexElements方法來獲取Element數(shù)組 
  27.     // splitDexPath(dexPath)方法是用來把我們之前按照“:”分隔的路徑轉(zhuǎn)為File集合。 
  28.     this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory, 
  29.                                        suppressedExceptions); 
  30.     if (suppressedExceptions.size() > 0) { 
  31.         this.dexElementsSuppressedExceptions = 
  32.             suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]); 
  33.     } else { 
  34.         dexElementsSuppressedExceptions = null
  35.     } 
  36.     this.nativeLibraryDirectories = splitLibraryPath(libraryPath); 

5、makeDexElements

makeDexElements方法的作用是獲取一個包含dex文件的元素集合;

# dalvik.system.DexPathList

  1. private static Element[] makeDexElements(ArrayList<File> files, File optimizedDirectory, 
  2.                                          ArrayList<IOException> suppressedExceptions) { 
  3.     ArrayList<Element> elements = new ArrayList<Element>(); 
  4.     // 遍歷打開所有的文件并且加載直接或者間接包含dex的文件。 
  5.     for (File file : files) { 
  6.         File zip = null
  7.         DexFile dex = null
  8.         String name = file.getName(); 
  9.         if (file.isDirectory()) { 
  10.             // We support directories for looking up resources. 
  11.             // This is only useful for running libcore tests. 
  12.             // 可以發(fā)現(xiàn)它是支持傳遞目錄的,但是說只測試libCore的時候有用 
  13.             elements.add(new Element(file, truenullnull)); 
  14.         } else if (file.isFile()){ 
  15.             // 如果文件名后綴是.dex,說明是原始dex文件 
  16.             if (name.endsWith(DEX_SUFFIX)) { 
  17.                 // Raw dex file (not inside a zip/jar). 
  18.                 try { 
  19.                     //調(diào)用loadDexFile()方法,加載dex文件,獲得DexFile對象 
  20.                     dex = loadDexFile(file, optimizedDirectory); 
  21.                 } catch (IOException ex) { 
  22.                     System.logE("Unable to load dex file: " + file, ex); 
  23.                 } 
  24.             } else { 
  25.                 // dex文件包含在其它文件中 
  26.                 zip = file; 
  27.                 try { 
  28.                     // 同樣調(diào)用loadDexFile()方法 
  29.                     dex = loadDexFile(file, optimizedDirectory); 
  30.                 } catch (IOException suppressed) { 
  31.                     // 和加載純dex文件不同的是,會把異常添加到異常集合中 
  32.                     /* 
  33.                      * IOException might get thrown "legitimately" by the DexFile constructor if 
  34.                      * the zip file turns out to be resource-only (that isno classes.dex file 
  35.                      * in it). 
  36.                      * Let dex == null and hang on to the exception to add to the tea-leaves for 
  37.                      * when findClass returns null
  38.                      */ 
  39.                     suppressedExceptions.add(suppressed); 
  40.                 } 
  41.             } 
  42.         } else { 
  43.             System.logW("ClassLoader referenced unknown path: " + file); 
  44.         } 
  45.         // 如果zip或者dex二者一直不為null,就把元素添加進來 
  46.         // 注意,現(xiàn)在添加進來的zip存在不為null也不包含dex文件的可能。 
  47.         if ((zip != null) || (dex != null)) { 
  48.             elements.add(new Element(file, false, zip, dex)); 
  49.         } 
  50.     } 
  51.     return elements.toArray(new Element[elements.size()]); 

6、loadDexFile()、loadDex

通過上面的代碼也可以看到,加載一個dex文件調(diào)用的是loadDexFile()方法;

# dalvik.system.DexPathList

 

  1. private static DexFile loadDexFile(File file, File optimizedDirectory) 
  2.         throws IOException { 
  3.     // 如果緩存存放目錄為null就直接創(chuàng)建一個DexFile對象返回 
  4.     if (optimizedDirectory == null) { 
  5.         return new DexFile(file); 
  6.     } else { 
  7.         // 根據(jù)緩存存放目錄和文件名得到一個優(yōu)化后的緩存文件路徑 
  8.         String optimizedPath = optimizedPathFor(file, optimizedDirectory); 
  9.         // 調(diào)用DexFile的loadDex()方法來獲取DexFile對象。 
  10.         return DexFile.loadDex(file.getPath(), optimizedPath, 0); 
  11.     } 

DexFile的loadDex()方法如下,內(nèi)部也做了一些調(diào)用。拋開這些細節(jié)來講,它的作用就是加載DexFile文件,而且會把優(yōu)化后的dex文件緩存到對應(yīng)目錄;

# dalvik.system.DexFile

 

  1. static public DexFile loadDex(String sourcePathName, String outputPathName, 
  2.     int flags)throws IOException { 
  3.     /* 
  4.      * TODO: we may want to cache previously-opened DexFile objects. 
  5.      * The cache would be synchronized with close().  This would help 
  6.      * us avoid mapping the same DEX more than once when an app 
  7.      * decided to open it multiple times.  In practice this may not 
  8.      * be a real issue. 
  9.      */ 
  10.     //loadDex方法內(nèi)部就是調(diào)用了DexFile的一個構(gòu)造方法 
  11.     return new DexFile(sourcePathName, outputPathName, flags); 
  12. private DexFile(String sourceName, String outputName, int flags) throws IOException { 
  13.     if (outputName != null) { 
  14.         try { 
  15.             String parent = new File(outputName).getParent(); 
  16.             if (Libcore.os.getuid() != Libcore.os.stat(parent).st_uid) { 
  17.                 throw new IllegalArgumentException("Optimized data directory " + parent 
  18.                         + " is not owned by the current user. Shared storage cannot protect" 
  19.                         + " your application from code injection attacks."); 
  20.             } 
  21.         } catch (ErrnoException ignored) { 
  22.             // assume we'll fail with a more contextual error later 
  23.         } 
  24.     } 
  25.     mCookie = openDexFile(sourceName, outputName, flags); 
  26.     mFileName = sourceName; 
  27.     guard.open("close"); 
  28.     //System.out.println("DEX FILE cookie is " + mCookie + " sourceName=" + sourceName + " outputName=" + outputName); 
  29. private static long openDexFile(String sourceName, String outputName, int flags) throws IOException { 
  30.     // Use absolute paths to enable the use of relative paths when testing on host. 
  31.     return openDexFileNative(new File(sourceName).getAbsolutePath(), 
  32.                              (outputName == null) ? null : new File(outputName).getAbsolutePath(), 
  33.                              flags); 
  34. private static native long openDexFileNative(String sourceName, String outputName, int flags); 
  • 在BaseDexClassLoader對象構(gòu)造方法內(nèi),創(chuàng)建了PathDexList對象。而在PathDexList構(gòu)造方法內(nèi)部,通過調(diào)用一系列方法,把直接包含或者間接包含dex的文件解壓縮并緩存優(yōu)化后的dex文件,通過PathDexList的成員變量 Element[] dexElements來指向這個文件;
  • 到此我們就分析完了BaseDexClassLoader的構(gòu)造方法;

7、loadClass

  • 之前講Java類加載器的時候已經(jīng)說了,類加載是按需加載,也就是說當(dāng)明確需要使用class文件的時候才會加載;
  • 與在Java中的loadClass()方法主要流程是類似的,不過因為Android中BootClassLoader是用Java代碼寫的,所以可以直接當(dāng)作系統(tǒng)類加載器的parent類加載器。在Android中如果parent類加載器找不到類,最終還是會調(diào)用ClassLoader對象自己的findClass()方法;

 

  1. protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException { 
  2.     Class<?> clazz = findLoadedClass(className); 
  3.     if (clazz == null) { 
  4.         ClassNotFoundException suppressed = null
  5.         try { 
  6.             clazz = parent.loadClass(className, false); 
  7.         } catch (ClassNotFoundException e) { 
  8.             suppressed = e; 
  9.         } 
  10.         if (clazz == null) { 
  11.             try { 
  12.                 clazz = findClass(className); 
  13.             } catch (ClassNotFoundException e) { 
  14.                 e.addSuppressed(suppressed); 
  15.                 throw e; 
  16.             } 
  17.         } 
  18.     } 
  19.     return clazz; 

我們可以去看一下BaseDexClassLoader類的findClass()方法;

# dalvik.system.BaseDexClassLoader

  1. @Override 
  2. protected Class<?> findClass(String name) throws ClassNotFoundException { 
  3.     List<Throwable> suppressedExceptions = new ArrayList<Throwable>(); 
  4.     // 調(diào)用DexPathList對象的findClass()方法 
  5.     Class c = pathList.findClass(name, suppressedExceptions); 
  6.     if (c == null) { 
  7.         ClassNotFoundException cnfe = new ClassNotFoundException("Didn't find class \"" + name + "\" on path: " + pathList); 
  8.         for (Throwable t : suppressedExceptions) { 
  9.             cnfe.addSuppressed(t); 
  10.         } 
  11.         throw cnfe; 
  12.     } 
  13.     return c; 

實際上BaseDexClassLoader調(diào)用的是其成員變量DexPathList pathList的findClass()方法;

# dalvik.system.DexPathList

  1. public Class findClass(String name, List<Throwable> suppressed) { 
  2.     // 遍歷Element 
  3.     for (Element element : dexElements) { 
  4.         // 獲取DexFile,然后調(diào)用DexFile對象的loadClassBinaryName()方法來加載Class文件。 
  5.         DexFile dex = element.dexFile; 
  6.         if (dex != null) { 
  7.             Class clazz = dex.loadClassBinaryName(name, definingContext, suppressed); 
  8.             if (clazz != null) { 
  9.                 return clazz; 
  10.             } 
  11.         } 
  12.     } 
  13.     if (dexElementsSuppressedExceptions != null) { 
  14.         suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions)); 
  15.     } 
  16.     return null
  • 從上面的代碼中我們也可以看到,實際上DexPathList最終還是遍歷其自身的Element[]數(shù)組,獲取DexFile對象來加載Class文件;
  • DexPathList構(gòu)造方法內(nèi)是調(diào)用其makeDexElements()方法來創(chuàng)建Element[]數(shù)組的,而且也提到了如果zip文件或者dex文件二者之一不為null,就把元素添加進來,而添加進來的zip存在不為null也不包含dex文件的可能;
  • 上面的代碼中也可以看到,獲取Class的時候跟這個zip文件沒什么關(guān)系,調(diào)用的是dex文件對應(yīng)的DexFile的方法來獲取Class;
  • 數(shù)組的遍歷是有序的,假設(shè)有兩個dex文件存放了二進制名稱相同的Class,類加載器肯定就會加載在放在數(shù)組前面的dex文件中的Class;
  • 現(xiàn)在很多熱修復(fù)技術(shù)就是把修復(fù)的dex文件放在DexPathList中Element[]數(shù)組的前面,這樣就實現(xiàn)了修復(fù)后的Class搶先加載了,達到了修改bug的目的;
  • Android加載一個Class是調(diào)用DexFile的defineClass()方法。而不是調(diào)用ClassLoader的defineClass()方法。這一點與Java不同,畢竟Android虛擬機加載的dex文件,而不是class文件;

# dalvik.system.DexFile

  1. public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) { 
  2.     return defineClass(name, loader, mCookie, suppressed); 
  3. private static Class defineClass(String name, ClassLoader loader, long cookie, 
  4.                                  List<Throwable> suppressed) { 
  5.     Class result = null
  6.     try { 
  7.         result = defineClassNative(name, loader, cookie); 
  8.     } catch (NoClassDefFoundError e) { 
  9.         if (suppressed != null) { 
  10.             suppressed.add(e); 
  11.         } 
  12.     } catch (ClassNotFoundException e) { 
  13.         if (suppressed != null) { 
  14.             suppressed.add(e); 
  15.         } 
  16.     } 
  17.     return result; 

# java.lang.ClassLoader

 

  1. protected final Class<?> defineClass(String className, byte[] classRep, int offset, int length, 
  2.         ProtectionDomain protectionDomain) throws java.lang.ClassFormatError { 
  3.     throw new UnsupportedOperationException("can't load this type of class file"); 

Android中加載一個類是遍歷PathDexList的Element[]數(shù)組,這個Element包含了DexFile,調(diào)用DexFile的方法來獲取Class文件,如果獲取到了Class,就跳出循環(huán)。否則就在下一個Element中尋找Class;

三、熱修復(fù)的原理

利用pathClassLoader 的 對dex 文件進行替換,補丁 dex 文件加載到Element對象,并插入到 dexElement前面,具體還是使用反射;

雙親委派:當(dāng)一個class文件被加載時,classloader發(fā)現(xiàn)已經(jīng)加載過則不會重新加載,如果沒加載過則遞歸地把這個請求委派給父類加載器完成。當(dāng)父加載器找不到指定的類時,子加載器嘗試自己加載

步驟

關(guān)鍵是ClassLoader中l(wèi)oadeClass() 方法, loadClass()雙親委托機制

一個dex被加載的步驟

先從自己緩存中取

自己緩存沒有,就在 父 ClassLoader 要 (parent.loadClass())

父 ClassLoader 沒有,就自加載(findClass)

makeDexElements(將dex文件或壓縮包中的信息保存到dexElements中)

findCLass(遍歷Element,并將Element轉(zhuǎn)成Dex文件,獲取Dex文件中的Class文件,直到找到對應(yīng)的class文件位置)

總結(jié)

了解各種加載流程,還是需要多深入源碼,Android-ClassLoader實現(xiàn)邏輯算是非常清晰易懂,但對我們?nèi)粘i_發(fā)如插件化方案會有非常大的幫助;

本文轉(zhuǎn)載自微信公眾號「Android開發(fā)編程」

 

責(zé)任編輯:姜華 來源: Android開發(fā)編程
相關(guān)推薦

2023-11-07 10:19:08

2009-10-27 10:28:33

Silverlight

2021-07-05 06:51:43

Java機制類加載器

2021-09-02 07:00:01

Glide流程Android

2024-05-27 09:52:57

反射技術(shù).NET動態(tài)庫

2023-10-19 09:14:34

Java開發(fā)

2025-01-20 09:09:59

2016-12-02 20:43:34

Android動態(tài)加載DL框架

2021-09-06 13:12:05

前端JavaScript編程

2023-10-17 09:26:44

Java工具

2021-09-01 06:48:16

AndroidGlide緩存

2011-06-24 09:01:20

Qt QLibrary dll

2024-09-06 09:37:45

WebApp類加載器Web 應(yīng)用

2020-10-26 11:20:04

jvm類加載Java

2021-10-18 12:04:22

Spring BootJava開發(fā)

2021-10-18 10:36:31

Spring Boot插件Jar

2012-07-09 14:25:04

程序集加載

2014-12-25 09:41:15

Android加載方式

2011-06-23 14:05:32

Qt 事件機制

2023-10-31 16:00:51

類加載機制Java
點贊
收藏

51CTO技術(shù)棧公眾號