/* * 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 java.lang.reflect.Field; import java.util.logging.Level; import static com.android.tools.fd.common.Log.logging; /** * Instant Run 生成的 AppPatchesLoaderImpl 的 父类 * * 主要用于:Hook 被修改的 类名 的 $change Field * * 1. 遍历所有 被修改的 类名 * 2. 拼接出 ???$override 类型 * 3. 通过 ClassLoader 加载 ???$override 类 * 4. 反射实例化一个 ???$override 类 的实例 * 5. 加载 被修改的 类 * 6. 反射 被修改的 类 的 $change Field * 7. 反射获取 被修改的 类 的 $change Field 的值 * 8. 判断 被修改的 类 的 $change Field 的值 * - 8.1 如果存在值,反射获取其 $obsolete Field,如果不为 null,则设置为 true * 9. HOOK 被修改的 类 的 $change Field = 4. 实例化好的 ???$override 类 * 10. 如果这写过程中抛出异常,返回 false。否则,返回 true */ public abstract class AbstractPatchesLoaderImpl implements PatchesLoader { /** * 拿到所有 被修改的 类名 * * @return 被修改的 类名 集合 */ public abstract String[] getPatchedClasses(); /** * Hook 被修改的 类名 的 $change Field * * 1. 遍历所有 被修改的 类名 * 2. 拼接出 ???$override 类型 * 3. 通过 ClassLoader 加载 ???$override 类 * 4. 反射实例化一个 ???$override 类 的实例 * 5. 加载 被修改的 类 * 6. 反射 被修改的 类 的 $change Field * 7. 反射获取 被修改的 类 的 $change Field 的值 * 8. 判断 被修改的 类 的 $change Field 的值 * - 7.1 如果存在值,反射获取其 $obsolete Field,如果不为 null,则设置为 true * 9. HOOK 被修改的 类 的 $change Field = 4. 实例化好的 ???$override 类 * 10. 如果这写过程中抛出异常,返回 false。否则,返回 true * * @return 加载成功 or 加载失败 */ @Override public boolean load() { try { for (String className : getPatchedClasses()) { ClassLoader cl = getClass().getClassLoader(); Class<?> aClass = cl.loadClass(className + "$override"); Object o = aClass.newInstance(); Class<?> originalClass = cl.loadClass(className); Field changeField = originalClass.getDeclaredField("$change"); // force the field accessibility as the class might not be "visible" // from this package. changeField.setAccessible(true); // If there was a previous change set, mark it as obsolete: Object previous = changeField.get(null); if (previous != null) { Field isObsolete = previous.getClass().getDeclaredField("$obsolete"); if (isObsolete != null) { isObsolete.set(null, true); } } changeField.set(null, o); if (logging != null && logging.isLoggable(Level.FINE)) { logging.log(Level.FINE, String.format("patched %s", className)); } } } catch (Exception e) { if (logging != null) { logging.log(Level.SEVERE, String.format("Exception while patching %s", "foo.bar"), e); } return false; } return true; } }