Android熱修復學習之旅——Andfix框架完全解析

在之前的博客 《Android熱修復學習之旅——HotFix完全解析》 中,我們學習了熱修復的實現方式之一,通過dex分包方案的原理還有HotFix框架的源碼分析,本次我將講解熱修復的另外一種思路,那就是通過native方法,使用這種思路的框架代表就是阿里的Andfix,本篇博客,我們將深入分析Andfix的實現。

Andfix的使用

下面一段代碼就是Andfix的使用代碼,為了方便大家理解,重要內容已進行注釋

public class MainApplication extends Application {
   private static final String TAG = "euler";

   private static final String APATCH_PATH = "/out.apatch";//被修復的文件都是以.apatch結尾
   /**
    * patch manager
    */
   private PatchManager mPatchManager;

   @Override
   public void onCreate() {
      super.onCreate();
      // initialize
      //初始化PatchManager,也就是修復包的管理器,因為修復包可能有多個,所以這里需要一個管理器進行管理
      mPatchManager = new PatchManager(this);
      mPatchManager.init("1.0");
      Log.d(TAG, "inited.");

      // load patch
      //開始加載修復包
      mPatchManager.loadPatch();
      Log.d(TAG, "apatch loaded.");

      // add patch at runtime
      try {
         // .apatch file path
         //存放patch補丁文件的路徑,這里使用的sd卡,真實項目中肯定是從服務器下載到sd卡中
         String patchFileString = Environment.getExternalStorageDirectory()
               .getAbsolutePath() + APATCH_PATH;
         mPatchManager.addPatch(patchFileString);
         Log.d(TAG, "apatch:" + patchFileString + " added.");
      } catch (IOException e) {
         Log.e(TAG, "", e);
      }

   }
}

其實就是通過一個PatchManager加載修復包,接下來我們分析一下PatchManager的代碼

/**
 * @param context
 *            context
 */
public PatchManager(Context context) {
   mContext = context;
   //初始化AndFixManager
   mAndFixManager = new AndFixManager(mContext);
   //初始化存放patch補丁文件的目錄
   mPatchDir = new File(mContext.getFilesDir(), DIR);
   //初始化存在Patch類的集合
   mPatchs = new ConcurrentSkipListSet<Patch>();
   //初始化存放類對應的類加載器集合
   mLoaders = new ConcurrentHashMap<String, ClassLoader>();
}

里面很重要的類就是AndFixManager,接下來我們看一下AndFixManager的初始化代碼

public AndFixManager(Context context) {
   mContext = context;
   //判斷Android機型是否適支持AndFix
   mSupport = Compat.isSupport();
   if (mSupport) {
      //初始化簽名安全判斷類,此類主要是進行修復包安全校驗的工作
      mSecurityChecker = new SecurityChecker(mContext);
      //初始化patch文件存放的目錄
      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;
      }
   }

概括一下AndFixManager的初始化,主要做了以下的工作:

1.判斷Android機型是否適支持AndFix,

2.初始化修復包安全校驗的工作

Andfix源碼分析

首先看一下isSupport方法內部的邏輯

public static synchronized boolean isSupport() {
   if (isChecked)
      return isSupport;

   isChecked = true;
   // not support alibaba's YunOs
   if (!isYunOS() && AndFix.setup() && isSupportSDKVersion()) {
      isSupport = true;
   }

   if (inBlackList()) {
      isSupport = false;
   }

   return isSupport;
}

可以看到判斷的條件主要是3個:

1.判斷系統是否是YunOs系統

@SuppressLint("DefaultLocale")
private static boolean isYunOS() {
   String version = null;
   String vmName = null;
   try {
      Method m = Class.forName("android.os.SystemProperties").getMethod(
            "get", String.class);
      version = (String) m.invoke(null, "ro.yunos.version");
      vmName = (String) m.invoke(null, "java.vm.name");
   } catch (Exception e) {
      // nothing todo
   }
   if ((vmName != null && vmName.toLowerCase().contains("lemur"))
         || (version != null && version.trim().length() > 0)) {
      return true;
   } else {
      return false;
   }
}

2.判斷是Dalvik還是Art虛擬機,來注冊Native方法

/**
 * initialize
 * 
 * @return true if initialize success
 */
