扣丁書屋

Android加殼脫殼學習—動態加載和類加載機制詳解

前言

最近一直在學習Android 加殼和脫殼,在進行Android加殼和脫殼的學習中,第一步便是深入理解類加載器和動態加載二者之間的關系。

本文詳細的介紹了類加載器和動態加載之間的關系和原理,之所以詳細的講解兩者之間的關系,一是學習脫殼和加殼的需要,二是為后面Android插件化漏洞挖掘的講解做鋪墊。

類加載器

Android中的類加載器機制與JVM一樣遵循雙親委派模式。

1.雙親委派模式

(1)雙親委派模式定義

(1)加載.class文件時,以遞歸的形式逐級向上委托給父加載器ParentClassLoader加載,如果加載過了,就不用再加載一遍
(2)如果父加載器沒有加載過,繼續委托給父加載器去加載,一直到這條鏈路的頂級,頂級ClassLoader如果沒有加載過,則嘗試加載,加載失敗,則逐級向下交還調用者加載

protected Class<?> loadClass(String name, boolean resolve) throws ClassNotFoundException{
           //1.先檢查是否已經加載過--findLoaded
           Class<?> c = findLoadedClass(name);
           if (c == null) {
               try {
                   //2.如果自己沒加載過,存在父類,則委托父類
                   if (parent != null) {
                       c = parent.loadClass(name, false);
                   } else {
                       c = findBootstrapClassOrNull(name);
                   }
               } catch (ClassNotFoundException e) {
               }

               if (c == null) {
                   //3.如果父類也沒加載過,則嘗試本級classLoader加載
                   c = findClass(name);
               }
           }
          return c;
   }

代碼解釋:

① 先檢查自己是否已經加載過class文件,用findLoadedClass方法,如果已經加載了直接返。

② 如果自己沒有加載過,存在父類,則委派父類去加載,用parent.loadClass(name,false)方法,此時會向上傳遞,然后去父加載器中循環第1步,一直到頂級ClassLoader。

③ 如果父類沒有加載,則嘗試本級classLoader加載,如果加載失敗了就會向下傳遞,交給調用方式實現.class文件的加載。

(2)雙親委派模式加載流程

(3)雙親委派的作用

① 防止同一個.class文件重復加載。 ② 對于任意一個類確保在虛擬機中的唯一性。由加載它的類加載器和這個類的全類名一同確立其在Java虛擬機中的唯一性。 ③ 保證.class文件不被篡改,通過委派方式可以保證系統類的加載邏輯不被篡改。

2. Android中類加載機制

(1)Android基本類預加載

我們了解Android基本類預加載,首先我們回顧上文的Dalvik虛擬機啟動相關:

我們執行app_process程序,進入main函數里面,然后進行AndroidRuntime::start:

Zygote native 進程主要工作:

① 創建虛擬機–startVM

② 注冊JNI函數–startReg

③ 通過JNI知道Java層的com.android.internal.os.ZygoteInit 類,調用main 函數,進入java 世界

然后進入Java層:

Zygote總結:

① 解析init.zygote.rc中的參數,創建AppRuntime并調用AppRuntime.start()方法。

② 調用AndroidRuntime的startVM()方法創建虛擬機,再調用startReg()注冊JNI函數。

③ 通過JNI方式調用ZygoteInit.main(),第一次進入Java世界。

④ registerZygoteSocket()建立socket通道,zygote作為通信的服務端,用于響應客戶端請求。

⑤ preload()預加載通用類、drawable和color資源、openGL以及共享庫以及WebView,用于提高app啟動效率。

⑥ 通過startSystemServer(),fork得力幫手system_server進程,也是Java Framework的運行載體(下面講到system server再詳細講解)。

⑦ 調用runSelectLoop(),隨時待命,當接收到請求創建新進程請求時立即喚醒并執行相應工作。

Android的類加載機制和JVM一樣遵循雙親委派模式,在dalvik/art啟動時將所有Java基本類和Android系統框架的基本類加載進來,預加載的類記錄在/frameworks/base/config/preloaded-classes中。


