Android Classloader

前言

關於ClassLoader的介紹,能夠參考以前提到的:
Android動態加載基礎 ClassLoader工做機制
另外文章會提到,android中classloader都是採用了「雙親委派機制」,關於這一點能夠參考:
Parents Delegation Modelhtml

簡單總結一下:
對於android中的classloader是按照如下的flow:java

loadClass方法在加載一個類的實例的時候:
會先查詢當前ClassLoader實例是否加載過此類,有就返回;
若是沒有。查詢Parent是否已經加載過此類,若是已經加載過,就直接返回Parent加載的類;
若是繼承路線上的ClassLoader都沒有加載,才由Child執行類的加載工做;android

這樣作的好處:segmentfault

首先是共享功能,一些Framework層級的類一旦被頂層的ClassLoader加載過就緩存在內存裏面,之後任何地方用到都不須要從新加載。
除此以外還有隔離功能,不一樣繼承路線上的ClassLoader加載的類確定不是同一個類,這樣的限制避免了用戶本身的代碼冒充核心類庫的類訪問核心類庫包可見成員的狀況。這也好理解,一些系統層級的類會在系統初始化的時候被加載,好比java.lang.String,若是在一個應用裏面可以簡單地用自定義的String類把這個系統的String類給替換掉,那將會有嚴重的安全問題。緩存

DexClassLoader和PathClassLoader

關於他們兩個恩怨情仇,Android動態加載基礎 ClassLoader工做機制 已經有說明。安全

直接說結論:ide

DexClassLoader能夠加載jar/apk/dex,能夠從SD卡中加載未安裝的apk;
PathClassLoader只能加載系統中已經安裝過的apk;函數

經過源代碼能夠知道,DexClassLoader和PathClassLoader都是繼承自BaseDexClassLoaderui

// DexClassLoader.java
public class DexClassLoader extends BaseDexClassLoader {
    public DexClassLoader(String dexPath, String optimizedDirectory,
            String libraryPath, ClassLoader parent) {
        super(dexPath, new File(optimizedDirectory), libraryPath, parent);
    }
}

// PathClassLoader.java
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爲例,trace一下整個ClassLoader的初始化過程。
其中有兩個問題比較重要:this

  1. dex文件如何被load進來,產生了class文件

  2. dex文件與oat文件的轉化

DexClassLoader

super(dexPath, new File(optimizedDirectory), libraryPath, parent);

這裏的四個參數做用以下:

  1. dexPath:dex文件的路徑

  2. new File(optimizedDirectory):解析出來的dex文件存放的路徑

  3. libraryPath:native library的存放路徑

  4. parent:父classloader

BaseDexClassLoader

繼續看super類BaseDexClassLoader:

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

其中super(parnent)會call到ClassLoader的構造函數:

protected ClassLoader(ClassLoader parentLoader) {
    this(parentLoader, false);
}

ClassLoader(ClassLoader parentLoader, boolean nullAllowed) {
    if (parentLoader == null && !nullAllowed) {
        throw new NullPointerException("parentLoader == null && !nullAllowed");
    }
    parent = parentLoader;
}

這邊只是簡單賦了一下parent成員變量。

DexPathList

public DexPathList(ClassLoader definingContext, String dexPath,
        String libraryPath, File optimizedDirectory) {
    ......
    this.dexElements = makePathElements(splitDexPath(dexPath), optimizedDirectory,
                                        suppressedExceptions);
    ......
}

這邊的傳入參數能夠看到,會先把dexPath作切割繼續丟下去

DexPathList.makePathElements

在makePathElements中主要是構造dexElements對象,具體的依據就是傳入的files list,即dexPath。

private static Element[] makePathElements(List<File> files, File optimizedDirectory,
                                          List<IOException> suppressedExceptions) {
    List<Element> elements = new ArrayList<>();
    for (File file : files) {
        ......
        dex = loadDexFile(file, optimizedDirectory);
        ......
        elements.add(new Element(dir, false, zip, dex));
        ......
    }
    return elements.toArray(new Element[elements.size()]);
}