public static boolean setup() {
   try {
      final String vmVersion = System.getProperty("java.vm.version");
      boolean isArt = vmVersion != null && vmVersion.startsWith("2");
      int apilevel = Build.VERSION.SDK_INT;
      return setup(isArt, apilevel);
   } catch (Exception e) {
      Log.e(TAG, "setup", e);
      return false;
   }
}

如果版本符合的話,會調用native的setup

static jboolean setup(JNIEnv* env, jclass clazz, jboolean isart,
      jint apilevel) {
   isArt = isart;
   LOGD("vm is: %s , apilevel is: %i", (isArt ? "art" : "dalvik"),
         (int )apilevel);
   if (isArt) {
      return art_setup(env, (int) apilevel);
   } else {
      return dalvik_setup(env, (int) apilevel);
   }
}

同樣在jboolean setup中分為art_setup和dalvik_setup

art_setup方法

extern jboolean __attribute__ ((visibility ("hidden"))) art_setup(JNIEnv* env,
      int level) {
   apilevel = level;
   return JNI_TRUE;
}

dalvik_setup方法

extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup(
      JNIEnv* env, int apilevel) {
   //打開系統的"libdvm.so"文件
   void* dvm_hand = dlopen("libdvm.so", RTLD_NOW);
   if (dvm_hand) {
       //獲取dvmDecodeIndirectRef_fnPtr和dvmThreadSelf_fnPtr倆個函數
       //這兩個函數可以通過類對象獲取ClassObject結構體
      dvmDecodeIndirectRef_fnPtr = dvm_dlsym(dvm_hand,
            apilevel > 10 ?
                  "_Z20dvmDecodeIndirectRefP6ThreadP8_jobject" :
                  "dvmDecodeIndirectRef");
      if (!dvmDecodeIndirectRef_fnPtr) {
         return JNI_FALSE;
      }
      dvmThreadSelf_fnPtr = dvm_dlsym(dvm_hand,
            apilevel > 10 ? "_Z13dvmThreadSelfv" : "dvmThreadSelf");
      if (!dvmThreadSelf_fnPtr) {
         return JNI_FALSE;
      }
      //通過Java層Method對象的getDeclaringClass方法
      //后續會調用該方法獲取某個方法所屬的類對象
      //因為Java層只傳遞了Method對象到native層
      jclass clazz = env->FindClass("java/lang/reflect/Method");
      jClassMethod = env->GetMethodID(clazz, "getDeclaringClass",
                  "()Ljava/lang/Class;");

      return JNI_TRUE;
   } else {
      return JNI_FALSE;
   }
}

主要做了兩件事,準備后續的replaceMethod函數中使用:

1、在libdvm.so動態獲取dvmDecodeIndirectRef_fnPtr函數指針和獲取dvmThreadSelf_fnPtr函數指針。

2、調用dest的 Method.getDeclaringClass方法獲取method的類對象clazz。

3.根據sdk版本判斷是否支持(支持Android2.3-7.0系統版本)

// from android 2.3 to android 7.0
private static boolean isSupportSDKVersion() {
   if (android.os.Build.VERSION.SDK_INT >= 8
         && android.os.Build.VERSION.SDK_INT <= 24) {
      return true;
   }
   return false;
}

然后我們看一下初始化簽名安全判斷類的代碼

public SecurityChecker(Context context) {
   mContext = context;
   init(mContext);
}

init方法要是獲取當前應用的簽名及其他信息,為了判斷與patch文件的簽名是否一致

// initialize,and check debuggable
//主要是獲取當前應用的簽名及其他信息,為了判斷與patch文件的簽名是否一致
private void init(Context context) {
   try {
      PackageManager pm = context.getPackageManager();
      String packageName = context.getPackageName();

      PackageInfo packageInfo = pm.getPackageInfo(packageName,
            PackageManager.GET_SIGNATURES);
      CertificateFactory certFactory = CertificateFactory
            .getInstance("X.509");
      ByteArrayInputStream stream = new ByteArrayInputStream(
            packageInfo.signatures[0].toByteArray());
      X509Certificate cert = (X509Certificate) certFactory
            .generateCertificate(stream);
      mDebuggable = cert.getSubjectX500Principal().equals(DEBUG_DN);
      mPublicKey = cert.getPublicKey();
   } catch (NameNotFoundException e) {
      Log.e(TAG, "init", e);
   } catch (CertificateException e) {
      Log.e(TAG, "init", e);
   }
}

