Android ClassLoader加載過程源碼分析

背景

Android開發過程當中,開發的小夥伴對動態加載代碼確定不陌生。使用各個開源框架的中都應該有接觸,其主要原理離不開ClassLoader等相關的類。這裏咱們會從Android中ClassLoader等相關類的源碼入手,更好的理解和學習動態加載類的原理。java

詳細分析ClassLoader加載原理

ClassLoader 的繼承關係以下:c++

這裏咱們主要分析一下 BaseDexClassLoader.findClass()ClassLoader.loadClass()兩個函數在系統中是怎麼進行查找class的過程。數組

咱們看一下系統加載類ClassLoader.loadClass()函數實現代碼,在ClassLoader.java中:緩存

protected Class<?> loadClass(String name, boolean resolve)
        throws ClassNotFoundException
    {
            // 首先 檢測是否已經加載過
            Class<?> c = findLoadedClass(name);
            if (c == null) {
                try {
                    if (parent != null) {
                        //去調用父類的loadClass
                        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) {
                    //未找到的狀況下,使用findClass在當前dex查找
                    c = findClass(name);
                }
            }
            return c;
    }

    protected Class<?> findClass(String name) throws ClassNotFoundException {
        throw new ClassNotFoundException(name);
    }
  • 1, loadClass()先調用findLoadedClass()來判斷當前類是否已加載;
  • 2, 未查找到遞歸去父類中查找是否加載到緩存;
  • 3, 均未緩存,去BootClassLoader中查找;
  • 4, 以上未發現,自頂級父類依次向下查找,調用findClass()查找當前dex。

findLoadedClass函數分析

下圖爲findLoadedClass()的調用流程;根據調用流程圖配合源代碼進行詳細的分析原理。微信

下面介紹對應的源代碼實現部分:cookie

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

函數最終統一調用VMClassLoader.findLoadedClass()進行查找類。框架

native static Class findLoadedClass(ClassLoader cl, String name);

實如今java_lang_VMClassLoader.cc文件中。ide

static jclass VMClassLoader_findLoadedClass(JNIEnv* env, jclass, jobject javaLoader,jstring javaName) {
  ....
  ObjPtr<mirror::ClassLoader> loader = soa.Decode<mirror::ClassLoader>(javaLoader);
  ClassLinker* cl = Runtime::Current()->GetClassLinker();

  ObjPtr<mirror::Class> c = VMClassLoader::LookupClass(cl,
                                                       soa.Self(),
                                                       descriptor.c_str(),
                                                       descriptor_hash,
                                                       loader);
  if (c != nullptr && c->IsResolved()) {
    return soa.AddLocalReference<jclass>(c);
  }
  ...
  if (loader != nullptr) {
    // Try the common case.
    StackHandleScope<1> hs(soa.Self());
    c = VMClassLoader::FindClassInPathClassLoader(cl,
                                                  soa,
                                                  soa.Self(),
                                                  descriptor.c_str(),
                                                  descriptor_hash,
                                                  hs.NewHandle(loader));
    if (c != nullptr) {
      return soa.AddLocalReference<jclass>(c);
    }
  }

  return nullptr;
}

  static mirror::Class* LookupClass(ClassLinker* cl,
                                    Thread* self,
                                    const char* descriptor,
                                    size_t hash,
                                    ObjPtr<mirror::ClassLoader> class_loader)
      REQUIRES(!Locks::classlinker_classes_lock_)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    return cl->LookupClass(self, descriptor, hash, class_loader);
  }
  static ObjPtr<mirror::Class> FindClassInPathClassLoader(ClassLinker* cl,
                                                          ScopedObjectAccessAlreadyRunnable& soa,
                                                          Thread* self,
                                                          const char* descriptor,
                                                          size_t hash,
                                                          Handle<mirror::ClassLoader> class_loader)
      REQUIRES_SHARED(Locks::mutator_lock_) {
    ObjPtr<mirror::Class> result;
    if (cl->FindClassInBaseDexClassLoader(soa, self, descriptor, hash, class_loader, &result)) {
      return result;
    }
    return nullptr;
  }

