ホットリペアクラスローディングスキームの原理を理解するための記事!

ClassLoaderタイプ

Javaでは、  ClassLoader jarファイルとClassファイルをロードできます(基本的にはClassファイルをロードします)。DVMとARTの両方がClassファイルではなく、dexファイルであるため、これはAndroidには適用されません。

AndroidのClassLoader タイプはJavaの タイプと ClassLoader 似ており、系统 ClassLoader との2つのタイプにも分けられ自定义 ClassLoaderます。その中でも、Androidは 系统 ClassLoader すなわち、3種類が含まれ BootClassLoaderPathClassLoaderそして 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は ありません  また 、から継承され 、メソッドの実装も  にあります。PathClassLoaderoptimizedDirectory/data/dalvik-cacheDexClassLoaderBaseDexClassLoaderBaseDexClassLoader

上記ClassLoader のAndroidシステムでの作成プロセスについては Zygote 、この記事の焦点では​​ないプロセスがここ に含まれているため、ここでは説明しません。

ClassLoaderの継承関係

image.png

  • ClassLoaderClassLoader 主な機能が 定義されている抽象クラス です。BootClassLoader その内部クラスです。
  • SecureClassLoaderクラスと JDK8 の SecureClassLoader クラスのコードは同じで、抽象クラスを継承し ClassLoaderます。 クラスの実現でSecureClassLoader はなく ClassLoaderClassLoader 権限の機能に加わり、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 ClassLoaderClassLoaderfindBootstrapClassOrNull

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

JDKfindBootstrapClassOrNull 会議は最終的に引き渡されます 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ファイルをロードできます
  • DexClassLoaderdex/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関連のファイルをに保存されます  のプロパティ オブジェクト BaseDexClassLoaderpathListdexElements

次に、ホットフィックスの原則は、修正されたバグのdex関連ファイルを dexElements コレクションの先頭に配置することです。これにより、トラバース時に、クラスローダーの親委任モードが古いdexであるため、最初に修復されたdexをトラバースし、修復されたクラスを見つけます。バグのあるクラスはプレイする機会がありません。このようにして、新しいバージョンをリリースせずに既存のバグクラスを修正することが可能です。

ホットリペア機能を手動で実装する

上記の熱間修理の原則によれば、対応するアイデアは次のように要約できます。

  1. BaseDexClassLoader サブクラス DexClassLoader ローダーを作成する 
  2. 修復されたclass.dex(サーバーによってダウンロードされた修復パッケージ)をロードします
  3. 独自とシステムを dexElements 組み合わせdexElements 、自由の優先順位を設定する 
  4. システムに割り当てられた反射技術を介して pathList

おすすめ

転載: blog.csdn.net/Java_Yhua/article/details/110223104