接下來是分析mPatchManager.init方法

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;
   }
   //使用SP存儲關于patch文件的信息
   SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
         Context.MODE_PRIVATE);
   //根據你傳入的版本號和之前的對比,做不同的處理
   String ver = sp.getString(SP_VERSION, null);
   if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
      //刪除本地patch文件
      cleanPatch();
      //并把傳入的版本號保存
      sp.edit().putString(SP_VERSION, appVersion).commit();
   } else {
      //初始化patch列表,把本地的patch文件加載到內存
      initPatchs();
   }
}

主要是進行版本號的對比,如果不一致則刪除本地所有的patch文件,同時保存新的版本號,否則就直接把本地的patch文件加載到內存

private void cleanPatch() {
   File[] files = mPatchDir.listFiles();
   for (File file : files) {
      //刪除所有的本地緩存patch文件
      mAndFixManager.removeOptFile(file);
      if (!FileUtil.deleteFile(file)) {
         Log.e(TAG, file.getName() + " delete error.");
      }
   }
}
private void initPatchs() {
   File[] files = mPatchDir.listFiles();
   for (File file : files) {
      addPatch(file);
   }
}
/**
 * add patch file
 * 
 * @param file
 * @return patch
 */
private Patch addPatch(File file) {
   Patch patch = null;
   if (file.getName().endsWith(SUFFIX)) {
      try {
         //創建Patch對象
         patch = new Patch(file);
         //把patch實例存儲到內存的集合中,在PatchManager實例化集合
         mPatchs.add(patch);
      } catch (IOException e) {
         Log.e(TAG, "addPatch", e);
      }
   }
   return patch;
}

Patch類無疑是進行修復的關鍵,所以我們需要查看Patch的代碼

public Patch(File file) throws IOException {
   mFile = file;
   init();
}
@SuppressWarnings("deprecation")
private void init() throws IOException {
   JarFile jarFile = null;
   InputStream inputStream = null;
   try {
      //使用JarFile讀取Patch文件
      jarFile = new JarFile(mFile);
      //獲取META-INF/PATCH.MF文件
      JarEntry entry = jarFile.getJarEntry(ENTRY_NAME);
      inputStream = jarFile.getInputStream(entry);
      Manifest manifest = new Manifest(inputStream);
      Attributes main = manifest.getMainAttributes();
      //獲取PATCH.MF文件中的屬性Patch-Name
      mName = main.getValue(PATCH_NAME);
      //獲取PATCH.MF屬性Created-Time
      mTime = new Date(main.getValue(CREATED_TIME));

      mClassesMap = new HashMap<String, List<String>>();
      Attributes.Name attrName;
      String name;
      List<String> strings;
      for (Iterator<?> it = main.keySet().iterator(); it.hasNext();) {
         attrName = (Attributes.Name) it.next();
         name = attrName.toString();
         //判斷name的后綴是否是-Classes,并把name對應的值加入到集合中,對應的值就是class類名的列表
         if (name.endsWith(CLASSES)) {
            strings = Arrays.asList(main.getValue(attrName).split(","));
            if (name.equalsIgnoreCase(PATCH_CLASSES)) {
               mClassesMap.put(mName, strings);
            } else {
               mClassesMap.put(
                     //為了移除掉"-Classes"的后綴
                     name.trim().substring(0, name.length() - 8),// remove
                                                      // "-Classes"
                     strings);
            }
         }
      }
   } finally {
      if (jarFile != null) {
         jarFile.close();
      }
      if (inputStream != null) {
         inputStream.close();
      }
   }

}

init方法主要的邏輯就是通過讀取.patch文件,每個修復包apatch文件其實都是一個jarFile文件,然后獲得其中META-INF/PATCH.MF文件,PATCH.MF文件中都是key-value的形式,獲取key是-Classes的所有的value,這些value就是所有要修復的類,他們是以“,”進行分割的,將它們放入list列表,將其存儲到一個集合中mClassesMap,list列表中存儲的就是所有要修復的類名

還有另一個addpath方法,接受的是文件路徑參數:

/**
 * add patch at runtime
 * 
 * @param path
 *            patch path
 * @throws IOException
 */
public void addPatch(String path) throws IOException {
   File src = new File(path);
   File dest = new File(mPatchDir, src.getName());
   if(!src.exists()){
      throw new FileNotFoundException(path);
   }
   if (dest.exists()) {
      Log.d(TAG, "patch [" + path + "] has be loaded.");
      return;
   }
   //把文件拷貝到專門存放patch文件的文件夾中
   FileUtil.copyFile(src, dest);// copy to patch's directory
   Patch patch = addPatch(dest);
   if (patch != null) {
      //使用loadPatch進行加載
      loadPatch(patch);
   }
}

總結一下兩個addPatch方法的不同之處:

addPatch(file)方法:需要結合上面的initPatchs方法一起使用,他調用的場景是:本地mPatchDir目錄中已經有了修復包文件,并且版本號沒有發生變化,這樣每次啟動程序的時候就會調用初始化操作,在這里會遍歷mPatchDir目錄中所有的修復包文件,然后調用這個方法添加到全局文件列表中,也即是mPatchs中。

addPatch(String path)方法:這個方法使用的場景是版本號發生變化,或者是本地目錄中沒有修復包文件。比如第一次操作的時候,會從網絡上下載修復包文件,下載成功之后會把這個文件路徑通過這個方法調用即可,執行完之后也會主動調用加載修復包的操作了,比如demo中第一次在SD卡中放了一個修復包文件:

// add patch at runtime
try {
   // .apatch file path
   //存放patch補丁文件的路徑,這里使用的sd卡,真實項目中肯定是從服務器下載到sd卡中
   String patchFileString = Environment.getExternalStorageDirectory()
         .getAbsolutePath() + APATCH_PATH;
   mPatchManager.addPatch(patchFileString);
   Log.d(TAG, "apatch:" + patchFileString + " added.");
} catch (IOException e) {
   Log.e(TAG, "", e);
}

接下來,看一下mPatchManager.loadPatch();

/**
 * load patch,call when application start
 * 
 */
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) {
         //獲取patch對應的class類的集合List
         classes = patch.getClasses(patchName);
         //調用mAndFixManager.fix修復bug
         mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
               classes);
      }
   }
}

這個方法主要是通過Patch類獲取修復包所有的修復類名稱,之前已經介紹了Patch類的初始化操作,在哪里會解析修復包的MF文件信息,獲取到修復包需要修復的類名然后保存到列表中,這里就通過getClasses方法來獲取指定修復包名稱對應的修復類名稱列表,然后調用AndFixManager的fix方法

接下來就是分析mAndFixManager.fix方法

/**
 * fix
 * 
 * @param patchPath
 *            patch path
 */
public synchronized void fix(String patchPath) {
   fix(new File(patchPath), mContext.getClassLoader(), null);
}
**
 * fix
 * 
 * @param file
 *            patch file
 * @param classLoader
 *            classloader of class that will be fixed
 * @param classes
 *            classes will be fixed
 */
public synchronized void fix(File file, ClassLoader classLoader,
      List<String> classes) {
   if (!mSupport) {
      return;
   }
   //判斷patch文件的簽名,檢查修復包的安全性
   if (!mSecurityChecker.verifyApk(file)) {// security check fail
      return;
   }

   try {
      File optfile = new File(mOptDir, file.getName());
      boolean saveFingerprint = true;
      if (optfile.exists()) {
         // need to verify fingerprint when the optimize file exist,
         // prevent someone attack on jailbreak device with
         // Vulnerability-Parasyte.
         // btw:exaggerated android Vulnerability-Parasyte
         // http://secauo.com/Exaggerated-Android-Vulnerability-Parasyte.html
         if (mSecurityChecker.verifyOpt(optfile)) {
            saveFingerprint = false;
         } else if (!optfile.delete()) {
            return;
         }
      }
      //使用dexFile 加載修復包文件,所以說patch文件其實本質是dex文件
      final DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),
            optfile.getAbsolutePath(), Context.MODE_PRIVATE);

      if (saveFingerprint) {
         mSecurityChecker.saveOptSig(optfile);
      }
      //這里重新new了一個ClasLoader,并重寫findClass方法
      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
         }
         //加載有bug的類文件
         clazz = dexFile.loadClass(entry, patchClassLoader);
         if (clazz != null) {
            //fixClass方法對有bug的文件進行替換
            fixClass(clazz, classLoader);
         }
      }
   } catch (IOException e) {
      Log.e(TAG, "pacth", e);
   }
}