上述代碼findLoadedClass()分爲兩步;函數

  • 1,經過class_linker_->Lookupclass()進行查找加載類;
  • 2,若是沒找到在經過class_linker_->FindClassInPathClassLoader()進行查找。

class_linker_在虛擬機的啓動startVM()函數的時候進行的初始化。<br> Runtime::class_linker_Runtime::Init()函數的時候作的初始化。學習

if (UNLIKELY(IsAotCompiler())) {
    class_linker_ = new AotClassLinker(intern_table_);
  } else {
    class_linker_ = new ClassLinker(intern_table_);
  }

繼續來分析ClassLinker::LookupClass()函數的具體實現;

mirror::Class* ClassLinker::LookupClass(Thread* self,
                                        const char* descriptor,
                                        size_t hash,
                                        ObjPtr<mirror::ClassLoader> class_loader) {
  ReaderMutexLock mu(self, *Locks::classlinker_classes_lock_);
  ClassTable* const class_table = ClassTableForClassLoader(class_loader);
  if (class_table != nullptr) {
    ObjPtr<mirror::Class> result = class_table->Lookup(descriptor, hash);
    if (result != nullptr) {
      return result.Ptr();
    }
  }
  return nullptr;
}

LookupClass()函數經過class_loader是否爲nullptrnullptr使用boot_class_table_來獲取class_table, 不然獲取當前ClassLoaderClassTableclass_table存放當前已經加載過的class,其實能夠理解爲class cache。如何進行dex 解析和aot等加載系統類和解析映射到內存中的不在此處展開分析。能夠了解art虛擬機啓動進行詳細分析。

findClass()函數分析

下圖是findClass的調用流程;根據調用流程圖配合下面的代碼進行詳細的分析瞭解;

下面咱們介紹對應的源代碼實現部分。

findClass()函數在BaseDexClassLoader.java實現, 該函數主要作的事情就是在當前dex中查找類。若是類在當前dex中即返回。

代碼以下:

@Override
    protected Class<?> findClass(String name) throws ClassNotFoundException {
        List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
        Class c = pathList.findClass(name, suppressedExceptions);
        if (c == null) {
            ...
            throw cnfe;
        }
        return c;
    }

pathList類型爲DexPathList用來保存dexfile文件的句柄等dex的操做。pathList.findClass()實如今當前dex中查找類, pathListnew DexClassLoader()構造時初始化。

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

DexPathList.java

public DexPathList(ClassLoader definingContext, String dexPath,
            String librarySearchPath, File optimizedDirectory) {

        ...
        this.definingContext = definingContext;
        ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
        // save dexPath for BaseDexClassLoader
        this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
                                           suppressedExceptions, definingContext);

        this.nativeLibraryDirectories = splitPaths(librarySearchPath, false);
        this.systemNativeLibraryDirectories =
                splitPaths(System.getProperty("java.library.path"), true);
        List<File> allNativeLibraryDirectories = new ArrayList<>(nativeLibraryDirectories);
        allNativeLibraryDirectories.addAll(systemNativeLibraryDirectories);

        this.nativeLibraryPathElements = makePathElements(allNativeLibraryDirectories);

        if (suppressedExceptions.size() > 0) {
            this.dexElementsSuppressedExceptions =
                suppressedExceptions.toArray(new IOException[suppressedExceptions.size()]);
        } else {
            dexElementsSuppressedExceptions = null;
        }
    }

dexElements數組保存dexfile文件句柄。具體實如今makeDexElements()函數中調用loadDexFile()函數加載dex。該函數實現:

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

DexFile.loadDex()進行解析加載dex文件。關鍵代碼以下:

private DexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) throws IOException {
    ...
    mCookie = openDexFile(sourceName, outputName, flags, loader, elements);
    mInternalCookie = mCookie;
    mFileName = sourceName;
    ...
}

private static Object openDexFile(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements) 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,loader,elements);
}

private static native Object openDexFileNative(String sourceName, String outputName, int flags, ClassLoader loader, DexPathList.Element[] elements);

最終打開dexfile是經過native方法實現,而且返回mCookie, mCookie類型是int用來標識dex的惟一性。 openDexFileNative()實現代碼:

//`dalvik_system_DexFile.cc`
static jobject DexFile_openDexFileNative(JNIEnv* env,
                                         jclass,
                                         jstring javaSourceName,
                                         jstring javaOutputName,
                                         jint flags ATTRIBUTE_UNUSED,
                                         jobject class_loader,
                                         jobjectArray dex_elements)
{
  ...
  Runtime* const runtime = Runtime::Current();
  ClassLinker* linker = runtime->GetClassLinker();
  
  ...

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

上述代碼經過aotManager打開並返回mCookie,進一步的打開實現不在此處展開。即上述已經已經填充elements[],下面開始展開pathList.findClass()函數的查找方式。

//BaseDexClassLoader.java
    public Class<?> findClass(String name, List<Throwable> suppressed) {
        for (Element element : dexElements) {
            Class<?> clazz = element.findClass(name, definingContext, suppressed);
            if (clazz != null) {
                return clazz;
            }
        }

        if (dexElementsSuppressedExceptions != null) {
            suppressed.addAll(Arrays.asList(dexElementsSuppressedExceptions));
        }
        return null;
    }

findClass()會遍歷elements[], 每一個element保存了dex的DexFile句柄,而後調用loadClassBinaryName()函數進行當前dex查找類。

//DexPathList.java
  public Class<?> findClass(String name, ClassLoader definingContext,
          List<Throwable> suppressed) {
      return dexFile != null ? dexFile.loadClassBinaryName(name, definingContext, suppressed): null;
  }
public Class loadClassBinaryName(String name, ClassLoader loader, List<Throwable> suppressed) {
      return defineClass(name, loader, mCookie, this, suppressed);
  }

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

真正去dex或者內存中查找類的函數在nativedefineClassNative()實現, 咱們來分析一下真正的實現過程:

private static native Class defineClassNative(String name, ClassLoader loader, Object cookie, DexFile dexFile)

//dalvik_system_DexFile.cc
static jclass DexFile_defineClassNative(JNIEnv* env,
                                        jclass,
                                        jstring javaName,
                                        jobject javaLoader,
                                        jobject cookie,
                                        jobject dexFile) {
  std::vector<const DexFile*> dex_files;
  const OatFile* oat_file;
  if (!ConvertJavaArrayToDexFiles(env, cookie, /*out*/ dex_files, /*out*/ oat_file)) {
    ...
    return nullptr;
  }

  ScopedUtfChars class_name(env, javaName);
  ...

  const std::string descriptor(DotToDescriptor(class_name.c_str()));
  const size_t hash(ComputeModifiedUtf8Hash(descriptor.c_str()));
  for (auto& dex_file : dex_files) {
      ...
      ClassLinker* class_linker = Runtime::Current()->GetClassLinker();
      ObjPtr<mirror::Class> result = class_linker->DefineClass(soa.Self(),
                                                               descriptor.c_str(), 
                                                               hash,
                                                               class_loader,
                                                               *dex_file,
                                                               *dex_class_def);
      // Add the used dex file. This only required for the DexFile.loadClass API since normal
      // class loaders already keep their dex files live.
      class_linker->InsertDexFileInToClassLoader(soa.Decode<mirror::Object>(dexFile),
                                                 class_loader.Get());
      ....
        return soa.AddLocalReference<jclass>(result);
      }
    }
    ...
  return nullptr;
}

經過Runtime拿到當前的ClassLinker對象,而後經過class_linker->DefineClass()在當前dex中進行查找類。而後把找到的類經過class_linker->InsertDexFileInToClassLoader()插入到class_table中進行緩存,返回查找到的類。這裏不進一步展開分析。

Android ClassLoader加載過程的源代碼分析到此已經分析的差很少了,若是想深刻的瞭解具體原理,能夠本身看源代碼的實現。 這裏就介紹到這裏。初次寫技術分享的文章,若有錯誤請指正,感謝!

<br>

(360技術原創內容,轉載請務必保留文末二維碼,謝謝~)

關於360技術

360技術是360技術團隊打造的技術分享公衆號,天天推送技術乾貨內容

更多技術信息歡迎關注「360技術」微信公衆號

相關文章
相關標籤/搜索