android.R$styleable
android.accessibilityservice.AccessibilityServiceInfo$1
android.accessibilityservice.AccessibilityServiceInfo
android.accessibilityservice.IAccessibilityServiceClient$Stub$Proxy
android.accessibilityservice.IAccessibilityServiceClient$Stub
android.accessibilityservice.IAccessibilityServiceClient
android.accounts.AbstractAccountAuthenticator$Transport
android.accounts.AbstractAccountAuthenticator
android.accounts.Account$1
android.accounts.Account
...

java.lang.Short
java.lang.StackOverflowError
java.lang.StackTraceElement
java.lang.StrictMath
java.lang.String$1
java.lang.String$CaseInsensitiveComparator
java.lang.String
java.lang.StringBuffer
java.lang.StringBuilder
java.lang.StringFactory
java.lang.StringIndexOutOfBoundsException
java.lang.System$PropertiesWithNonOverrideableDefaults
java.lang.System
java.lang.Thread$1
...

這些類只需要在Zygote進程啟動時加載一遍就可以了,后續沒一個APP或Android運行時環境的進程,都是從Zygote中fork出來,天然保留了加載過的類緩存。

ZygoteInit.preload()

static void preload(TimingsTraceLog bootTimingsTraceLog) {
    // ...省略
    preloadClasses();
    // ...省略
}

private static void preloadClasses() {
    final VMRuntime runtime = VMRuntime.getRuntime();

    // 讀取 preloaded_classes 文件
    InputStream is;
    try {
        is = new FileInputStream(PRELOADED_CLASSES);
    } catch (FileNotFoundException e) {
        Log.e(TAG, "Couldn't find " + PRELOADED_CLASSES + ".");
        return;
    }

    // ...省略

    try {
        BufferedReader br =
                new BufferedReader(new InputStreamReader(is), Zygote.SOCKET_BUFFER_SIZE);

        int count = 0;
        String line;
        while ((line = br.readLine()) != null) {
            // Skip comments and blank lines.
            line = line.trim();
            if (line.startsWith("#") || line.equals("")) {
                continue;
            }

            Trace.traceBegin(Trace.TRACE_TAG_DALVIK, line);
            try {
                // 逐行加載基本類
                Class.forName(line, true, null);
                count++;
                // ...省略
            } catch (Throwable t) {
                // ...省略
            }
        }

        // ...省略
    } catch (IOException e) {
        Log.e(TAG, "Error reading " + PRELOADED_CLASSES + ".", e);
    } finally {
        // ...省略
    }
}

(2)Android類加載器層級關系及分析

Android中的ClassLoader類型分為系統ClassLoader和自定義ClassLoader。其中系統ClassLoader包括3種是BootClassLoader、DexClassLoader、PathClassLoader。

① BootClassLoader:Android平臺上所有Android系統啟動時會使用BootClassLoader來預加載常用的類。

② BaseDexClassLoader:實際應用層類文件的加載,而真正的加載委托給pathList來完成。

③ DexClassLoader:可以加載dex文件以及包含dex的壓縮文件(apk,dex,jar,zip),可以安裝一個未安裝的apk文件,一般為自定義類加載器。

④ PathClassLoader:可以加載系統類和應用程序的類,通常用來加載已安裝的apk的dex文件。

補充:Android 提供的原生加載器叫做基礎類加載器,包括:BootClassLoader,PathClassLoader,DexClassLoader,InMemoryDexClassLoader(Android 8.0 引入),DelegateLastClassLoader(Android 8.1 引入)。

<1> BootClassLoader

啟動類加載器,用于加載 Zygote 進程已經預加載的基本類,可以推測它只需從緩存中加載。這是基類 ClassLoader 的一個內部類,是包訪問權限,所以應用程序無權直接訪問。

public abstract class ClassLoader {
    // ...省略

    class BootClassLoader extends ClassLoader {
        private static BootClassLoader instance;

        public static synchronized BootClassLoader getInstance() {
            if (instance == null) {
                instance = new BootClassLoader();
            }

            return instance;
        }

        public BootClassLoader() {
            super(null);
        }

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

        // ...省略

        @Override
        protected Class<?> loadClass(String className, boolean resolve)
               throws ClassNotFoundException {
            Class<?> clazz = findLoadedClass(className);

            if (clazz == null) {
                clazz = findClass(className);
            }

            return clazz;
        }

        // ...省略
    }
}

源碼分析: 我們可以看見,BootClassLoader沒有父加載器,在緩存取不到類是直接調用自己的findClass()方法。

