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