在 Android 熱補丁框架 Robust 中,幾個重要的流程包括:html
在上一篇文章Android熱補丁之Robust原理解析(一)中,咱們分析了前兩個,補丁加載過程和基礎包插樁過程,分析的版本爲 0.3.2
。 該篇文章爲該系列的第二篇文章,主要分析補丁自動化生成的過程,分析的版本爲0.4.82
。java
時間跨度有點大...android
系列文章:緩存
首先在 Gradle 插件中註冊了一個名爲 AutoPatchTranform 的 Tranformapp
class AutoPatchTransform extends Transform implements Plugin<Project> {
@Override
void apply(Project target) {
initConfig();
project.android.registerTransform(this)
}
def initConfig() {
NameManger.init();
InlineClassFactory.init();
ReadMapping.init();
Config.init();
...
ReadXML.readXMl(project.projectDir.path);
Config.methodMap = JavaUtils.getMapFromZippedFile(project.projectDir.path + Constants.METHOD_MAP_PATH)
}
複製代碼
該類爲插件的入口,實現了 GradlePlugin 並繼承自 Transform,在入口處初始化配置並註冊 Transform。配置主要是讀取 Robust xml 配置、混淆優化後的 mapping 文件、插莊過程當中生成的 methodsMap.robust 文件、初始化內聯工廠類等等。 而後最主要的是transform
方法框架
@Override
void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException {
project.android.bootClasspath.each {
Config.classPool.appendClassPath((String) it.absolutePath)
}
def box = ReflectUtils.toCtClasses(inputs, Config.classPool)
...
autoPatch(box)
...
}
def autoPatch(List<CtClass> box) {
...
ReadAnnotation.readAnnotation(box, logger);
if(Config.supportProGuard) {
ReadMapping.getInstance().initMappingInfo();
}
generatPatch(box,patchPath);
zipPatchClassesFile()
executeCommand(jar2DexCommand)
executeCommand(dex2SmaliCommand)
SmaliTool.getInstance().dealObscureInSmali();
executeCommand(smali2DexCommand)
//package patch.dex to patch.jar
packagePatchDex2Jar()
deleteTmpFiles()
}
複製代碼
在 transform
方法中,使用 javassist API 把全部須要處理的類加載到待掃描隊列中,而後調用autoPatch
方法自動生成補丁。 在 autoPatch
方法中,主要作了這麼幾件事情:ide
generatPatch
方法實際生成補丁一、2 比較好懂就不逐步分析了,主要看3;後面的五、六、七、八、9 都是爲了 7 中處理 smali,因此只要搞懂smali處理就行了。下面咱們分步來看。函數
主要邏輯在 generatPatch
方法工具
def generatPatch(List<CtClass> box,String patchPath){
...
InlineClassFactory.dealInLineClass(patchPath, Config.newlyAddedClassNameList)
initSuperMethodInClass(Config.modifiedClassNameList);
//auto generate all class
for (String fullClassName : Config.modifiedClassNameList) {
CtClass ctClass = Config.classPool.get(fullClassName)
CtClass patchClass = PatchesFactory.createPatch(patchPath, ctClass, false, NameManger.getInstance().getPatchName(ctClass.name), Config.patchMethodSignatureSet)
patchClass.writeFile(patchPath)
patchClass.defrost();
createControlClass(patchPath, ctClass)
}
createPatchesInfoClass(patchPath);
}
...
}
複製代碼
分爲兩個部分優化
首先調用InlineClassFactory.dealInLineClass(patchPath, Config.newlyAddedClassNameList)
識別被優化過的方法,這裏的優化是泛指,包括被優化、內聯、新增過的類和方法,具體的邏輯爲掃描修改後的全部類和類中的方法,若是這些類和方法不在 mapping 文件中存在,那麼能夠定義爲被優化過,其中包括@Add
新增的類或方法。 而後調用initSuperMethodInClass
方法識別修改後的全部類和類中的方法中,分析是否如包含 super
方法,若是有那麼緩存下來。 而後調用 PatchesFactory.createPatch
反射翻譯修改的類和方法,具體的實如今
private CtClass createPatchClass(CtClass modifiedClass, boolean isInline, String patchName, Set patchMethodSignureSet, String patchPath) throws CannotCompileException, IOException, NotFoundException {
//清洗須要處理的方法,略..
CtClass temPatchClass = cloneClass(modifiedClass, patchName, methodNoNeedPatchList);
JavaUtils.addPatchConstruct(temPatchClass, modifiedClass);
CtMethod reaLParameterMethod = CtMethod.make(JavaUtils.getRealParamtersBody(), temPatchClass);
temPatchClass.addMethod(reaLParameterMethod);
dealWithSuperMethod(temPatchClass, modifiedClass, patchPath);
for (CtMethod method : temPatchClass.getDeclaredMethods()) {
// shit !!too many situations need take into consideration
// methods has methodid and in patchMethodSignatureSet
if (!Config.addedSuperMethodList.contains(method) && reaLParameterMethod != method && !method.getName().startsWith(Constants.ROBUST_PUBLIC_SUFFIX)) {
method.instrument(
new ExprEditor() {
public void edit(FieldAccess f) throws CannotCompileException {
...
if (f.isReader()) { f.replace(ReflectUtils.getFieldString(f.getField(), memberMappingInfo, temPatchClass.getName(), modifiedClass.getName()));
} else if (f.isWriter()) {
f.replace(ReflectUtils.setFieldString(f.getField(), memberMappingInfo, temPatchClass.getName(), modifiedClass.getName()));
}
...
}
@Override
void edit(NewExpr e) throws CannotCompileException {
//inner class in the patched class ,not all inner class
...
if (!ReflectUtils.isStatic(Config.classPool.get(e.getClassName()).getModifiers()) && JavaUtils.isInnerClassInModifiedClass(e.getClassName(), modifiedClass)) {
e.replace(ReflectUtils.getNewInnerClassString(e.getSignature(), temPatchClass.getName(), ReflectUtils.isStatic(Config.classPool.get(e.getClassName()).getModifiers()), getClassValue(e.getClassName())));
return;
}
...
@Override
void edit(Cast c) throws CannotCompileException {
MethodInfo thisMethod = ReflectUtils.readField(c, "thisMethod");
CtClass thisClass = ReflectUtils.readField(c, "thisClass");
def isStatic = ReflectUtils.isStatic(thisMethod.getAccessFlags());
if (!isStatic) {
//inner class in the patched class ,not all inner class
if (Config.newlyAddedClassNameList.contains(thisClass.getName()) || Config.noNeedReflectClassSet.contains(thisClass.getName())) {
return;
}
// static函數是沒有this指令的,直接會報錯。
c.replace(ReflectUtils.getCastString(c, temPatchClass))
}
}
@Override
void edit(MethodCall m) throws CannotCompileException {
...
if (!repalceInlineMethod(m, method, false)) {
Map memberMappingInfo = getClassMappingInfo(m.getMethod().getDeclaringClass().getName());
if (invokeSuperMethodList.contains(m.getMethod())) {
int index = invokeSuperMethodList.indexOf(m.getMethod());
CtMethod superMethod = invokeSuperMethodList.get(index);
if (superMethod.getLongName() != null && superMethod.getLongName() == m.getMethod().getLongName()) {
String firstVariable = "";
if (ReflectUtils.isStatic(method.getModifiers())) {
//修復static 方法中含有super的問題,好比Aspectj處理後的方法
MethodInfo methodInfo = method.getMethodInfo();
LocalVariableAttribute table = methodInfo.getCodeAttribute().getAttribute(LocalVariableAttribute.tag);
int numberOfLocalVariables = table.tableLength();
if (numberOfLocalVariables > 0) {
int frameWithNameAtConstantPool = table.nameIndex(0);
firstVariable = methodInfo.getConstPool().getUtf8Info(frameWithNameAtConstantPool)
}
}
m.replace(ReflectUtils.invokeSuperString(m, firstVariable));
return;
}
}
m.replace(ReflectUtils.getMethodCallString(m, memberMappingInfo, temPatchClass, ReflectUtils.isStatic(method.getModifiers()), isInline));
}
...
}
});
}
}
//remove static code block,pay attention to the class created by cloneClassWithoutFields which construct's
CtClass patchClass = cloneClassWithoutFields(temPatchClass, patchName, null);
patchClass = JavaUtils.addPatchConstruct(patchClass, modifiedClass);
return patchClass;
}
複製代碼
這段代碼實際上是這個插件的核心部分,整體來講就是將修改後的代碼所有翻譯成反射調用生成 xxxPatch 類。 咱們先只關注method.instrument()
這個方法,這個Javassist的API,做用是遍歷方法中的代碼邏輯,包括:
ReflectUtils.getFieldString
方法,將代碼邏輯使用Javassist翻譯成反射調用,而後替換。ReflectUtils.getNewInnerClassString
翻譯成反射,而後替換ReflectUtils.getCreateClassString
翻譯成反射,而後替換ReflectUtils.getCastString
翻譯成反射,而後替換ReflectUtils.getNewInnerClassString
生成內部類的方法並翻譯成反射,而後替換ReflectUtils.getInLineMemberString
方法生成佔位內聯類xxInLinePatch
,並在改類中把修改的方法翻譯成反射,而後替換調用,這方法中又有一些其餘狀況判斷,感興趣的讀者能夠自行閱讀ReflectUtils.getMethodCallString
方法翻譯成反射,而後替換請注意,以上全部方法和須要處理的方法都須要特別注意方法簽名!
最後調用 createControlClass(patchPath, ctClass)
、createPatchesInfoClass(patchPath);
生成 PatchesInfoImpl、xxxPatchControl 寫入補丁信息和控制補丁行爲。
其中,PatchesInfoImpl
中包含全部補丁類的一一對應關係,好比 MainActivity -> MainActivityPatch
,不清楚的能夠參考該系列的上一篇文章。生成的xxxPatchControl
類用於生成xxPatch
類,並判斷補丁中的方法是否和methods.robust中的方法id匹配,若是匹配纔會去調用補丁中方法。
至此總體流程基本梳理完成。後面會針對具體的複雜狀況加以解析。
首先,在補丁類中 xxPatch 中,this
指代的是xxPatch類的對象,而咱們是想要的對象是被補丁的類的對象。
在PatchesFactory.createPatchClass()
方法中
CtMethod reaLParameterMethod = CtMethod.make(JavaUtils.getRealParamtersBody(), temPatchClass);
temPatchClass.addMethod(reaLParameterMethod);
public static String getRealParamtersBody() {
StringBuilder realParameterBuilder = new StringBuilder();
realParameterBuilder.append("public Object[] " + Constants.GET_REAL_PARAMETER + " (Object[] args){");
realParameterBuilder.append("if (args == null || args.length < 1) {");
realParameterBuilder.append(" return args;");
realParameterBuilder.append("}");
realParameterBuilder.append(" Object[] realParameter = new Object[args.length];");
realParameterBuilder.append("for (int i = 0; i < args.length; i++) {");
realParameterBuilder.append("if (args[i] instanceof Object[]) {");
realParameterBuilder.append("realParameter[i] =" + Constants.GET_REAL_PARAMETER + "((Object[]) args[i]);");
realParameterBuilder.append("} else {");
realParameterBuilder.append("if (args[i] ==this) {");
realParameterBuilder.append(" realParameter[i] =this." + ORIGINCLASS + ";");
realParameterBuilder.append("} else {");
realParameterBuilder.append(" realParameter[i] = args[i];");
realParameterBuilder.append(" }");
realParameterBuilder.append(" }");
realParameterBuilder.append(" }");
realParameterBuilder.append(" return realParameter;");
realParameterBuilder.append(" }");
return realParameterBuilder.toString();
}
複製代碼
這段的做用是,在每一個xxPatch補丁類中都插入一個getRealParameter()
方法,反編譯出來最終的結果:
public Object[] getRealParameter(Object[] objArr) {
if (objArr == null || objArr.length < 1) {
return objArr;
}
Object[] objArr2 = new Object[objArr.length];
for (int i = 0; i < objArr.length; i++) {
if (objArr[i] instanceof Object[]) {
objArr2[i] = getRealParameter((Object[]) objArr[i]);
} else if (objArr[i] == this) {
objArr2[i] = this.originClass;
} else {
objArr2[i] = objArr[i];
}
}
return objArr2;
}
複製代碼
它的做用是,在每一個xxPatch中使用的this
,都轉換成xx被補丁類的對象。其中的originClass
就是補丁類的對象。
同this
相似,xxPatch中調用 super
方法一樣須要轉爲調用被補丁類中相關方法的super
調用。
仍是在PatchesFactory.createPatchClass()
方法中有dealWithSuperMethod(temPatchClass, modifiedClass, patchPath);
調用
private void dealWithSuperMethod(CtClass patchClass, CtClass modifiedClass, String patchPath) throws NotFoundException, CannotCompileException, IOException {
...
methodBuilder.append("public static " + invokeSuperMethodList.get(index).getReturnType().getName() + " " + ReflectUtils.getStaticSuperMethodName(invokeSuperMethodList.get(index).getName()) + "(" + patchClass.getName() + " patchInstance," + modifiedClass.getName() + " modifiedInstance," + JavaUtils.getParameterSignure(invokeSuperMethodList.get(index)) + "){");
...
CtClass assistClass = PatchesAssistFactory.createAssistClass(modifiedClass, patchClass.getName(), invokeSuperMethodList.get(index));
...
methodBuilder.append(NameManger.getInstance().getAssistClassName(patchClass.getName()) + "." + ReflectUtils.getStaticSuperMethodName(invokeSuperMethodList.get(index).getName()) + "(patchInstance,modifiedInstance");
...
}
複製代碼
保留主要代碼,根據方法簽名生成了一個新的方法,以staticRobust+methodName
命名,方法中調用以RobustAssist
結尾的類中的同名方法,並調用 PatchesAssistFactory.createAssistClass
方法生成該類,這個類的父類是被補丁類的父類。
PatchesAssistFactory.createAssistClass:
static createAssistClass(CtClass modifiedClass, String patchClassName, CtMethod removeMethod) {
...
staticMethodBuidler.append("public static " + removeMethod.returnType.name + " " + ReflectUtils.getStaticSuperMethodName(removeMethod.getName())
+ "(" + patchClassName + " patchInstance," + modifiedClass.getName() + " modifiedInstance){");
staticMethodBuidler.append(" return patchInstance." + removeMethod.getName() + "(" + JavaUtils.getParameterValue(removeMethod.getParameterTypes().length) + ");");
staticMethodBuidler.append("}");
...
}
複製代碼
而後在遍歷MethodCall
過程當中,處理方法調用
m.replace(ReflectUtils.invokeSuperString(m, firstVariable));
...
def static String invokeSuperString(MethodCall m, String originClass) {
...
stringBuilder.append(getStaticSuperMethodName(m.methodName) + "(this," + Constants.ORIGINCLASS + ",\$\$);");
...
}
複製代碼
傳遞的參數,patch、originClass(被補丁類對象)、方法實際參數列表。
反編譯出的結果實際是這樣的:
MainFragmentActivity:
public void onCreate(Bundle bundle) {
super.onCreate(bundle);
...
}
MainFragmentActivityPatch:
public static void staticRobustonCreate(MainFragmentActivityPatch mainFragmentActivityPatch, MainFragmentActivity mainFragmentActivity, Bundle bundle) {
MainFragmentActivityPatchRobustAssist.staticRobustonCreate(mainFragmentActivityPatch, mainFragmentActivity, bundle);
}
MainFragmentActivityPatchRobustAssist:
public class MainFragmentActivityPatchRobustAssist extends WrapperAppCompatFragmentActivity {
public static void staticRobustonCreate(MainFragmentActivityPatch mainFragmentActivityPatch, MainFragmentActivity mainFragmentActivity, Bundle bundle) {
mainFragmentActivityPatch.onCreate(bundler);
}
}
複製代碼
參數是按照實際的方法參數傳進去的,最後調用了xxPatch.superMethod方法。可是這樣也並無實現super
方法的轉義啊,再往下看。
在處理smali過程當中,有這麼一段:
private String invokeSuperMethodInSmali(final String line, String fullClassName) {
...
result = line.replace(Constants.SMALI_INVOKE_VIRTUAL_COMMAND, Constants.SMALI_INVOKE_SUPER_COMMAND);
...
result = result.replace("p0", "p1");
...
}
複製代碼
在處理以前,smali是長這樣的:invoke-invoke-virtual {p0, p2}, Lcom/meituan/robust/patch/SecondActivityPatch;->onCreate(Landroid/os/Bundle;)V
, 處理以後是這樣的:invoke-super {p1, p2}, Landroid/support/v7/app/AppCompatActivity;->onCreate(Landroid/os/Bundle;)V
,意思就是原本是調用正常方法,如今轉爲調用super方法,而且把參數換了一下,把p0(補丁類對象)換成了p1(被補丁類對象),這樣就完成了super
的處理。反編譯後最終結果:
public class MainFragmentActivityPatchRobustAssist extends WrapperAppCompatFragmentActivity {
public static void staticRobustonCreate(MainFragmentActivityPatch mainFragmentActivityPatch, MainFragmentActivity mainFragmentActivity, Bundle bundle) {
super.onCreate(bundle);
}
}
複製代碼
內聯是個廣義的概念,包括了混淆過程當中的優化(修改方法簽名、刪除方法等)、內聯。在上面的分析中處理zi方法基本也提到了,缺啥補啥:就是把內聯掉的方法再補回來。 對於內聯的方法,不能用@Modify
註解標註,只能使用RobustModify.modify()
標註,由於在基礎包中方法都沒了,打了l補丁方法也沒用。
主要邏輯在遍歷MethodCall
-> repalceInlineMethod()
-> ReflectUtils.getInLineMemberString()
...
stringBuilder.append(" instance=new " + NameManger.getInstance().getInlinePatchName(method.declaringClass.name) + "(\$0);")
stringBuilder.append("\$_=(\$r)instance." + getInLineMethodName(method) + "(" + parameterBuilder.toString() + ");")
...
複製代碼
做用就是把內聯掉的方法調用替換爲InLine類中的新增方法。
結果就是這樣的:
public class Parent {
private String first=null;
//privateMethod被內聯了
// private void privateMethod(String fir){
// System.out.println(fir);
//}
public void setFirst(String fir){
first=fir;
Parent children=new Children();
//children.privateMethod("Robust");
//內聯替換的邏輯
ParentInline inline= new ParentInline(children);
inline.privateMethod("Robust");
}
}
public class ParentInline{
private Parent children ;
public ParentInline(Parent p){
children=p;
}
//混淆爲c
public void privateMethod(String fir){
System.out.println(fir);
}
}
複製代碼
Robust 的核心其實就是自動化生成補丁這塊,至於插莊、補丁加載這些都是很好實現的,由於沒有不少的特殊狀況須要處理。 這篇文章主要分析了自動化補丁插件的主要工做流程,和一些特殊狀況的處理,文章有限,固然還有不少特殊狀況沒有分析,這裏只是提供一些分析源碼的思路,碰到特殊狀況能夠按照這個思路排查解決問題。
就像代碼中有一行註釋,我以爲特別能歸納自動化生成補丁的團隊的內心路程,在此也再次感謝美團團隊對開源作出的貢獻。
// shit !!too many situations need take into consideration
複製代碼
整體來講,Robust 坑是有的,可是它也是高可用性、高兼容性的熱修復框架,尤爲是在Android 系統開放性愈來愈收緊的趨勢下,Robust 做爲不 hook 系統 API 的熱修復框架優點更加突出。雖然其中可能有一些坑,只要咱們對原理熟悉掌握,纔有信心能搞定這些問題。
下一篇文章,主要就講下Robust與其餘框架搭配使用出現的一些坑。