findClass()方法調用Class.classForName()方法,而ZygoteInit.preloadClasses()中,加載基本類是Class.forName()。

ublic final class Class<T> implements java.io.Serializable,
                              GenericDeclaration,
                              Type,
                              AnnotatedElement {
    // ...省略

    public static Class<?> forName(String className)
                throws ClassNotFoundException {
        Class<?> caller = Reflection.getCallerClass();
        return forName(className, true, ClassLoader.getClassLoader(caller));
    }

    public static Class<?> forName(String name, boolean initialize,
                                   ClassLoader loader)
        throws ClassNotFoundException
    {
        if (loader == null) {
            loader = BootClassLoader.getInstance();
        }
        Class<?> result;
        try {
            result = classForName(name, initialize, loader);
        } catch (ClassNotFoundException e) {
            Throwable cause = e.getCause();
            if (cause instanceof LinkageError) {
                throw (LinkageError) cause;
            }
            throw e;
        }
        return result;
    }

    // 本地方法
    static native Class<?> classForName(String className, boolean shouldInitialize,
            ClassLoader classLoader) throws ClassNotFoundException;

    // ...省略
}

我們可以發現,預加載時,ZygoteInit.preloadClasses()中調用Class.forName(),實際是指定BootClassLoader為類加載器,且只需要在預加載的時候進行類初始化,只需要一次。

總之,通過 Class.forName() 或者 Class.classForName() 可以且僅可以直接加載基本類,一旦基本類預加載后,對于應用程序而言,我們雖然不能直接訪問BootClassLoader,但可以通過Class.forName/Class.classForName加載。

無論是系統類加載器(PathClassLoader)還是自定義的類加載器(DexClassLoader),最頂層的祖先加載器默認是 BootClassLoader,與 JVM 一樣,保證了基本類的類型安全。

Class文件加載:

① 通過Class.forName()方法動態加載

② 通過ClassLoader.loadClass()方法動態加載 類的加載分為3個步驟:1.裝載(Load),2.鏈接(Link),3.初始化(Intialize) 類加載時機:

1.隱式加載:

① 創建類的實例,也就是new一個對象

② 訪問某個類或接口的靜態變量,或者對該靜態變量賦值

③ 調用類的靜態方法

④ 反射Class.forName("android.app.ActivityThread")

⑤ 初始化一個類的子類(會首先初始化子類的父類)

2.顯示加載:

① 使用LoadClass()加載

② 使用forName()加載

<2> PathClassLoader

主要用于系統和app的類加載器,其中optimizedDirectory為null, 采用默認目錄/data/dalvik-cache/。

PathClassLoader 是作為應用程序的系統類加載器,也是在 Zygote 進程啟動的時候初始化的(基本流程為:ZygoteInit.main() -> ZygoteInit.forkSystemServer() -> ZygoteInit.handleSystemServerProcess() -> ZygoteInit.createPathClassLoader()。在預加載基本類之后執行),所以每一個 APP 進程從 Zygote 中 fork 出來之后都自動攜帶了一個 PathClassLoader,它通常用于加載 apk 里面的 .dex 文件。

<3> DexClassLoader

可以從包含classes.dex的jar或者apk中,加載類的類加載器, 可用于執行動態加載, 但必須是app私有可寫目錄來緩存odex文件. 能夠加載系統沒有安裝的apk或者jar文件, 因此很多熱修復和插件化方案都是采用DexClassLoader。


public class
DexClassLoader extends BaseDexClassLoader {

   public DexClassLoader(String dexPath, String optimizedDirectory,
            String librarySearchPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), librarySearchPath, parent);
    }
}

總結:

我們可以發現DexClassLoader與PathClassLoader都繼承于BaseDexClassLoader,這兩個類只是提供了自己的構造函數,沒有額外的實現。

區別:

DexClassLoader提供了optimizedDirectory,而PathClassLoader則沒有,optimizedDirectory正是用來存放odex文件的地方,所以可以利用DexClassLoader實現動態加載。

<4> BaseDexClassLoader

public class BaseDexClassLoader extends ClassLoader {
    private final DexPathList pathList;  //記錄dex文件路徑信息

    public BaseDexClassLoader(String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) {
        super(parent);
        this.pathList = new DexPathList(this, dexPath, libraryPath, optimizedDirectory);
    }
}

