美團Robust原理解析
本篇文章將帶大家解析Robust框架熱修復原理
主流的熱修復框架型別
- ClassLoader:將熱修復的類放在dexElements[]的最前面,這樣載入類時會優先載入到要修復的類以達到修復目的。如騰訊的Tinker、Nuwa等。
- Native hook:修改java方法在native層的函式指標,指向修復後的方法以達到修復目的。如阿里的Andifix、DexPosed等。
- Instant run:在編譯打包階段對每個函式都插入一段控制邏輯程式碼。就是今天我們要介紹的Robust實現原理。
Robust原理
-
修復流程
首先我們先來看下Robust是如何達到修復目的的。
- 在每個類中注入一個靜態變數、在每個方法前插入控制邏輯
public long getIndex() { return 100; }
以上程式碼經過Robust框架注入後會被處理成
public static ChangeQuickRedirect changeQuickRedirect; public long getIndex() { if(changeQuickRedirect != null) { //PatchProxy中封裝了獲取當前className和methodName的邏輯,並在其內部最終呼叫了changeQuickRedirect的對應函式 if(PatchProxy.isSupport(new Object[0], this, changeQuickRedirect, false)) { return ((Long)PatchProxy.accessDispatch(new Object[0], this, changeQuickRedirect, false)).longValue(); } } return 100L; }
當有補丁的時候changeQuickRedirect的值就不再是空,所以執行到需要熱修的方法時就會走到補丁的方法實現而不是原邏輯達到修復目的。
-
補丁載入
補丁載入既如何將changeQuickRedirect的值設定成補丁項的
補丁載入.png
當應用獲取到載入補丁後,會建立DexClassLoader載入補丁,每個補丁有被修復的類資訊及該類對應的補丁資訊。通過被修復的類資訊找到該類,反射將changeQuickRedirect的值賦為補丁物件完成補丁載入操作。
-
實現原理
瞭解修復流程後我們一起看下具體的實現原理,實現原理主要包含三部分:基礎包插樁、補丁載入及自動化補丁。
-
插樁實現: 具體實現在gradle-plugin Moudle中
原理:在class轉dex的過程中會呼叫Transform,在該時機修改class物件,完成程式碼的注入。
- 註冊Transfrom
class RobustTransform extends Transform implements Plugin<Project> { @Override void apply(Project target) { ... //解析應用設定的robust.xml檔案,確定需要注入類 robust = new XmlSlurper().parse(new File("${project.projectDir}/${Constants.ROBUST_XML}")) ... //將該類注入到工程的Transform過程中 project.android.registerTransform(this) }
-
修改class物件:通過 Asm或者JavaAssist操作修改class物件。
當class轉dex時會呼叫每個Transform類的transform方法。
@Override void transform(Context context, Collection<TransformInput> inputs, Collection<TransformInput> referencedInputs, TransformOutputProvider outputProvider, boolean isIncremental) throws IOException, TransformException, InterruptedException { ... if(useASM){ insertcodeStrategy=new AsmInsertImpl(hotfixPackageList,hotfixMethodList,exceptPackageList,exceptMethodList,isHotfixMethodLevel,isExceptMethodLevel); }else { insertcodeStrategy=new JavaAssistInsertImpl(hotfixPackageList,hotfixMethodList,exceptPackageList,exceptMethodList,isHotfixMethodLevel,isExceptMethodLevel); } insertcodeStrategy.insertCode(box, jarFile); ... }
接下來看一下JavaAssistInsertImpl注入程式碼的具體實現
@Override protected void insertCode(List<CtClass> box, File jarFile) throws CannotCompileException, IOException, NotFoundException { ... for (CtClass ctClass : box) { ... boolean addIncrementalChange = false; for (CtBehavior ctBehavior : ctClass.getDeclaredBehaviors()) { if (!addIncrementalChange) { // 1.靜態變數注入 addIncrementalChange = true; ClassPool classPool = ctBehavior.getDeclaringClass().getClassPool(); CtClass type = classPool.getOrNull(Constants.INTERFACE_NAME); CtField ctField = new CtField(type, Constants.INSERT_FIELD_NAME, ctClass); ctField.setModifiers(AccessFlag.PUBLIC | AccessFlag.STATIC); ctClass.addField(ctField); } .... try { if (ctBehavior.getMethodInfo().isMethod()) { CtMethod ctMethod = (CtMethod) ctBehavior; boolean isStatic = (ctMethod.getModifiers() & AccessFlag.STATIC) != 0; CtClass returnType = ctMethod.getReturnType(); String returnTypeString = returnType.getName(); //2.每個方法前控制邏輯注入 String body = "Object argThis = null;"; if (!isStatic) { body += "argThis = $0;"; } String parametersClassType = getParametersClassType(ctMethod); body += "if (com.meituan.robust.PatchProxy.isSupport($args, argThis, " + Constants.INSERT_FIELD_NAME + ", " + isStatic + ", " + methodMap.get(ctBehavior.getLongName()) + "," + parametersClassType + "," + returnTypeString + ".class)) {"; body += getReturnStatement(returnTypeString, isStatic, methodMap.get(ctBehavior.getLongName()), parametersClassType, returnTypeString + ".class"); body += "}"; ctBehavior.insertBefore(body); } } catch (Throwable t) { ... } } } } }
-
在學習補丁載入流程前,我們先看下每個補丁包的結構
-
補丁結構:每個補丁包含以下三個部分
- PatchesInfoImpl:補丁包說明類,可以獲取所有補丁物件;每個物件包含被修復類名及該類對應的補丁類。
public class PatchesInfoImpl implements PatchesInfo { public List getPatchedClassesInfo() { ArrayList localArrayList = new ArrayList(); localArrayList.add(newPatchedClassInfo("com.xxx.android.robustdemo.MainActivity", "com.bytedance.robust.patch.MainActivityPatchControl")); com.meituan.robust.utils.EnhancedRobustUtils.isThrowable = false; return localArrayList; } }
- PatchControl:補丁類,具備判斷方法是否執行補丁邏輯,及補丁方法的排程。
public class MainActivityPatchControl implements ChangeQuickRedirect{ ... //1.方法是否支援熱修 public boolean isSupport(String paramString, Object[] paramArrayOfObject) { Log.d("robust", "arrivied in isSupport " + paramString + " paramArrayOfObject" + paramArrayOfObject); String str = paramString.split(":")[3]; Log.d("robust", "in isSupport assemble method numberis" + str); Log.d("robust", "arrivied in isSupport " + paramString + " paramArrayOfObject" + paramArrayOfObject + " isSupport result is " + ":2:".contains(new StringBuffer().append(":").append(str).append(":").toString())); return ":2:".contains(":" + str + ":"); } //2.呼叫補丁的熱修邏輯 public Object accessDispatch(String paramString, Object[] paramArrayOfObject) { for (;;) { try { Object localObject = new java/lang/StringBuffer; ((StringBuffer)localObject).<init>(); if (!paramString.split(":")[2].equals("false")) { continue; } if (keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]) != null) { continue; } localObject = new com/bytedance/robust/patch/MainActivityPatch; ((MainActivityPatch)localObject).<init>(paramArrayOfObject[(paramArrayOfObject.length - 1)]); keyToValueRelation.put(paramArrayOfObject[(paramArrayOfObject.length - 1)], null); paramArrayOfObject = (Object[])localObject; localObject = paramString.split(":")[3]; paramString = new java/lang/StringBuffer; paramString.<init>(); if (!"2".equals(localObject)) { continue; } paramString = paramArrayOfObject.RobustPublicgetShowText(); }catch (Throwable paramString){ ...} return paramString; paramArrayOfObject = (MainActivityPatch)keyToValueRelation.get(paramArrayOfObject[(paramArrayOfObject.length - 1)]); continue; //具體實現邏輯在Patch中 paramArrayOfObject = new MainActivityPatch(null); } } }
- Patch:具體補丁方法的實現。該類中包含被修復類中需要熱修的方法。
public class MainActivityPatch { MainActivity originClass; public MainActivityPatch(Object paramObject) { this.originClass = ((MainActivity)paramObject); } //熱修的方法具體實現 private String getShowText() { Object localObject = getRealParameter(new Object[] { "Error Text" }); localObject = (String)EnhancedRobustUtils.invokeReflectConstruct("java.lang.String", (Object[])localObject, new Class[] { String.class }); localObject = getRealParameter(new Object[] { "Fixed Text" }); return (String)EnhancedRobustUtils.invokeReflectConstruct("java.lang.String", (Object[])localObject, new Class[] { String.class }); } }
- PatchesInfoImpl:補丁包說明類,可以獲取所有補丁物件;每個物件包含被修復類名及該類對應的補丁類。
-
補丁載入過程:具體實現在patch Moudle中
- 首先需自定義PatchManipulate實現類,確定如何拉取補丁、校驗補丁等邏輯
public abstract class PatchManipulate { /** * 獲取補丁列表 * * @param context * @return 相應的補丁列表 */ protected abstract List<Patch> fetchPatchList(Context context); /** * 驗證補丁檔案md5是否一致 * 如果不存在,則動態下載 * * @param context * @param patch * @return 校驗結果 */ protected abstract boolean verifyPatch(Context context, Patch patch); /** * 努力確保補丁檔案存在,驗證md5是否一致。 * 如果不存在,則動態下載 * * @param patch * @return 是否存在 */ protected abstract boolean ensurePatchExist(Patch patch); }
- 開啟PatchExecutor,拉取補丁並應用
PatchExecutor是個執行緒,該類實現了應用補丁的具體邏輯public class PatchExecutor extends Thread { ... @Override public void run() { ... //拉取補丁列表 List<Patch> patches = patchManipulate.fetchPatchList(context); //應用補丁列表 applyPatchList(patches); ... } /** * 應用補丁列表 */ protected void applyPatchList(List<Patch> patches) { ... for (Patch p : patches) { ... if (patchManipulate.ensurePatchExist(p)) { ... patch(context, p); ... } } } protected boolean patch(Context context, Patch patch) { 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 { patchsInfoClass = classLoader.loadClass(patch.getPatchesInfoImplClassFullName()); patchesInfo = (PatchesInfo) patchsInfoClass.newInstance(); } catch (Throwable t) { ... } ... //1.獲取補丁包中所有要熱修的物件 List<PatchedClassInfo> patchedClasses = patchesInfo.getPatchedClassesInfo(); ... for (PatchedClassInfo patchedClassInfo : patchedClasses) { String patchedClassName = patchedClassInfo.patchedClassName; String patchClassName = patchedClassInfo.patchClassName; ... try { //2.載入被修復的類 oldClass = classLoader.loadClass(patchedClassName.trim()); Field[] fields = oldClass.getDeclaredFields(); Field changeQuickRedirectField = null; for (Field field : fields) { if (TextUtils.equals(field.getType().getCanonicalName(), ChangeQuickRedirect.class.getCanonicalName()) && TextUtils.equals(field.getDeclaringClass().getCanonicalName(), oldClass.getCanonicalName())) { //3.找到注入的靜態變數 changeQuickRedirectField = field; break; } } ... try { //4.載入對應的補丁類 patchClass = classLoader.loadClass(patchClassName); Object patchObject = patchClass.newInstance(); changeQuickRedirectField.setAccessible(true); //5.將靜態變數值設定為補丁 changeQuickRedirectField.set(null, patchObject); } catch (Throwable t) { ... } } catch (Throwable t) { ... } } return true; } }
- 首先需自定義PatchManipulate實現類,確定如何拉取補丁、校驗補丁等邏輯
-
自動化補丁
- Robust支援補丁自動化生成,具體操作如下。
- 在修復完的方法上新增@Modify註解或者 “RobustModify.modify();”,新建立的方法或類新增@Add註解。
- 工程新增依賴 apply plugin: 'auto-patch-plugin',編譯完成後會在outputs/robust目錄下生成patch.jar。
- 自動化補丁生成原理簡析
自動化補丁具體實現在auto-patch-plugin Moudle中,也是利用了Transform API自動生成jar。本篇文章我們只關注如何自動生成patch類。private CtClass createPatchClass(CtClass modifiedClass, boolean isInline, String patchName, Set patchMethodSignureSet, String patchPath) throws CannotCompileException, IOException, NotFoundException { ... //1.將包含修復後邏輯的類clone一份 CtClass temPatchClass = cloneClass(modifiedClass, patchName, methodNoNeedPatchList); ... for (CtMethod method : temPatchClass.getDeclaredMethods()) { if (!Config.addedSuperMethodList.contains(method) && reaLParameterMethod != method && !method.getName().startsWith(Constants.ROBUST_PUBLIC_SUFFIX)) { //2.將欄位、方法呼叫通過反射實現 method.instrument( new ExprEditor() { public void edit(FieldAccess f) throws CannotCompileException { if (Config.newlyAddedClassNameList.contains(f.getClassName())) { return; } Map memberMappingInfo = getClassMappingInfo(f.getField().declaringClass.name); try { 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())); } } catch (NotFoundException e) { e.printStackTrace(); throw new RuntimeException(e.getMessage()); } } @Override void edit(NewExpr e) throws CannotCompileException { //inner class in the patched class ,not all inner class if (Config.newlyAddedClassNameList.contains(e.getClassName()) || Config.noNeedReflectClassSet.contains(e.getClassName())) { return; } try { 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; } } catch (NotFoundException e1) { e1.printStackTrace(); } e.replace(ReflectUtils.getCreateClassString(e, getClassValue(e.getClassName()), temPatchClass.getName(), ReflectUtils.isStatic(method.getModifiers()))); } @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 { //methods no need reflect if (Config.noNeedReflectClassSet.contains(m.method.declaringClass.name)) { return; } if (m.getMethodName().contains("lambdaFactory")) { //method contain modifeid class m.replace(ReflectUtils.getNewInnerClassString(m.getSignature(), temPatchClass.getName(), ReflectUtils.isStatic(method.getModifiers()), getClassValue(m.getClassName()))); return; } try { 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)); } } catch (NotFoundException e) { e.printStackTrace(); } } }); } } ... return patchClass; }
簡單說就是首先將包含修復完程式碼的類複製一份,然後將方法裡的欄位、方法呼叫都使用反射方式呼叫。為什麼用反射呢?因為patch裡只包含要修復的程式碼,呼叫非修復方法內的程式碼正常是呼叫不到的,所以使用反射。
- Robust支援補丁自動化生成,具體操作如下。
Robust優缺點分析
- 優點
- 缺點
- 增加包體積
- 不支援so檔案和資源替換:美團官網說已經在內側中
以上就是本文全部內容,歡迎所有善意的交流和指正! _
參考文獻:
- ofollow,noindex">https://tech.meituan.com/android_robust.html
- https://tech.meituan.com/android_autopatch.html
- http://w4lle.com/2017/03/31/robust-0/index.html