DexPathList.loadDexFile

觀察這個函數的入參,file對應的是dex路徑,optimizedDirectory則是對應的釋放dex文件地址。
簡單來講,src:file,dst:optimizedDirectory

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

這邊遇到了一個optimizedPath,看看他是怎麼來的(其實就是一個路徑轉換罷了):

private static String optimizedPathFor(File path,
        File optimizedDirectory) {
    ......
    File result = new File(optimizedDirectory, fileName);
    return result.getPath();
}

DexFile.loadDex

靜態方法,並無什麼特別的東西,其中會new DexFile:

static public DexFile loadDex(String sourcePathName, String outputPathName,
    int flags) throws IOException {
    return new DexFile(sourcePathName, outputPathName, flags);
}

DexFile Construct

這裏在剛開始的時候回作一個權限檢測,若是當前的文件目錄全部者跟當前進程的不一致,那就GG。
固然了,這一句不是重點,重點仍舊在後面:openDexFile

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) {
                ...
            }
        } catch (ErrnoException ignored) {
            // assume we'll fail with a more contextual error later
        }
    }

    mCookie = openDexFile(sourceName, outputName, flags);
    mFileName = sourceName;
    guard.open("close")
}

DexFile.openDexFile

private static Object 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);
}

code trace到這邊,基本上java層就已經到底了。由於openDexFileNative會直接call到jni層了:

private static native Object openDexFileNative(String sourceName, String outputName, int flags);

再次來回顧一下這邊的三個參數:

  1. String sourceName:dex文件的所在地址

  2. String outputName:釋放dex文件的目標地址

  3. int flags:0

JNI Native

Register JNI Method

DexFile對應的jni函數在:art/runtime/native/dalvik_system_DexFile.cc

grep大法好!

static JNINativeMethod gMethods[] = {
  ......
  NATIVE_METHOD(DexFile, openDexFileNative,
                "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;"),
};

void register_dalvik_system_DexFile(JNIEnv* env) {
  REGISTER_NATIVE_METHODS("dalvik/system/DexFile");
}

Macro的定義

能夠看到,在這裏有兩個Macro,他們到底是什麼呢?

廬山真面目在:art/runtime/jni_internal.h