概括一下fix方法做的幾件事:

1.使用mSecurityChecker進行修復包的校驗工作,這里的校驗就是比對修復包的簽名和應用的簽名是否一致:

/**
 * @param path
 *            Apk file
 * @return true if verify apk success
 */
public boolean verifyApk(File path) {
   if (mDebuggable) {
      Log.d(TAG, "mDebuggable = true");
      return true;
   }

   JarFile jarFile = null;
   try {
      jarFile = new JarFile(path);

      JarEntry jarEntry = jarFile.getJarEntry(CLASSES_DEX);
      if (null == jarEntry) {// no code
         return false;
      }
      loadDigestes(jarFile, jarEntry);
      Certificate[] certs = jarEntry.getCertificates();
      if (certs == null) {
         return false;
      }
      return check(path, certs);
   } catch (IOException e) {
      Log.e(TAG, path.getAbsolutePath(), e);
      return false;
   } finally {
      try {
         if (jarFile != null) {
            jarFile.close();
         }
      } catch (IOException e) {
         Log.e(TAG, path.getAbsolutePath(), e);
      }
   }
}

2.使用DexFile和自定義類加載器來加載修復包文件

//這里重新new了一個ClasLoader,并重寫findClass方法
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
   }
   //加載修復包patch中的文件信息,獲取其中要修復的類名,然后進行加載
   clazz = dexFile.loadClass(entry, patchClassLoader);
   if (clazz != null) {
      //fixClass方法對有bug的文件進行替換
      fixClass(clazz, classLoader);
   }
}

這里創建一個新的classLoader的原因是,我們需要獲取修復類中bug的方法名稱,而這個方法名稱是通過修復方法的注解來獲取到的,所以得先進行類的加載然后獲取到他的方法信息,最后通過分析注解獲取方法名,這里用的是反射機制來進行操作的。使用自定義的classLoader為了過濾我們需要加載的類

接下來是fixClass方法的邏輯

/**
 * fix class
 * 
 * @param clazz
 *            class
 */
private void fixClass(Class<?> clazz, ClassLoader classLoader) {
   Method[] methods = clazz.getDeclaredMethods();
   MethodReplace methodReplace;
   String clz;
   String meth;
   for (Method method : methods) {
      //遍歷所有的方法,獲取方法的注解,因為有bug的方法在生成的patch的類中的方法都是有注解的
      methodReplace = method.getAnnotation(MethodReplace.class);
      if (methodReplace == null)
         continue;
      //獲取注解中clazz的值
      clz = methodReplace.clazz();
      //獲取注解中method的值
      meth = methodReplace.method();
      if (!isEmpty(clz) && !isEmpty(meth)) {
         //進行替換
         replaceMethod(classLoader, clz, meth, method);
      }
   }
}

通過反射獲取指定類名需要修復類中的所有方法類型,然后在獲取對應的注解信息,上面已經分析了通過DexFile加載修復包文件,然后在加載上面Patch類中的getClasses方法獲取到的修復類名稱列表來進行類的加載,然后在用反射機制獲取類中所有的方法對應的注解信息,通過注解信息獲取指定修復的方法名稱,看一下注解的定義:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MethodReplace {
   String clazz();

   String method();
}

兩個方法:一個是獲取當前類名稱,一個是獲取當前方法名稱

/**
 * replace method
 * 
 * @param classLoader classloader
 * @param clz class
 * @param meth name of target method 
 * @param method source method
 */
private void replaceMethod(ClassLoader classLoader, String clz,
      String meth, Method method) {
   try {
      String key = clz + "@" + classLoader.toString();
      //判斷此類是否已經被fix
      Class<?> clazz = mFixedClass.get(key);
      if (clazz == null) {// class not load
         Class<?> clzz = classLoader.loadClass(clz);
         // initialize target class
         clazz = AndFix.initTargetClass(clzz);//初始化class
      }
      if (clazz != null) {// initialize class OK
         mFixedClass.put(key, clazz);
         //根據反射獲取到有bug的類的方法(有bug的apk)
         Method src = clazz.getDeclaredMethod(meth,
               method.getParameterTypes());
         //src是有bug的方法,method是補丁方法
         AndFix.addReplaceMethod(src, method);
      }
   } catch (Exception e) {
      Log.e(TAG, "replaceMethod", e);
   }
}

