(这里-t 为老的apk -f为修复过的apk,-o 为输出目录 -k 为打包的key -p -e 为密码 -a 为别名)
其实这个工具就是比对两个dex文件,分析出修改过的地方,然后生成补丁包
@Override
public void onCreate() {
super.onCreate();
// 初始化patch管理类
mPatchManager = new PatchManager(this);
// 初始化patch版本
mPatchManager.init("2.0");
// 加载已经添加到PatchManager中的patch
mPatchManager.loadPatch();
}
public PatchManager(Context context) {
mContext = context;
mAndFixManager = new AndFixManager(mContext); //初始化AndFixManager,等会再介绍
//getFileDir 获取的是/data/data//files
//在这里是 /data/data//files/apatch
mPatchDir = new File(mContext.getFilesDir(), DIR);
// 支持并发访问的有序的补丁集合
mPatchs = new ConcurrentSkipListSet();
// ClassLoader的集合,同样也是基于线程安全的
mLoaders = new ConcurrentHashMap();
}
public void init(String appVersion) {
//再次检测patch存放路径
if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail
Log.e(TAG, "patch dir create error.");
return;
} else if (!mPatchDir.isDirectory()) {// not directory
mPatchDir.delete();
return;
}
//用SharedPreferences 获取path的版本
SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
Context.MODE_PRIVATE);
String ver = sp.getString(SP_VERSION, null);
if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
//如果是第一次使用或者版本不一致,则删除所有Patch ,这里equalsIgnoreCase是忽略大小写的equals
cleanPatch();
// 存放版本
sp.edit().putString(SP_VERSION, appVersion).commit();
} else {
// 加载有所的Patch
initPatchs();
}
}
private void initPatchs() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
addPatch(file);
}
}
/**
* load patch,call when application start
* 这里写的也很清楚了,在程序启动的时候调用
*/
public void loadPatch() {
//首先加载了通用的类加载器
mLoaders.put("*", mContext.getClassLoader());// wildcard
Set patchNames;
List classes;
//遍历每个补丁包 Patch 的结构HashMap>()
//实际中,只会有一个key,就是你修改后apk的名字,list中存放修改的className
//fix----[cv.cocoa.com.andfixdemo.MainActivity_CF]
for (Patch patch : mPatchs) {
patchNames = patch.getPatchNames();
for (String patchName : patchNames) {
classes = patch.getClasses(patchName);
//更新补丁
mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
classes);
}
}
}
public class Compat {
public static boolean isChecked = false;
public static boolean isSupport = false;
/**
* whether support on the device
* 需要对兼容性进行检测,检测的判断是不能是YunOs的手机,sdk的版本必须是在2.3-6.0之间
* @return true if the device support AndFix
*/
public static synchronized boolean isSupport() {
if (isChecked)
return isSupport;
isChecked = true;
// AndFix.setup()判断是Dalvik还是Art虚拟机,来注册Native方法
if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) {
isSupport = true;
}
if (inBlackList()) {
isSupport = false;
}
return isSupport;
}
}
/**
* initialize
*
* @return true if initialize success
*
* 判断是Dalvik还是Art虚拟机,并初始化native的方法,
*
* 官方文档中说到https://developer.android.com/guide/practices/verifying-apps-art.html
* You can verify which runtime is in use by calling System.getProperty("java.vm.version"). If ART is in use, the property's value is "2.0.0" or higher.
*
*/
public static boolean setup() {
try {
final String vmVersion = System.getProperty("java.vm.version");
boolean isArt = vmVersion != null && vmVersion.startsWith("2");
int apilevel = Build.VERSION.SDK_INT;
return setup(isArt, apilevel);
} catch (Exception e) {
Log.e(TAG, "setup", e);
return false;
}
}
//最关键的就是这里,获取到Class对象后,用反射获取修改的方法
private void fixClass(Class> clazz, ClassLoader classLoader) {
//java反射获取Class的方法
Method[] methods = clazz.getDeclaredMethods();
MethodReplace methodReplace;
String clz;
String meth;
for (Method method : methods) {
// 找到MethodReplace的注解
methodReplace = method.getAnnotation(MethodReplace.class);
if (methodReplace == null)
continue;
//对照上面的demo,clz就是cv.cocoa.com.andfixdemo.MainActivity
clz = methodReplace.clazz();
//对照上面的demo,meth 就是showToast
meth = methodReplace.method();
if (!isEmpty(clz) && !isEmpty(meth)) {
//替换方法
replaceMethod(classLoader, clz, meth, method);
}
}
}
/**
* replace method
*
* @param classLoader classloader
* @param clz class
* @param meth name of target method
* @param method source method
*/
private void replaceMethod(ClassLoader classLoader, String clz,
String meth, Method method) {
try {
String key = clz + "@" + classLoader.toString();
Class> clazz = mFixedClass.get(key);
if (clazz == null) {// class not load
Class> clzz = classLoader.loadClass(clz);
// initialize target class
clazz = AndFix.initTargetClass(clzz);
}
if (clazz != null) {// initialize class OK
mFixedClass.put(key, clazz);
Method src = clazz.getDeclaredMethod(meth,
method.getParameterTypes());
//调用jni的方法
AndFix.addReplaceMethod(src, method);
}
} catch (Exception e) {
Log.e(TAG, "replaceMethod", e);
}
}
AndFix 支持 ART 和Dalvik , 同是art 又区分为5.0,5.1,6.0, 这里主要使用 AndFix类中 setup()的方法,然后再调用andfix.cpp 中的方法
//jni层的setup
//isart
//apilevel
static jboolean setup(JNIEnv* env, jclass clazz, jboolean isart,
jint apilevel) {
isArt = isart;
LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"),
(int )apilevel);
//判断是否art
if (isArt) {
return art_setup(env, (int) apilevel);
} else {
return dalvik_setup(env, (int) apilevel);
}
}
/*
* 注册jni
*/
static JNINativeMethod gMethods[] = {
/* name, signature, funcPtr */
{ "setup", "(ZI)Z", (void*) setup }, { "replaceMethod",
"(Ljava/lang/reflect/Method;Ljava/lang/reflect/Method;)V",
(void*) replaceMethod }, { "setFieldFlag",
"(Ljava/lang/reflect/Field;)V", (void*) setFieldFlag }, };
参考 :http://blog.csdn.net/com360/article/details/46671315
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
JNIEnv* env, int apilevel) {
//Davik虚拟机实现 是在libdvm.so中
//dlopen()函数以指定模式打开指定的动态链接库文件,并返回dalvik的句柄
void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
if (dvm_hand) {
//调用dvm_dlsym 获取dvmDecodeIndirectRef 的函数指针
dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ?
"_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
"dvmDecodeIndirectRef");
if (!dvmDecodeIndirectRef_fnPtr) {
return JNI_FALSE;
}
dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
if (!dvmThreadSelf_fnPtr) {
return JNI_FALSE;
}
jclass clazz = env->FindClass("java/lang/reflect/Method");
jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
"()Ljava/lang/Class;");
return JNI_TRUE;
} else {
return JNI_FALSE;
}
}
extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
JNIEnv* env, jobject src, jobject dest) {
jobject clazz = env->CallObjectMethod(dest, jClassMethod);
ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
dvmThreadSelf_fnPtr(), clazz);
clz->status = CLASS_INITIALIZED;
//FromReflectedMethod转换一个java.lang.reflect.Method或java.lang.reflect.Constructor对象到一个方法ID。
Method* meth = (Method*) env->FromReflectedMethod(src);
Method* target = (Method*) env->FromReflectedMethod(dest);
LOGD("dalvikMethod: %s", meth->name);
meth->clazz = target->clazz;
meth->accessFlags |= ACC_PUBLIC;
meth->methodIndex = target->methodIndex;
meth->jniArgInfo = target->jniArgInfo;
meth->registersSize = target->registersSize;
meth->outsSize = target->outsSize;
meth->insSize = target->insSize;
meth->prototype = target->prototype;
meth->insns = target->insns;
meth->nativeFunc = target->nativeFunc;
}