/* * Copyright (C) 2015 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tools.fd.runtime; import android.util.Log; import com.android.annotations.NonNull; import dalvik.system.BaseDexClassLoader; import java.io.File; import java.lang.reflect.Field; import java.util.List; import static com.android.tools.fd.runtime.BootstrapApplication.LOG_TAG; // This is based on com.google.devtools.build.android.incrementaldeployment.IncrementalClassLoader // with some cleanup around path handling and made it compile on JDK 6 (e.g. removed multicatch // etc) // See // https://github.com/google/bazel/blob/master/src/tools/android/java/com/google/devtools/build/android/incrementaldeployment/IncrementalClassLoader.java // (May 11 revision, ca96e11) /** * A class loader that loads classes from any .dex file in a particular directory on the SD card. * <p> * <p>Used to implement incremental deployment to Android phones. * * IncrementalClassLoader 主要提供了静态方法 inject(ClassLoader classLoader, * String nativeLibraryPath, * String codeCacheDir, * List<String> dexes) * * 方便将目标 classLoader 的 parent 替换为 IncrementalClassLoader * 这样的话 parent 的父加载的 class 和 res 就走的是 IncrementalClassLoader 的加载 * BootClassLoader -> classLoader 就会变为 BootClassLoader -> IncrementalClassLoader -> classLoader * * 然而 IncrementalClassLoader 的加载的逻辑又靠 DelegateClassLoader * DelegateClassLoader 是 BaseDexClassLoader 的子类 * 覆写了 findClass 方法,但是,只是为了打点 Log * * 可以理解 IncrementalClassLoader 就是 插件 class 的 classLoader */ public class IncrementalClassLoader extends ClassLoader { /** When false, compiled out of runtime library */ public static final boolean DEBUG_CLASS_LOADING = false; private final DelegateClassLoader delegateClassLoader; /** * 为了保证和 要 Hook 的 classLoader 保持一致的 父 classLoader,特意用了 super(original.getParent()) 去实例化 * 一个和 要 Hook 的 classLoader 一样的 父 classLoader * 然后执行 静态工厂方法 createDelegateClassLoader 去 构造一个 DelegateClassLoader * * 比如 original 的关系如下:BootClassLoader -> original * 正常的话,我们拿到 original 去实例化一个 classLoader 会如下情况: * BootClassLoader -> original —> classLoader * 但是这里执行的是 super(original.getParent()) 而不是 super(original) * * 所以这里得到的是 BootClassLoader -> classLoader * * @param original hook 的 classLoader * @param nativeLibraryPath 本地 lib 路径 * @param codeCacheDir 项目 cache 文件夹路径 * @param dexes dex 的所有路径 */ public IncrementalClassLoader( ClassLoader original, String nativeLibraryPath, String codeCacheDir, List<String> dexes) { super(original.getParent()); // TODO(bazel-team): For some mysterious reason, we need to use two class loaders so that // everything works correctly. Investigate why that is the case so that the code can be // simplified. delegateClassLoader = createDelegateClassLoader(nativeLibraryPath, codeCacheDir, dexes, original); } @Override public Class<?> findClass(String className) throws ClassNotFoundException { try { Class<?> aClass = delegateClassLoader.findClass(className); //noinspection PointlessBooleanExpression,ConstantConditions if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "Incremental class loader: findClass(" + className + ") = " + aClass); } return aClass; } catch (ClassNotFoundException e) { //noinspection PointlessBooleanExpression,ConstantConditions if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "Incremental class loader: findClass(" + className + ") : not found"); } throw e; } } /** * 代理 ClassLoader * 没有做任何功能上的扩展 * 主要为了想在 findClass 里打点 Log * * A class loader whose only purpose is to make {@code findClass()} public. */ private static class DelegateClassLoader extends BaseDexClassLoader { private DelegateClassLoader( String dexPath, File optimizedDirectory, String libraryPath, ClassLoader parent) { super(dexPath, optimizedDirectory, libraryPath, parent); } /** * 代理 BaseDexClassLoader 的 findClass(String name) 方法 * 主要就是想打点 Log * * @param name 类名 * @return Class * @throws ClassNotFoundException */ @Override public Class<?> findClass(String name) throws ClassNotFoundException { try { Class<?> aClass = super.findClass(name); //noinspection PointlessBooleanExpression,ConstantConditions if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "Delegate class loader: findClass(" + name + ") = " + aClass); } return aClass; } catch (ClassNotFoundException e) { //noinspection PointlessBooleanExpression,ConstantConditions if (DEBUG_CLASS_LOADING && Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "Delegate class loader: findClass(" + name + ") : not found"); } throw e; } } } /** * 静态工厂方法,构建一个 DelegateClassLoader 实例 * * @param nativeLibraryPath 本地 lib 路径 * @param codeCacheDir 项目 cache 文件夹路径 * @param dexes dex 的所有路径 * @param original 父 classloader(双亲机制需要) * @return DelegateClassLoader */ private static DelegateClassLoader createDelegateClassLoader( String nativeLibraryPath, String codeCacheDir, List<String> dexes, ClassLoader original) { String pathBuilder = createDexPath(dexes); return new DelegateClassLoader(pathBuilder, new File(codeCacheDir), nativeLibraryPath, original); } /** * 拿到一组 dex 的路径,合成一个 String 数据 * * @param dexes 一组 dex 的路径 * @return String */ @NonNull private static String createDexPath(List<String> dexes) { StringBuilder pathBuilder = new StringBuilder(); boolean first = true; for (String dex : dexes) { // 最后一个不加分隔符 "/" if (first) { first = false; } else { pathBuilder.append(File.pathSeparator); } pathBuilder.append(dex); } if (Log.isLoggable(LOG_TAG, Log.VERBOSE)) { Log.v(LOG_TAG, "Incremental dex path is " + BootstrapApplication.join('\n', dexes)); } return pathBuilder.toString(); } /** * Hook ClassLoader 的 parent,由于 ClassLoader 的 parent 的只涉及到加载 class 和 res,所有 hook 了加载机制 * newParent 一般都会是通过 BootClassloader 实例化出来的,所以 BootClassloader -> newParent * Hook 之后变为 * BootClassloader -> classLoader >>>>>> BootClassloader -> newParent -> classLoader * * @param classLoader hook 目标 classLoader * @param newParent 需要作为 classLoader 的 parent classLoader 去加载 class 和 res */ private static void setParent(ClassLoader classLoader, ClassLoader newParent) { try { Field parent = ClassLoader.class.getDeclaredField("parent"); parent.setAccessible(true); parent.set(classLoader, newParent); } catch (IllegalArgumentException e) { throw new RuntimeException(e); } catch (IllegalAccessException e) { throw new RuntimeException(e); } catch (NoSuchFieldException e) { throw new RuntimeException(e); } } /** * 静态工厂方法,通过 classLoader + nativeLibraryPath + codeCacheDir + dexes = incrementalClassLoader * incrementalClassLoader 的构造方法指明了,会拿到 classLoader 的 parent 去作为 incrementalClassLoader 的 parent * 去实例化 incrementalClassLoader。所以是 BootClassLoader -> incrementalClassLoader。 * 然后 classLoader 的关系是 BootClassLoader -> classLoader * 执行 setParent 后:BootClassLoader -> incrementalClassLoader -> classLoader * * @param classLoader 需要 hook 的 classLoader * @param nativeLibraryPath 本地 lib 路径 * @param codeCacheDir 项目 cache 文件夹路径 * @param dexes dex 的所有路径 * @return hook 后的 classLoader 的 parent(incrementalClassLoader) */ public static ClassLoader inject( ClassLoader classLoader, String nativeLibraryPath, String codeCacheDir, List<String> dexes) { IncrementalClassLoader incrementalClassLoader = new IncrementalClassLoader(classLoader, nativeLibraryPath, codeCacheDir, dexes); setParent(classLoader, incrementalClassLoader); // This works as follows: // We're given the current class loader that's used to load the bootstrap application. // We have a new class loader which reads patches/overrides from the data directory // instead. We want *that* class loader to have the bootstrap class loader's parent // as its parent, and then we make the bootstrap class loader parented by our // class loader. // // In other words, we have this: // BootstrapApplication.classLoader = ClassLoader1, parent=ClassLoader2 // We create ClassLoader3 from the .dex files in the data directory, and arrange for // the hierarchy to be like this: // BootstrapApplication.classLoader = ClassLoader1, parent=ClassLoader3, parent=ClassLoader2 // With this approach, a class find (which should always look at the parents first) should // find anything from ClassLoader3 before they get them from ClassLoader1. // (Note that ClassLoader2 in the above is generally the BootClassLoader, not containing // any classes we care about.) return incrementalClassLoader; } }