dexPath: 包含目標類或資源的apk/jar列表;當有多個路徑則采用:分割;optimizedDirectory: 優化后dex文件存在的目錄, 可以為null;libraryPath: native庫所在路徑列表;當有多個路徑則采用:分割;ClassLoader:父類的類加載器。

BaseDexClassLoader會初始化dexPathList,收集dex文件和Native文件動態庫。

初始化:

DexPathList:

該類主要用來查找Dex、SO庫的路徑,并這些路徑整體呈一個數組。

final class DexPathList {
    private Element[] dexElements;
    private final List<File> nativeLibraryDirectories;
    private final List<File> systemNativeLibraryDirectories;

    final class DexPathList {
    public DexPathList(ClassLoader definingContext, String dexPath,
            String libraryPath, File optimizedDirectory) {
        ...
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();

        //記錄所有的dexFile文件
        this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory, suppressedExceptions);

        //app目錄的native庫
        this.nativeLibraryDirectories = splitPaths(libraryPath, false);
        //系統目錄的native庫
        this.systemNativeLibraryDirectories = splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);
        //記錄所有的Native動態庫
        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories, null, suppressedExceptions);
        ...
    }
}

DexPathList初始化過程,主要收集以下兩個變量信息:

(1)dexElements: 根據多路徑的分隔符“;”將dexPath轉換成File列表,記錄所有的dexFile

(2)nativeLibraryPathElements: 記錄所有的Native動態庫, 包括app目錄的native庫和系統目錄的native庫

makePathElements:

private static Element[] makePathElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, null);
}

makeDexElements:

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

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader) {
    return makeDexElements(files, optimizedDirectory, suppressedExceptions, loader, false);
}

private static Element[] makeDexElements(List<File> files, File optimizedDirectory,
        List<IOException> suppressedExceptions, ClassLoader loader, boolean isTrusted) {
  Element[] elements = new Element[files.size()];  //獲取文件個數
  int elementsPos = 0;
  for (File file : files) {
      if (file.isDirectory()) {
          elements[elementsPos++] = new Element(file);
      } else if (file.isFile()) {
          String name = file.getName();
          DexFile dex = null;
          //匹配以.dex為后綴的文件
          if (name.endsWith(DEX_SUFFIX)) {
              dex = loadDexFile(file, optimizedDirectory, loader, elements);
              if (dex != null) {
                  elements[elementsPos++] = new Element(dex, null);
              }
          } else {
              dex = loadDexFile(file, optimizedDirectory, loader, elements);             
              if (dex == null) {
                  elements[elementsPos++] = new Element(file);
              } else {
                  elements[elementsPos++] = new Element(dex, file);
              }
          }
          if (dex != null && isTrusted) {
            dex.setTrusted();
          }
      } else {
          System.logW("ClassLoader referenced unknown path: " + file);
      }
  }
  if (elementsPos != elements.length) {
      elements = Arrays.copyOf(elements, elementsPos);
  }

  return elements;
}

該方法的主要功能是創建Element數組。

loadDexFile:

加載DexFile文件,而且會把優化后的dex文件緩存到對應目錄。

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

DexFile:

用來描述Dex文件,Dex的加載以及Class的查找都是由該類調用它的native方法完成的。

DexFile(File file, ClassLoader loader, DexPathList.Element[] elements)
        throws IOException {
    this(file.getPath(), loader, elements);
}

DexFile(String fileName, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    mCookie = openDexFile(fileName, null, 0, loader, elements);
    mInternalCookie = mCookie;
    mFileName = fileName;
}

openDexFile:

private static Object openDexFile(String sourceName, String outputName, int flags,
        ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    return openDexFileNative(new File(sourceName).getAbsolutePath(),
                             (outputName == null) ? null : new File(outputName).getAbsolutePath(),
                             flags,
                             loader,
                             elements);
}
此時參數取值說明:
sourceName為PathClassLoader構造函數傳遞的dexPath中以分隔符劃分之后的文件名;
outputName為null;
flags = 0
loader為null;
elements為makeDexElements()過程生成的Element數組;

openDexFileNative:

static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName ATTRIBUTE_UNUSED,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements) {
  ScopedUtfChars sourceName(env, javaSourceName);
  if (sourceName.c_str() == nullptr) {
    return 0;
  }
  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  std::vector<std::unique_ptr<const DexFile>> dex_files;
  std::vector<std::string> error_msgs;
  const OatFile* oat_file = nullptr;

  dex_files = runtime->GetOatFileManager().OpenDexFilesFromOat(sourceName.c_str(),
                                                               class_loader,
                                                               dex_elements,
                                                               /*out*/ &oat_file,
                                                               /*out*/ &error_msgs);

  if (!dex_files.empty()) {
    jlongArray array = ConvertDexFilesToJavaArray(env, oat_file, dex_files);
    ...
    return array;
  } else {
    ...
    return nullptr;
  }
}

這樣就完成了dex的加載過程,而BaseDexClassLoader派生出兩個子類加載器:PathClassLoader和DexClassLoader。

Android中如果parent類加載器加載不到類,最終還是會調用ClassLoader對象自己的findClass()方法。

loadClass()加載:

public abstract class ClassLoader {

    public Class<?> loadClass(String className) throws ClassNotFoundException {
        return loadClass(className, false);
    }

    protected Class<?> loadClass(String className, boolean resolve) throws ClassNotFoundException {
        //判斷當前類加載器是否已經加載過指定類,若已加載則直接返回
        Class<?> clazz = findLoadedClass(className);

        if (clazz == null) {
            //如果沒有加載過,則調用parent的類加載遞歸加載該類,若已加載則直接返回
            clazz = parent.loadClass(className, false);

            if (clazz == null) {
                //還沒加載,則調用當前類加載器來加載
                clazz = findClass(className);
            }
        }
        return clazz;
    }
}

該方法的加載流程如下:

① 判斷當前類加載器是否已經加載過指定類,若已加載則直接返回,否則繼續執行;

② 調用parent的類加載遞歸加載該類,檢測是否加載,若已加載則直接返回,否則繼續執行;

③ 調用當前類加載器,通過findClass加載。

findLoadedClass:

[-> ClassLoader.java]


protected final Class<?> findLoadedClass(String name) {
    ClassLoader loader;
    if (this == BootClassLoader.getInstance())
        loader = null;
    else
        loader = this;
    return VMClassLoader.findLoadedClass(loader, name);
}

findClass:

[-> BaseDexClassLoader.java]


public class BaseDexClassLoader extends ClassLoader {
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        Class c = pathList.findClass(name, suppressedExceptions);
        ...
        return c;
    }
}

DexPathList.findClass:


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

代碼解釋:

一個Classloader可以包含多個dex文件,每個dex文件被封裝到一個Element對象,這些Element對象排列成有序的數組 dexElements。當查找某個類時,會遍歷所有的dex文件,如果找到則直接返回,不再繼續遍歷dexElements。也就是說當兩個類不同的dex中出現,會優先處理排在前面的dex文件,這便是熱修復的核心精髓,將需要修復的類所打包的dex文件插入到dexElements前面。

熱修復原理:

現在很多熱修復技術就是把修復的dex文件放在DexPathList中Element[]數組的前面,這樣就實現了修復后的Class搶先加載了,達到了修改bug的目的。

DexFile.loadClassBinaryName:

public final class DexFile {

    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); 
        } catch (NoClassDefFoundError e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        } catch (ClassNotFoundException e) {
            if (suppressed != null) {
                suppressed.add(e);
            }
        }
        return result;
    }
}

defineClassNative()這是native方法。

defineClassNative:

[-> dalvik_system_DexFile.cc]

static jclass DexFile_defineClassNative(JNIEnv* env, jclass, jstring javaName, jobject javaLoader,
                                        jobject cookie) {
  std::unique_ptr<std::vector<const DexFile*>> dex_files = ConvertJavaArrayToNative(env, cookie);
  if (dex_files.get() == nullptr) {
    return nullptr; //dex文件為空, 則直接返回
  }

  ScopedUtfChars class_name(env, javaName);
  if (class_name.c_str() == nullptr) {
    return nullptr; //類名為空, 則直接返回
  }

  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str())); //將類名轉換為hash碼
  for (auto& dex_file : *dex_files) {
    const DexFile::ClassDef* dex_class_def = dex_file->FindClassDef(descriptor.c_str(), hash);
    if (dex_class_def != nullptr) {
      ScopedObjectAccess soa(env);
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      class_linker->RegisterDexFile(*dex_file);
      StackHandleScope<1> hs(soa.Self());
      Handle<mirror::ClassLoader> class_loader(
          hs.NewHandle(soa.Decode<mirror::ClassLoader*>(javaLoader)));
      //獲取目標類
      mirror::Class* result = class_linker->DefineClass(soa.Self(), descriptor.c_str(), hash,
                                                        class_loader, *dex_file, *dex_class_def);
      if (result != nullptr) {
        // 找到目標對象
        return soa.AddLocalReference<jclass>(result);
      }
    }
  }
  return nullptr; //沒有找到目標類
}