這里說明一下,獲得有bug方法的這段代碼:

Method src = clazz.getDeclaredMethod(meth,
      method.getParameterTypes());

通過方法名和本地已有的該方法的參數信息獲取有bug的方法,然后將有bug的方法和修復的方法一起傳入進行修復

注意:上面的操作,傳入的是修復新的方法信息以及需要修復的舊方法名稱,不過這里得先獲取到舊方法類型,可以看到修復的新舊方法的簽名必須一致,所謂簽名就是方法的名稱,參數個數,參數類型都必須一致,不然這里就報錯的。進而也修復不了了。

接下來就是交給native方法了,由于Android4.4后才用的Art虛擬機,之前的系統都是Dalvik虛擬機,因此Natice層寫了2個方法,對不同的系統做不同的處理方式。

#andfix.cpp
static void replaceMethod(JNIEnv* env, jclass clazz, jobject src,
      jobject dest) {
   if (isArt) {
      art_replaceMethod(env, src, dest);
   } else {
      dalvik_replaceMethod(env, src, dest);
   }
}

Dalvik replaceMethod的實現:

extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod(
      JNIEnv* env, jobject src, jobject dest) {
   jobject clazz = env->CallObjectMethod(dest, jClassMethod);
   //ClassObject結構體包含很多信息,在native中這個值很有用
   ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr(
         dvmThreadSelf_fnPtr(), clazz);
   clz->status = CLASS_INITIALIZED;//更改狀態為類初始化完成的狀態
    //通過java層傳遞的方法對象,在native層獲得他們的結構體
   Method* meth = (Method*) env->FromReflectedMethod(src);
   Method* target = (Method*) env->FromReflectedMethod(dest);
   LOGD("dalvikMethod: %s", meth->name);

// meth->clazz = target->clazz;
    //核心方法如下,就是替換新舊方法結構體中的信息
   meth->accessFlags |= ACC_PUBLIC;
   meth->methodIndex = target->methodIndex;
   meth->jniArgInfo = target->jniArgInfo;
   meth->registersSize = target->registersSize;
   meth->outsSize = target->outsSize;
   meth->insSize = target->insSize;

   meth->prototype = target->prototype;
   meth->insns = target->insns;
   meth->nativeFunc = target->nativeFunc;
}

簡單來說,就是通過上層傳遞過來的新舊方法類型對象,通過JNIEnv的FromReflectedMethod方法獲取對應的方法結構體信息,然后將其信息進行替換即可

其余art的native方法,讀者可以自行閱讀,因為原理也是差不多.

如何生成patch包

細心的同學發現,我們還沒說如何生成patch包,可以通過apatch進行生成

使用神器apatch進行線上發布的release包和這次修復的fix包進行比對,獲取到修復文件apatch

java -jar apkpatch.jar -f app-release-fix.apk -t app-release-online.apk -o C:\Users\mayu-g\Desktop\apkpatch-1.0.3 -k    myl.keystore -p 123456 -a mayunlong -e 123456

使用命令的時候需要用到簽名文件,因為在前面分析代碼的時候知道會做修復包的簽名驗證。這里得到了一個修復包文件如下:

而且會產生一個diff.dex文件和smali文件夾,而我們用壓縮軟件可以打開apatch文件看看:

可以看到這里的classes.dex文件其實就是上面的diff.dex文件,只是這里更像是Android中的apk文件目錄格式,同樣有一個META-INF目錄,這里存放了簽名文件以及需要修復類信息的PATCH.MF文件:

至此,Andfix框架已基本分析完畢。

 

來自:http://blog.csdn.net/u012124438/article/details/64623253

 

 本文由用戶 bmnk0073 自行上傳分享,僅供網友學習交流。所有權歸原作者,若您的權利被侵害,請聯系管理員。
 轉載本站原創文章,請注明出處,并保留原始鏈接、圖片水印。
 本站是一個以用戶分享為主的開源技術平臺,歡迎各類分享!