原理:

修复过程:

源码解析过程如下:
patchManager=newPatchManager(context);
patchManager.init(appversion);//current version
patchManager做了以下工作:
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">public PatchManager(Context context) {
mContext = context;
mAndFixManager = new AndFixManager(mContext);
mPatchDir = new File(mContext.getFilesDir(), *DIR*);
mPatchs = new ConcurrentSkipListSet<Patch>();
mLoaders = new ConcurrentHashMap<String, ClassLoader>();
}</pre>
new了个AndFixmanager,看一下
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">public AndFixManager(Context context) {
mContext = context;
mSupport = Compat.*isSupport*();
if (mSupport) {
mSecurityChecker = new SecurityChecker(mContext);
mOptDir = new File(mContext.getFilesDir(), *DIR*);
if (!mOptDir.exists() && !mOptDir.mkdirs()) {// make directory fail
mSupport = false;
Log.*e*(*TAG*, "opt dir create error.");
} else if (!mOptDir.isDirectory()) {// not directory
mOptDir.delete();
mSupport = false;
}
}
}</pre>
在这个类里面主要是检测设备是否是支持的AndFix的设备,YunOS不支持。
在SecurityChecker里面获取数字证书和检测应用是否debugable
另外是初始化patch路径
PachManager.init()里面获取AndFix的版本信息,如果信息一致
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">public void init(String appVersion) {
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 sp = mContext.getSharedPreferences(*SP_NAME*,
Context.*MODE_PRIVATE*);
String ver = sp.getString(*SP_VERSION*, null);
if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
cleanPatch();
sp.edit().putString(*SP_VERSION*, appVersion).commit();
} else {
initPatchs();
}
}</pre>
则initPatchs
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">private void initPatchs() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
addPatch(file);
}
}</pre>
把路径下的patch加到mPatch列表里面。
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">patchManager.loadPatch();</pre>
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">public void loadPatch() {
mLoaders.put("*", mContext.getClassLoader());// wildcard
Set<String> patchNames;
List<String> classes;
for (Patch patch : mPatchs) {
patchNames = patch.getPatchNames();
for (String patchName : patchNames) {
classes = patch.getClasses(patchName);
mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
classes);
}
}
}</pre>
最终调用fix方法
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
classes);</pre>
fix方法首先签名验证,一般是文件的MD5,通过之后获取dex文件,
然后实现自己的加载器(只需要继承ClassLoader,并覆盖findClass方法)。
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">ClassLoader patchClassLoader = new ClassLoader(classLoader) {
@Override
protected Class<?> findClass(String className)
throws ClassNotFoundException {
Class<?> clazz = dexFile.loadClass(className, this);
if (clazz == null
&& className.startsWith("com.alipay.euler.andfix")) {
return Class.*forName*(className);// annotation’s class
// not found
}
if (clazz == null) {
throw new ClassNotFoundException(className);
}
return clazz;
}
};
Enumeration<String> entrys = dexFile.entries();
Class<?> clazz = null;
while (entrys.hasMoreElements()) {
String entry = entrys.nextElement();
if (classes != null && !classes.contains(entry)) {
continue;// skip, not need fix
}
clazz = dexFile.loadClass(entry, patchClassLoader);
if (clazz != null) {
fixClass(clazz, classLoader);
}
}
} catch (IOException e) {
Log.e(*TAG*, "pacth", e);
}</pre>
在类加载器里面,如果知道需要修改的方法(annotation标记的),则调用fixClass去修复bug.
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">private void fixClass(Class<?> clazz, ClassLoader classLoader) {
Method[] methods = clazz.getDeclaredMethods();
MethodReplace methodReplace;
String clz;
String meth;
for (Method method : methods) {
methodReplace = method.getAnnotation(MethodReplace.class);
if (methodReplace == null)
continue;
clz = methodReplace.clazz();
meth = methodReplace.method();
if (!*isEmpty*(clz) && !*isEmpty*(meth)) {
replaceMethod(classLoader, clz, meth, method);
}
}
}</pre>
在fixClass里面调用replaceMethod方法用patch里面的方法替换掉要修改的方法。
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">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());
AndFix.*addReplaceMethod*(src, method);
}
} catch (Exception e) {
Log.e(*TAG*, "replaceMethod", e);
}
}</pre>
最终调用native层的方法
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">AndFix.*addReplaceMethod*(src, method);</pre>
<pre style="margin: 0px; padding: 0px; font-size: 0.85em; font-family: monospace, serif; overflow: auto; line-height: 1.45; background-color: rgb(248, 248, 248); border-radius: 0.5em; position: relative; border: 1px solid rgb(238, 238, 238);">public static void addReplaceMethod(Method src, Method dest) {
try {
*replaceMethod*(src, dest);
*initFields*(dest.getDeclaringClass());
} catch (Throwable e) {
Log.e(*TAG*, "addReplaceMethod", e);
}
}</pre>
Native里面的replaceMethod和虚拟机类型有关。
源码路径:https://github.com/alibaba/AndFix
最后介绍个QQ群:979045005,Android开发的朋友可以加一下,有什么新技术大家一起交流学习一下,整理了一些干货,需要的话,可以进群找管理免费领取,不多说直接上图吧!

网友评论