在native層創建目標類的對象并添加到虛擬機列表。 我們繼續分析Native層可以發現:

DexFile.defineClassNative() 的實現在 /art/runtime/native/dalvik_system_DexFile.cc,最終由 ClassLinker.DefineClass() 實現
Class.classForName() 的實現在 /art/runtime/native/java_lang_Class.cc,最終由 ClassLinker.FindClass() 實現

ClassLinker核心原理:

先從已加載類的 class_table 中查詢,若找到則直接返回;若找不到則說明該類是第一次加載,則執行加載流程,其中可能需要穿插加載依賴的類,加載完成后將其緩存到 class_table 中。

在 ClassLinker 中,會維護兩類 class_table,一類針對基本類,一類針對其它的類。class_table 是作為緩存已經加載過的類的緩沖池。不管以什么樣的方式去加載類,都需要先從 class_table 中先進行查詢以提高加載性能。

ClassLinker 在加載類的時候遇到該類依賴的類,進行穿插加載依賴類:

我們總結BaseDexClassLoader初始化和加載原理:

Android類加載詳細流程:

3.案例

(1)驗證類加載器

我們驗證App中的MainActivity類加載器和系統類String類的類加載器:

ClassLoader thisclassloader = MainActivity.class.getClassLoader();
ClassLoader StringClassloader = String.class.getClassLoader();
Log.e("ClassLoader1","MainActivity is in" + thisclassloader.toString());
Log.e("ClassLoader1","String is in" + StringClassloader.toString());

我們可以明顯發現PathClassLoader加載已安裝的APK類加載器,而BootClassLoader加載系統預安裝的類。

(2)遍歷父類加載器

public static  void printClassLoader(ClassLoader classLoader) {
       Log.e("printClassLoader","this->"+ classLoader.toString());
       ClassLoader parent = classLoader.getParent();
       while (parent!=null){
           Log.i("printClassLoader","parent->"+parent.toString());
           parent = parent.getParent();
       }
   }

(3)驗證雙親委派機制


try {
            Class StringClass = thisclassloader.loadClass("java.lang.String");
            Log.e("ClassLoader1","load StringClass!"+thisclassloader.toString());
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            Log.e("ClassLoader1","load MainActivity fail!"+thisclassloader.toString());
        }

我們使用PathClassLoader去加載 String.class類,還是可以加載成功,因為雙親委派的機制。

(4)動態加載

這里我借用網上寒冰大佬動態加載的案例,來進一步講述使用DexClassLoader類實現簡單的動態加載插件dex,并驗證ClassLoader的繼承關系。

我們先編寫一個測試類文件,然后生成dex文件。

我們先將dex文件放到模擬器的sdcard/下。

我們新建一個程序,然后編寫主程序的代碼,并授權sd讀取權限。

Context appContext = this.getApplication();
testDexClassLoader(appContext,"/sdcard/classes.dex");
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

然后我們編寫類加載器代碼。

