package com.meituan.robust;
import android.content.Context;
import android.text.TextUtils;
import android.util.Log;
import java.lang.reflect.Field;
import java.util.List;
import dalvik.system.DexClassLoader;
/**
* Created by c_kunwu on 16/5/20.
* if you need,you can override PatchExecutor
*/
public class PatchExecutor extends Thread {
protected Context context;
protected PatchManipulate patchManipulate;
protected RobustCallBack robustCallBack;
public PatchExecutor(Context context, PatchManipulate patchManipulate, RobustCallBack robustCallBack) {
this.context = context.getApplicationContext();
this.patchManipulate = patchManipulate;
this.robustCallBack = robustCallBack;
}
@Override
public void run() {
try {
//拉取补丁列表
List<Patch> patches = fetchPatchList();
//应用补丁列表
applyPatchList(patches);
} catch (Throwable t) {
Log.e("robust", "PatchExecutor run", t);
robustCallBack.exceptionNotify(t, "class:PatchExecutor,method:run,line:36");
}
}
/**
* 拉取补丁列表
*/
protected List<Patch> fetchPatchList() {
return patchManipulate.fetchPatchList(context);
}
/**
* 应用补丁列表
*/
protected void applyPatchList(List<Patch> patches) {
if (null == patches || patches.isEmpty()) {
return;
}
Log.d("robust", " patchManipulate list size is " + patches.size());
for (Patch p : patches) {
if (p.isAppliedSuccess()) {
Log.d("robust", "p.isAppliedSuccess() skip " + p.getLocalPath());
continue;
}
if (patchManipulate.ensurePatchExist(p)) {
boolean currentPatchResult = false;
try {
currentPatchResult = patch(context, p);
} catch (Throwable t) {
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:applyPatchList line:69");
}
if (currentPatchResult) {
//设置patch 状态为成功
p.setAppliedSuccess(true);
//统计PATCH成功率 PATCH成功
robustCallBack.onPatchApplied(true, p);
} else {
//统计PATCH成功率 PATCH失败
robustCallBack.onPatchApplied(false, p);
}
Log.d("robust", "patch LocalPath:" + p.getLocalPath() + ",apply result " + currentPatchResult);
}
}
}
protected boolean patch(Context context, Patch patch) {
if (!patchManipulate.verifyPatch(context, patch)) {
robustCallBack.logNotify("verifyPatch failure, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:107");
return false;
}
DexClassLoader classLoader = new DexClassLoader(patch.getTempPath(), context.getCacheDir().getAbsolutePath(),
null, PatchExecutor.class.getClassLoader());
patch.delete(patch.getTempPath());
Class patchClass, oldClass;
Class patchsInfoClass;
PatchesInfo patchesInfo = null;
try {
Log.d("robust", "PatchsInfoImpl name:" + patch.getPatchesInfoImplClassFullName());
patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName());
patchesInfo = (PatchesInfo) patchsInfoClass.newInstance();
Log.d("robust", "PatchsInfoImpl ok");
} catch (Throwable t) {
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:108");
Log.e("robust", "PatchsInfoImpl failed,cause of" + t.toString());
t.printStackTrace();
}
if (patchesInfo == null) {
robustCallBack.logNotify("patchesInfo is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:114");
return false;
}
//classes need to patch
List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo();
if (null == patchedClasses || patchedClasses.isEmpty()) {
robustCallBack.logNotify("patchedClasses is null or empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:122");
return false;
}
for (PatchedClassInfo patchedClassInfo : patchedClasses) {
String patchedClassName = patchedClassInfo.patchedClassName;
String patchClassName = patchedClassInfo.patchClassName;
if (TextUtils.isEmpty(patchedClassName) || TextUtils.isEmpty(patchClassName)) {
robustCallBack.logNotify("patchedClasses or patchClassName is empty, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:131");
continue;
}
Log.d("robust", "current path:" + patchedClassName);
try {
oldClass = classLoader.loadClass(patchedClassName.trim());
Field[] fields = oldClass.getDeclaredFields();
Log.d("robust", "oldClass :" + oldClass + " fields " + fields.length);
Field changeQuickRedirectField = null;
for (Field field : fields) {
if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) {
changeQuickRedirectField = field;
break;
}
}
if (changeQuickRedirectField == null) {
robustCallBack.logNotify("changeQuickRedirectField is null, patch info:" + "id = " + patch.getName() + ",md5 = " + patch.getMd5(), "class:PatchExecutor method:patch line:147");
Log.d("robust", "current path:" + patchedClassName + " something wrong !! can not find:ChangeQuickRedirect in" + patchClassName);
continue;
}
Log.d("robust", "current path:" + patchedClassName + " find:ChangeQuickRedirect " + patchClassName);
try {
patchClass = classLoader.loadClass(patchClassName);
Object patchObject = patchClass.newInstance();
changeQuickRedirectField.setAccessible(true);
changeQuickRedirectField.set(null, patchObject);
Log.d("robust", "changeQuickRedirectField set sucess " + patchClassName);
} catch (Throwable t) {
Log.e("robust", "patch failed! ");
t.printStackTrace();
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:163");
}
} catch (Throwable t) {
Log.e("robust", "patch failed! ");
t.printStackTrace();
robustCallBack.exceptionNotify(t, "class:PatchExecutor method:patch line:169");
}
}
Log.d("robust", "patch finished ");
return true;
}
}