ClassLoaderタイプ
Javaでは、 ClassLoader
jarファイルとClassファイルをロードできます(基本的にはClassファイルをロードします)。DVMとARTの両方がClassファイルではなく、dexファイルであるため、これはAndroidには適用されません。
AndroidのClassLoader
タイプはJavaの タイプと ClassLoader
似ており、系统 ClassLoader
との2つのタイプにも分けられ自定义 ClassLoader
ます。その中でも、Androidは 系统 ClassLoader
すなわち、3種類が含まれ BootClassLoader
、PathClassLoader
そして DexClassLoader
、およびJavaシステムクラスローダはまた、すなわち、3種類が含まれ Bootstrap ClassLoader
、 Extensions ClassLoader
そして App ClassLoader
。
BootClassLoader
Androidシステムが起動すると、BootClassLoader
共通クラスのプリロードに使用さ れます。Javaとは異なり、 BootClassLoader
C / C ++コードではなく、Javaによって実装されます。BootClassLoade
コードは次のとおりです。
// libcore/ojluni/src/main/java/java/lang/ClassLoader.java
class BootClassLoader extends ClassLoader {
private static BootClassLoader instance;
@FindBugsSuppressWarnings("DP_CREATE_CLASSLOADER_INSIDE_DO_PRIVILEGED")
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);
}
...
}
BootClassLoader
はい、 ClassLoader
内部クラスであり、から継承され ClassLoader
ます。 BootClassLoader
これはシングルトンクラスです。アクセス修飾子はデフォルトであり、同じパッケージ内でのみアクセスできるため、アプリケーションで直接呼び出すことはできないことに注意し **BootClassLoader**
てください。
PathClassLoader
Androidシステムは、PathClassLoader
システムクラスとアプリケーションクラスのロードに使用さ れます。非システムアプリケーションクラスをロードする場合はdata/app/$packagename
、dexファイルとdexを含むapkファイルまたはjarファイルがロードされ ます。どのファイルがロードされても、最終的にはdexファイルをロードします。わかりやすくするために、dexファイルとdexを含むapkファイルまたはjarファイルをまとめてdex関連ファイルと呼びます。PathClassLoaderは、開発および直接使用にはお勧めしません。
// libcore/dalvik/src/main/java/dalvik/system/PathClassLoader.java
public class PathClassLoader extends BaseDexClassLoader {
public PathClassLoader(String dexPath, ClassLoader parent) {
super(dexPath, null, null, parent);
}
public PathClassLoader(String dexPath, String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
PathClassLoader
から継承された BaseDexClassLoader
、明らかな PathClassLoader
メソッドの実装はにあり BaseDexClassLoader
ます。
PathClassLoader
構築方法には3つのパラメータがあります。
- dexPath:dexファイルとapkファイルまたはdexを含むjarファイルのパスのコレクション。複数のパスはファイル区切り文字で区切られます。デフォルトのファイル区切り記号は「:」です。
- librarySearchPath:C / C ++ライブラリを含むパスのコレクション。複数のパスはファイル区切り文字で区切られます。ファイル区切り文字はnullにすることができます。
- 親:ClassLoader的親
DexClassLoader
DexClassLoader
dexファイルとdexを含むapkファイルまたはjarファイルをロードでき、SDカードからのロードもサポートしDexClassLoader
ます。つまり、アプリケーションがインストールされていないときにdex関連のファイルをロード できます。したがって、それはホットリペアとプラグインテクノロジーの基礎です。
public class DexClassLoader extends BaseDexClassLoader {
public DexClassLoader(String dexPath, String optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
super(dexPath, null, librarySearchPath, parent);
}
}
DexClassLoader
施工方法のパラメータが多い PathClassLoader
ものよりも optimizedDirectory
パラメータ。optimizedDirectory
どういうパラメータを 表しますか?アプリケーションを初めてロードするときは、将来の起動速度と実行効率を向上させるために、Androidシステムはdex関連ファイルをある程度最適化してファイルを生成し ODEX
、アプリケーションを再度実行するときにロードするだけです。最適化された ODEX
ファイルが機能し、毎回最適化する時間を節約します。パラメータ はoptimizedDirectory
ストレージODEX
ファイルのパスを表し 、このパスは内部ストレージパスである必要があります。 すでにデフォルトのパラメータ パスは次のとおりであるため、PathClassLoader
引数optimizedDirectory
は ありません 。 また 、から継承され 、メソッドの実装も にあります。PathClassLoader
optimizedDirectory
/data/dalvik-cache
DexClassLoader
BaseDexClassLoader
BaseDexClassLoader
上記ClassLoader
のAndroidシステムでの作成プロセスについては Zygote
、この記事の焦点ではないプロセスがここ に含まれているため、ここでは説明しません。
ClassLoaderの継承関係
ClassLoader
ClassLoader
主な機能が 定義されている抽象クラス です。BootClassLoader
その内部クラスです。SecureClassLoader
クラスとJDK8
のSecureClassLoader
クラスのコードは同じで、抽象クラスを継承しClassLoader
ます。 クラスの実現でSecureClassLoader
はなくClassLoader
、ClassLoader
権限の機能に加わり、ClassLoader
セキュリティを強化するための拡張 クラス です。URLClassLoader
クラスとJDK8
のURLClassLoader
クラスのコードは同じであり、それを継承しSecureClassLoader
、URLパスを介してjarファイルおよびフォルダーからクラスとリソースをロードするために使用されます。BaseDexClassLoader
から継承されるのClassLoader
は、抽象ClassLoader
クラスの具体的な実装クラスでありPathClassLoader
、DexClassLoader
どちらもそれを継承します。
Androidプログラムを実行するために必要ないくつかのタイプのクラスローダーを見てみましょう
class MainActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
var classLoader = this.classLoader
// 打印 ClassLoader 继承关系
while (classLoader != null) {
Log.d("MainActivity", classLoader.toString())
classLoader = classLoader.parent
}
}
}
MainActivity
印刷されたクラスローダ、および現在の親クラスローダローダの印刷、ローダーなし親、そしてサイクルが終了するまで。印刷結果は次のとおりです。
com.zhgqthomas.github.hotfixdemo D/MainActivity: dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk"],nativeLibraryDirectories=[/data/app/com.zhgqthomas.github.hotfixdemo-2/lib/arm64, /oem/lib64, /system/lib64, /vendor/lib64]]]
com.zhgqthomas.github.hotfixdemo D/MainActivity: java.lang.BootClassLoader@4d7e926
あなたは1つがあり、クラスローダの2種類があることがわかります PathClassLoader
他のです、 BootClassLoader
。 サンプルアプリケーションが電話にインストールされている場所DexPathList
を含め/data/app/com.zhgqthomas.github.hotfixdemo-2/base.apk
、その中には多くのパスがあります 。
親委任モデル
クラスローダーは、親委任モードを使用してクラスを検索します。いわゆる親委任モードでは、最初にクラスが読み込まれたかどうかを判断します。読み込まれていない場合は、それ自体では検索されず、親ローダーに検索を委託され、その後、次のように再帰的に検索されます。トップレベルに委託され、BootstrapClassLoader
クラスが見つかった場合は直接返され、見つからなかった場合は1つずつ見下ろし、見つからなかった場合は最終的に自分自身に渡されて見つかります。 これは、 実装ロジックJDKで、 点と相違点がある 中 でのメソッドのロジック処理のAndroidは 。BootstrapClassLoader
ClassLoader
ClassLoader
findBootstrapClassOrNull
// ClassLoader.java
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) {
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
}
}
return c;
}
上記のコードは理解しやすいです。まず、ロードされたクラスがロードされているかどうかを確認します。直接返される場合は、親ローダーに委任されて検索され、親ローダーがなくなるまでfindBootstrapClassOrNull
メソッドが呼び出され ます。
のは、見てみましょう 、それが実装されている方法findBootstrapClassOrNull
に JDK
して Android
、それぞれでの
// JDK ClassLoader.java
private Class<?> findBootstrapClassOrNull(String name)
{
if (!checkName(name)) return null;
return findBootstrapClass(name);
}
JDK
findBootstrapClassOrNull
会議は最終的に引き渡されます BootstrapClassLoader
見つけるために、 Class
ファイルをしたように、上記の BootstrapClassLoader
それは、findBootstrapClass
され、それはので、C ++で実装 ネイティブメソッドであります
// JDK ClassLoader.javaプライベートネイティブクラス<?> findBootstrapClass(String name);
Androidで findBootstrapClassOrNull
の実装 JDK
は異なります
// Android
private Class<?> findBootstrapClassOrNull(String name)
{
return null;
}
Android
使用する必要がないため、 BootstrapClassLoader
このメソッドは直接戻ります null
クラスローダーを使用して、クラスで採用されている親委任モードを見つけるので、クラスローダーがdex関連ファイルをロードする順序をリフレクションによって変更して、ホットリペアの目的を達成できます。
クラスの読み込みプロセス
上記の分析を通して、私たちは見ることができます
PathClassLoader
AndroidシステムにdexファイルをロードできますDexClassLoader
dex/zip/apk/jar
ファイルは 任意のディレクトリにロード できますが、指定する必要がありますoptimizedDirectory
。
継承されたばかりのコードで知られているこれらの2つのクラス BaseDexClassLoader
は、特定の実装がまだ BaseDexClassLoader
完全に行われています。
BaseDexClassLoader
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
public class BaseDexClassLoader extends ClassLoader {
...
private final DexPathList pathList;
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent) {
this(dexPath, optimizedDirectory, librarySearchPath, parent, false);
}
/**
* @hide
*/
public BaseDexClassLoader(String dexPath, File optimizedDirectory,
String librarySearchPath, ClassLoader parent, boolean isTrusted) {
super(parent);
this.pathList = new DexPathList(this, dexPath, librarySearchPath, null, isTrusted);
if (reporter != null) {
reportClassLoaderChain();
}
}
...
public BaseDexClassLoader(ByteBuffer[] dexFiles, ClassLoader parent) {
// TODO We should support giving this a library search path maybe.
super(parent);
this.pathList = new DexPathList(this, dexFiles);
}
...
}
BaseDexClassLoader
既知のコンストラクタ最も重要なことを初期化することがある pathList
ことがあり DexPathList
、主に文書を管理するために使用され、このクラス、DEX
// libcore/dalvik/src/main/java/dalvik/system/BaseDexClassLoader.java
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
List<Throwable> suppressedExceptions = new ArrayList<Throwable>();
Class c = pathList.findClass(name, suppressedExceptions); // 查找逻辑交给 DexPathList
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
最も重要なことは、この findClass
メソッドです。これは、対応するファイルをdexclass
ファイルにロードするために使用され ます。そして最終的にはDexPathList
、実装を処理するためにクラスに渡され ます findClass
DexPathList
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
final class DexPathList {
...
/** class definition context */
private final ClassLoader definingContext;
/**
* List of dex/resource (class path) elements.
* Should be called pathElements, but the Facebook app uses reflection
* to modify 'dexElements' (http://b/7726934).
*/
private Element[] dexElements;
...
DexPathList(ClassLoader definingContext, String dexPath,
String librarySearchPath, File optimizedDirectory, boolean isTrusted) {
...
this.definingContext = definingContext;
ArrayList<IOException> suppressedExceptions = new ArrayList<IOException>();
// save dexPath for BaseDexClassLoader
this.dexElements = makeDexElements(splitDexPath(dexPath), optimizedDirectory,
suppressedExceptions, definingContext, isTrusted);
...
}
}
探し DexPathList
コアコンストラクタのコードでは、私たちがいることを見ることができますDexPathList
クラスが Element
格納され dex 路径
てmakeDexElements
機能し、DEX関連ファイルがロードされて 機能、およびElement
コレクションがされて返さ
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.java
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;
/*
* Open all files and load the (direct or contained) dex files up front.
*/
for (File file : files) {
if (file.isDirectory()) {
// We support directories for looking up resources. Looking up resources in
// directories is useful for running libcore tests.
elements[elementsPos++] = new Element(file);
} else if (file.isFile()) {
String name = file.getName();
DexFile dex = null;
if (name.endsWith(DEX_SUFFIX)) { // 判断是否是 dex 文件
// Raw dex file (not inside a zip/jar).
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
if (dex != null) {
elements[elementsPos++] = new Element(dex, null);
}
} catch (IOException suppressed) {
System.logE("Unable to load dex file: " + file, suppressed);
suppressedExceptions.add(suppressed);
}
} else { // 如果是 apk, jar, zip 等文件
try {
dex = loadDexFile(file, optimizedDirectory, loader, elements);
} 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);
}
// 将 dex 文件或压缩文件包装成 Element 对象,并添加到 Element 集合中
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;
}
一般にDexPathList
、コンストラクターは、dex関連ファイル(dex、apk、jar、zipの場合があり、これらのタイプは最初に定義されElement
ます)をオブジェクトにカプセル化し 、最後にElement
コレクションに追加し ます。
実際、AndroidのクラスローダーはPathClassLoaderまたはDexClassLoaderのいずれかであり、最後のdexファイルのみを認識し loadDexFile
、dex jar、apk、zipから抽出できるdexファイルをロードするためのコアメソッドです。
// libcore/dalvik/src/main/java/dalvik/system/DexPathList.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;
}
DexPathList
コンストラクターdexElements
で初期化され ている ため、このメソッドは理解しやすいです。Element配列をトラバースするだけです。同じクラス名と名前のクラスが見つかると、このクラスを直接返します。見つからない場合は、nullを返します。
ホットフィックスの実装
上記の分析を通じて、我々は実行していることを知ることができる Android
プログラムが使用されて PathClassLoader
いること、 BaseDexClassLoader
、APK内のdex関連のファイルをに保存されます のプロパティ オブジェクト 。BaseDexClassLoader
pathList
dexElements
次に、ホットフィックスの原則は、修正されたバグのdex関連ファイルを
dexElements
コレクションの先頭に配置することです。これにより、トラバース時に、クラスローダーの親委任モードが古いdexであるため、最初に修復されたdexをトラバースし、修復されたクラスを見つけます。バグのあるクラスはプレイする機会がありません。このようにして、新しいバージョンをリリースせずに既存のバグクラスを修正することが可能です。
ホットリペア機能を手動で実装する
上記の熱間修理の原則によれば、対応するアイデアは次のように要約できます。
BaseDexClassLoader
サブクラスDexClassLoader
ローダーを作成する- 修復されたclass.dex(サーバーによってダウンロードされた修復パッケージ)をロードします
- 独自とシステムを
dexElements
組み合わせdexElements
、自由の優先順位を設定する - システムに割り当てられた反射技術を介して
pathList