private void testDexClassLoader(Context context, String dexfilepath) {
        //構建文件路徑:/data/data/com.emaxple.test02/app_opt_dex,存放優化后的dex,lib庫
        File optfile = context.getDir("opt_dex",0);
        File libfile = context.getDir("lib_dex",0);

        ClassLoader parentclassloader = MainActivity.class.getClassLoader();
        ClassLoader tmpclassloader = context.getClassLoader();
    //可以為DexClassLoader指定父類加載器
        DexClassLoader dexClassLoader = new DexClassLoader(dexfilepath,optfile.getAbsolutePath(),libfile.getAbsolutePath(),parentclassloader);

        Class clazz = null;
        try {
            clazz = dexClassLoader.loadClass("com.example.test.TestClass");
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
        }
        if(clazz!=null){
            try {
                Method testFuncMethod = clazz.getDeclaredMethod("test02");
                Object obj = clazz.newInstance();
                testFuncMethod.invoke(obj);
            } catch (NoSuchMethodException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (InvocationTargetException e) {
                e.printStackTrace();
            }
        }

    }

(5)獲得類列表

我們通過getClassNameList來獲取類列表

private static native String[] getClassNameList(Object cookie);
public static void getClassListInClassLoader(ClassLoader classLoader){
        //先拿到BaseDexClassLoader
        try {
            //拿到pathList
            Class BaseDexClassLoader = Class.forName("dalvik.system.BaseDexClassLoader");
            Field pathListField = BaseDexClassLoader.getDeclaredField("pathList");
            pathListField.setAccessible(true);
            Object pathListObj = pathListField.get(classLoader);

            //拿到dexElements
            Class DexElementClass = Class.forName("dalvik.system.DexPathList");
            Field DexElementFiled = DexElementClass.getDeclaredField("dexElements");
            DexElementFiled.setAccessible(true);
            Object[]  dexElementObj = (Object[]) DexElementFiled.get(pathListObj);
            //拿到dexFile
            Class Element = Class.forName("dalvik.system.DexPathList$Element");
            Field dexFileField = Element.getDeclaredField("dexFile");
            dexFileField.setAccessible(true);
            Class DexFile =Class.forName("dalvik.system.DexFile");
            Field mCookieField = DexFile.getDeclaredField("mCookie");
            mCookieField.setAccessible(true);
            Field mFiledNameField = DexFile.getDeclaredField("mFileName");
            mFiledNameField.setAccessible(true);
            //拿到getClassNameList
            Method getClassNameListMethod = DexFile.getDeclaredMethod("getClassNameList",Object.class);
            getClassNameListMethod.setAccessible(true);

            for(Object dexElement:dexElementObj){
                Object dexfileObj = dexFileField.get(dexElement);
                Object mCookiedobj = mCookieField.get(dexfileObj);
                String mFileNameobj = (String) mFiledNameField.get(dexfileObj);
                String[] classlist = (String[]) getClassNameListMethod.invoke(null,mCookiedobj);
                for(String classname:classlist){
                    Log.e("classlist",classLoader.toString()+"-----"+mFileNameobj+"-----"+classname);
                }
            }

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

實驗總結

花了一段時間,斷斷續續總算把這篇類加載器和動態加載的帖子寫完了,從中學習到了很多,這里如果有什么錯誤,就請各位大佬指正了。

參考文獻:

http://gityuan.com/2017/03/19/android-classloader/

https://www.jianshu.com/p/7193600024e7

https://www.jianshu.com/p/ff489696ada2

https://www.jianshu.com/p/363a4ad0489d

https://github.com/huanzhiyazi/articles/issues/30

https://juejin.cn/post/6844903940094427150#heading-12


https://mp.weixin.qq.com/s/tf0wJxcrmcfZB2UngbCN1w

最多閱讀

簡化Android的UI開發 2年以前  |  515113次閱讀
Android 深色模式適配原理分析 1年以前  |  26416次閱讀
Android 樣式系統 | 主題背景覆蓋 1年以前  |  7953次閱讀
Android Studio 生成so文件 及調用 1年以前  |  5587次閱讀
30分鐘搭建一個android的私有Maven倉庫 3年以前  |  4751次閱讀
Android設計與開發工作流 2年以前  |  4413次閱讀
Google Enjarify:可代替dex2jar的dex反編譯 3年以前  |  4397次閱讀
Android多渠道打包工具:apptools 3年以前  |  4028次閱讀
移動端常見崩潰指標 2年以前  |  4014次閱讀
Google Java編程風格規范(中文版) 3年以前  |  3942次閱讀
Android-模塊化-面向接口編程 1年以前  |  3857次閱讀
Android內存異常機制(用戶空間)_NE 1年以前  |  3824次閱讀
Android UI基本技術點 3年以前  |  3790次閱讀
Android死鎖初探 2年以前  |  3734次閱讀

手機掃碼閱讀
18禁止午夜福利体验区,人与动人物xxxx毛片人与狍,色男人窝网站聚色窝
<蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <蜘蛛词>| <文本链> <文本链> <文本链> <文本链> <文本链> <文本链>