1. 程式人生 > >Anroid分析Andfix原理手寫實現

Anroid分析Andfix原理手寫實現

前言

         目前市面上對於熱修復一線網際網路企業大概分為三家: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_;
}