#ifndef NATIVE_METHOD
#define NATIVE_METHOD(className, functionName, signature) \
  { #functionName, signature, reinterpret_cast<void*>(className ## _ ## functionName) }
#endif
#define REGISTER_NATIVE_METHODS(jni_class_name) \
  RegisterNativeMethods(env, jni_class_name, gMethods, arraysize(gMethods))

JNINativeMethod

typedef struct {
    const char* name;
    const char* signature;
    void*       fnPtr;
} JNINativeMethod;

Macro合體

NATIVE_METHOD(DexFile, openDexFileNative,
                "(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;"),

展開後

"openDexFileNative",
"(Ljava/lang/String;Ljava/lang/String;I)Ljava/lang/Object;",
reinterpret_cast<void*> DexFile_openDexFileNative,

很天然,就是對應了JNINativeMethod中的name,signature以及fnPtr。
所以對應native層的function : DexFile_openDexFileNative

DexFile_openDexFileNative

native層的代碼用到了不少C++ STL庫的相關知識,另外還有很多namespace的相關概念。
STL:C++:STL標準入門彙總三十分鐘掌握STL
namespace:詳解C++中命名空間的意義和用法

static jobject DexFile_openDexFileNative(
JNIEnv* env, jclass, jstring javaSourceName, jstring javaOutputName, jint) {
  ......
  ClassLinker* linker = Runtime::Current()->GetClassLinker();
  ......
  dex_files = linker->OpenDexFilesFromOat(sourceName.c_str(), outputName.c_str(), &error_msgs);
    ......
}

ClassLinker是一個很重要的東西,它與art息息相關。簡單來講它的建立是伴隨着runtime init的時候起來的,能夠認爲是每一個art & 進程會維護一個對象(具體代碼尚未分析,只是從網上看了一篇文章
這裏咱們暫時放下ClassLinker的建立初始化職責權限,先看看OpenDexFilesFromOat

ClassLinker::OpenDexFilesFromOat

這個函數很是大,其主要的幾個動做以下:

  1. 構造OatFileAssistant對象:這個對象的主要做用是協助dex文件解析成oat文件

  2. 遍歷ClassLinker的成員變量:oat_files_,目的是查看當前的dex文件是否已經被解析成oat了

    1. 若是當前dex已經被解析成了oat文件,那麼就跳過dex的解析,直接進入3)

    2. 反之,若是當前dex文件並無被解析過,那麼就會走:oat_file_assistant.MakeUpToDate

  3. 填充dex_files對象並返回,這裏也有兩種狀況(區分oat or dex):

    1. oat_file_assistant.LoadDexFiles

    2. DexFile::Open

其中咱們須要關注的點其實只有兩點(上文劃出的重點)共兩個函數:

  1. oat_file_assistant.MakeUpToDate

  2. oat_file_assistant.LoadDexFiles

std::vector<std::unique_ptr<const DexFile>> ClassLinker::OpenDexFilesFromOat(
    const char* dex_location, const char* oat_location,
    std::vector<std::string>* error_msgs) {
  ......
  OatFileAssistant oat_file_assistant(dex_location, oat_location, kRuntimeISA,
     !Runtime::Current()->IsAotCompiler());
  ......
  const OatFile* source_oat_file = nullptr;
  ......
    for (const OatFile* oat_file : oat_files_) {
      CHECK(oat_file != nullptr);
      if (oat_file_assistant.GivenOatFileIsUpToDate(*oat_file)) {
        source_oat_file = oat_file;
        break;
      }
    }
  ......

  if (source_oat_file == nullptr) {
    if (!oat_file_assistant.MakeUpToDate(&error_msg)) {
      ......
    }

    // Get the oat file on disk.
    std::unique_ptr<OatFile> oat_file = oat_file_assistant.GetBestOatFile();
    if (oat_file.get() != nullptr) {
          ......
        if (!DexFile::MaybeDex(dex_location)) {
          accept_oat_file = true;
          ......
        }
      }

      if (accept_oat_file) {
        source_oat_file = oat_file.release();
        RegisterOatFile(source_oat_file);
      }
    }
  }

  std::vector<std::unique_ptr<const DexFile>> dex_files;
  ......
  if (source_oat_file != nullptr) {
    dex_files = oat_file_assistant.LoadDexFiles(*source_oat_file, dex_location);
    ......
  }

  // Fall back to running out of the original dex file if we couldn't load any
  // dex_files from the oat file.
  if (dex_files.empty()) {
    ......
        if (!DexFile::Open(dex_location, dex_location, &error_msg, &dex_files)) {
    ......
  }
  return dex_files;
}

下面的內容會牽扯到不少oat file的解析,這一塊的內容有點龐大,我也理解的不是很透徹。
若是有興趣的話能夠先閱讀一下《羅昇陽:Android運行時ART加載OAT文件的過程分析》,其中對於oat file format會有進一步的瞭解

這一部分的知識對於理順classloader的flow來講障礙不大,可是對於瞭解整個art的flow來講相當重要。

OatFileAssistant::MakeUpToDate

一出4岔口的switch分支,對於這邊咱們關注的是kDex2OatNeeded(能夠認爲是第一次加載,這樣就省略掉不少細節)
另外千萬不要小看GetDexOptNeeded(),在它內部會去讀取dex文件,檢查checksum

bool OatFileAssistant::MakeUpToDate(std::string* error_msg) {
  switch (GetDexOptNeeded()) {
    case kNoDexOptNeeded: return true;
    case kDex2OatNeeded: return GenerateOatFile(error_msg);
    case kPatchOatNeeded: return RelocateOatFile(OdexFileName(), error_msg);
    case kSelfPatchOatNeeded: return RelocateOatFile(OatFileName(), error_msg);
  }
  UNREACHABLE();
}

因爲咱們這裏是第一次load,因此就會進入到GenerateOatFile

OatFileAssistant::GenerateOatFile

能夠看到這支函數的主要最用是call Dex2Oat,在最終call以前會去檢查一下文件是否存在。

bool OatFileAssistant::GenerateOatFile(std::string* error_msg) {
  ......
  std::vector<std::string> args;
  args.push_back("--dex-file=" + std::string(dex_location_));
  args.push_back("--oat-file=" + oat_file_name);
  ......
  if (!OS::FileExists(dex_location_)) {
    ......
    return false;
  }
  if (!Dex2Oat(args, error_msg)) {
    ......
  }
  ......
  return true;
}

OatFileAssistant::Dex2Oat

argv是此處的關鍵,不光會傳入dst,src等基本信息,還會有諸如debugger,classpath等信息

bool OatFileAssistant::Dex2Oat(const std::vector<std::string>& args,
                               std::string* error_msg) {
  ......
  std::vector<std::string> argv;
  argv.push_back(runtime->GetCompilerExecutable());
  argv.push_back("--runtime-arg");
  ......
  return Exec(argv, error_msg);
}

至於文末的Exec,則會來到art/runtime/utils.cc
bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg)

