Anroid分析Andfix原理手寫實現
阿新 • • 發佈:2018-12-17
前言
目前市面上對於熱修復一線網際網路企業大概分為三家:1、阿里 2、騰訊 3、美團 而這三家公司提供的開源庫,給了我們android開發者一些答案,今天我們瞭解一下阿里的andfix,目前andfix已經在16底停止維護了,新推出的是sophix,兼任到7.0,原理也同樣來自於AndFix,當我們開發人員修復線上的包的時候,普遍方式是通過下載完整的apk or 差分包,讓使用者重新安裝,使用者體驗不是很好。
技術實現對比
從上圖可以得知,andfix優點是即使生效,並且修復包是比較小的,相對來說效能的代價比較小,定位準確(它是方法上的替換) 而它的缺點也顯現易見,因為這項技術是對於虛擬機器層的,它的相容性也就是硬傷了,而虛擬機器google都會發布新的版本,針對於新版本的釋出,它必須要相應的去進行相容性的適配
實現流程
後臺 ---- 修復類 ----- java ----- class ----- dex,修復時候,開發人員一定知道哪個類哪個方法出現了問題,如果這個不知道那就不用玩了,下面來看程式碼,首先模擬一個出異常情況如圖所示:
通過註解方式找到哪個類裡面的哪個方法需要修復(友情提示:android 的Application.java是修復不了的)
利用android SDK dx.bat工具進行打包dex檔案
把編譯好的dex檔案放到手機的SD卡中,模擬一下網路下載到本地操作
載入dex檔案
package com.note.andfix; import android.Manifest; import android.content.pm.PackageManager; import android.os.Build; import android.os.Environment; import android.support.v4.app.ActivityCompat; import android.support.v4.content.ContextCompat; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.view.View; import android.widget.TextView; import android.widget.Toast; import java.io.File; public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("native-lib"); } String[] permissions = new String[]{ Manifest.permission.READ_EXTERNAL_STORAGE, Manifest.permission.WRITE_EXTERNAL_STORAGE, Manifest.permission.MOUNT_UNMOUNT_FILESYSTEMS }; private static final int MY_PERMISSIONS_REQUEST_CALL_PHONE = 1; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // Example of a call to a native method TextView tv = (TextView) findViewById(R.id.sample_text); tv.setText(stringFromJNI()); } /** * A native method that is implemented by the 'native-lib' native library, * which is packaged with this application. */ public native String stringFromJNI(); public void text(View view) {//測試異常出現情況 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { if (ContextCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) { ActivityCompat.requestPermissions(this, new String[]{Manifest.permission.CALL_PHONE}, MY_PERMISSIONS_REQUEST_CALL_PHONE); }else { Toast.makeText(this, "許可權已申請", Toast.LENGTH_SHORT).show(); } } AbnormalCaclutor caclutor = new AbnormalCaclutor(); caclutor.text(this); } public void fix(View view) { DexManager dexManager = new DexManager(); dexManager.setContext(this); dexManager.load(new File(Environment.getExternalStorageDirectory(), "out.dex")); } @Override public void onRequestPermissionsResult(int requestCode, String[] permissions, int[] grantResults) { if (requestCode == MY_PERMISSIONS_REQUEST_CALL_PHONE) { if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { Toast.makeText(this, "許可權已申請", Toast.LENGTH_SHORT).show(); } else { Toast.makeText(this, "許可權已拒絕", Toast.LENGTH_SHORT).show(); } } super.onRequestPermissionsResult(requestCode, permissions, grantResults); } }
package com.note.andfix; import android.content.Context; import android.os.Environment; import java.io.File; import java.io.IOException; import java.lang.reflect.Method; import java.util.Enumeration; import dalvik.system.DexClassLoader; import dalvik.system.DexFile; /** * Created by m.wang on 2018/10/24. */ public class DexManager { public Context context; public void setContext(Context context){ this.context = context; } /** * 載入dex方法 * @param file */ public void load(File file){ //DexFile 是載入dex檔案工具 try { // File dexOutputDir = context.getDir("dex", 0); // String dexOutputPath = dexOutputDir.getAbsolutePath(); DexFile dexFile = DexFile.loadDex(file.getAbsolutePath(),new File(context.getDir("dex", 0),"opt") .getAbsolutePath(),Context.MODE_PRIVATE); // File dexOutputDir = context.getDir("dex", 0); // DexClassLoader localDexClassLoader = new DexClassLoader(file.getAbsolutePath(), dexOutputDir.getAbsolutePath(), null, // ClassLoader.getSystemClassLoader().getParent()); Enumeration<String> entry = dexFile.entries();//dexFile.entries() 這個返回的 類似於hashMap的迭代器 while (entry.hasMoreElements()){//遍歷找到類名 和方法名 String className = entry.nextElement(); Class realClass = dexFile.loadClass(className,context.getClassLoader()); if (realClass != null){ fixClass(realClass); } } } catch (Exception e) { e.printStackTrace(); } } private void fixClass(Class realClass){ Method[] methods = realClass.getMethods();//方法清單找出來 for (Method rightMethod: methods){//找到有註解的方法 Replace replace = rightMethod.getAnnotation(Replace.class); if (replace == null){ continue; } //找到座標 String clazzName = replace.clazz(); String method = replace.method(); try { Class wrongClazz = Class.forName(clazzName); //找到 異常 和 修復包的 2個方法 Method wrongMethod = wrongClazz.getDeclaredMethod(method,rightMethod.getParameterTypes()); replace(wrongMethod,rightMethod);//native JNI方法 } catch (Exception e) { e.printStackTrace(); } } } public native void replace(Method wrongMethod,Method rightMethod); }
extern "C"
JNIEXPORT void JNICALL
Java_com_note_andfix_DexManager_replace(JNIEnv *env, jobject instance, jobject wrongMethod,
jobject rightMethod) {
// ArtMethod
art::mirror::ArtMethod *wrong= reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(wrongMethod));
art::mirror::ArtMethod *right= reinterpret_cast<art::mirror::ArtMethod *>(env->FromReflectedMethod(rightMethod));
// wrong=right;
wrong->declaring_class_ = right->declaring_class_;
wrong->dex_cache_resolved_methods_ = right->dex_cache_resolved_methods_;
wrong->access_flags_ = right->access_flags_;
wrong->dex_cache_resolved_types_ = right->dex_cache_resolved_types_;
wrong->dex_code_item_offset_ = right->dex_code_item_offset_;
wrong->dex_method_index_ = right->dex_method_index_;
wrong->method_index_ = right->method_index_;
}