原理:
修復過程:
原始碼解析過程如下:
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和虛擬機器型別有關。
原始碼路徑:github.com/alibaba/And…