Exec

這個函數的重點是在fork,而fork以後會在子進程執行arg帶入的第一個參數指定的那個可執行文件。
而在fork的父進程,則會繼續監聽子進程的運行狀態並一直wait,因此對於上層來講..這邊就是一個同步調用了。

bool Exec(std::vector<std::string>& arg_vector, std::string* error_msg) {
  ......
  const char* program = arg_vector[0].c_str();
  ......
  pid_t pid = fork();
  if (pid == 0) {
    ......
    execv(program, &args[0]);
    ......
  } else {
    ......
    pid_t got_pid = TEMP_FAILURE_RETRY(waitpid(pid, &status, 0));
    ......
  return true;
}

再回頭看一下argv的第一個參數:argv.push_back(runtime->GetCompilerExecutable());
因此..若是非debug狀態下,咱們用到可執行文件就是:/bin/dex2oat

std::string Runtime::GetCompilerExecutable() const {
  if (!compiler_executable_.empty()) {
    return compiler_executable_;
  }
  std::string compiler_executable(GetAndroidRoot());
  compiler_executable += (kIsDebugBuild ? "/bin/dex2oatd" : "/bin/dex2oat");
  return compiler_executable;
}

因此到這邊,咱們大體就瞭解了dex文件是如何轉換到oat文件的,因此也就是回答了最先提出的問題2。
從Exec返回以後,咱們還會繼續去檢查一下oat文件是否真的弄好了,若是一切正常,最後就會經過

RegisterOatFile(source_oat_file);

把對應的oat file加入到ClassLinker的成員變量:oat_files_,下次就不用繼續執行dex2oat了。

OatFileAssistant::LoadDexFiles

對於這一段的flow我以前一直很不解,爲何以前要把dex轉換成oat,這邊看起來又要經過oat去獲取dex?
其實簡單來想這裏應該是兩種狀況:

  1. 首先是經過dex拿到oat文件

  2. 構造dex structure給上層
    因此光從字面意義來解釋這個函數,就會讓人進入一種誤區,咱們來看code:

std::vector<std::unique_ptr<const DexFile>> OatFileAssistant::LoadDexFiles(
    const OatFile& oat_file, const char* dex_location) {
  std::vector<std::unique_ptr<const DexFile>> dex_files;

  // Load the primary dex file.
  ......
  const OatFile::OatDexFile* oat_dex_file = oat_file.GetOatDexFile(
      dex_location, nullptr, false);
  ......
  std::unique_ptr<const DexFile> dex_file = oat_dex_file->OpenDexFile(&error_msg);
  ......
  dex_files.push_back(std::move(dex_file));

  // Load secondary multidex files
  for (size_t i = 1; ; i++) {
    std::string secondary_dex_location = DexFile::GetMultiDexLocation(i, dex_location);
    oat_dex_file = oat_file.GetOatDexFile(secondary_dex_location.c_str(), nullptr, false);
    ......
    dex_file = oat_dex_file->OpenDexFile(&error_msg);
    ......
  }
  return dex_files;
}

這裏的實現看起來比較複雜,但其實就是:

  1. 分別load primary dex和secondary multidex

  2. 再經過OpenDexFile方法得到DexFile對象。

由於對於oat文件來講,它能夠是由多個dex產生的,詳細的狀況能夠參考老羅的文章,好比boot.oat。

因此,咱們須要看看這邊OpenDexFile到底作了什麼:

std::unique_ptr<const DexFile> OatFile::OatDexFile::OpenDexFile(std::string* error_msg) const {
  return DexFile::Open(dex_file_pointer_, FileSize(), dex_file_location_,
                       dex_file_location_checksum_, this, error_msg);
}

其中dex_file_pointer_是oat文件中dex數據的起始地址(DexFile Meta段)

DexFile::Open

上文提到的Open原型是:

static std::unique_ptr<const DexFile> Open(const uint8_t* base, size_t size,
                                             const std::string& location,
                                             uint32_t location_checksum,
                                             const OatDexFile* oat_dex_file,
                                             std::string* error_msg) {
    return OpenMemory(base, size, location, location_checksum, nullptr, oat_dex_file, error_msg);
  }

繼續trace一下:

std::unique_ptr<const DexFile> DexFile::OpenMemory(const uint8_t* base,
                                                   size_t size,
                                                   const std::string& location,
                                                   uint32_t location_checksum,
                                                   MemMap* mem_map,
                                                   const OatDexFile* oat_dex_file,
                                                   std::string* error_msg) {
  CHECK_ALIGNED(base, 4);  // various dex file structures must be word aligned
  std::unique_ptr<DexFile> dex_file(
      new DexFile(base, size, location, location_checksum, mem_map, oat_dex_file));
  if (!dex_file->Init(error_msg)) {
    dex_file.reset();
  }
  return std::unique_ptr<const DexFile>(dex_file.release());
}

最後看一下構造函數:

DexFile::DexFile(const uint8_t* base, size_t size,
                 const std::string& location,
                 uint32_t location_checksum,
                 MemMap* mem_map,
                 const OatDexFile* oat_dex_file)
    : begin_(base),
      size_(size),
      location_(location),
      location_checksum_(location_checksum),
      mem_map_(mem_map),
      header_(reinterpret_cast<const Header*>(base)),
      string_ids_(reinterpret_cast<const StringId*>(base + header_->string_ids_off_)),
      type_ids_(reinterpret_cast<const TypeId*>(base + header_->type_ids_off_)),
      field_ids_(reinterpret_cast<const FieldId*>(base + header_->field_ids_off_)),
      method_ids_(reinterpret_cast<const MethodId*>(base + header_->method_ids_off_)),
      proto_ids_(reinterpret_cast<const ProtoId*>(base + header_->proto_ids_off_)),
      class_defs_(reinterpret_cast<const ClassDef*>(base + header_->class_defs_off_)),
      find_class_def_misses_(0),
      class_def_index_(nullptr),
      oat_dex_file_(oat_dex_file) {
  CHECK(begin_ != nullptr) << GetLocation();
  CHECK_GT(size_, 0U) << GetLocation();
}

其實就是一些數據的填充,而後再打包傳回到上層(java端),丟該DexFile的mCookie對象。
:)

寫在最後

整個classloader的部分到這裏就算走完了,可是對於android art 來講仍是冰山一角。

相關文章
相關標籤/搜索