Anroid分析Andfix原理手写实现

前言

         目前市面上对于热修复一线互联网企业大概分为三家:1、阿里  2、腾讯  3、美团 而这三家公司提供的开源库,给了我们android开发者一些答案,今天我们了解一下阿里的andfix,目前andfix已经在16底停止维护了,新推出的是sophix,兼任到7.0,原理也同样来自于AndFix,当我们开发人员修复线上的包的时候,普遍方式是通过下载完整的apk or 差分包,让用户重新安装,用户体验不是很好。

技术实现对比

Anroid分析Andfix原理手写实现

从上图可以得知,andfix优点是即使生效,并且修复包是比较小的,相对来说性能的代价比较小,定位准确(它是方法上的替换) 而它的缺点也显现易见,因为这项技术是对于虚拟机层的,它的兼容性也就是硬伤了,而虚拟机google都会发布新的版本,针对于新版本的发布,它必须要相应的去进行兼容性的适配

实现流程

后台  ----  修复类 ----- java ----- class -----  dex,修复时候,开发人员一定知道哪个类哪个方法出现了问题,如果这个不知道那就不用玩了,下面来看代码,首先模拟一个出异常情况如图所示:

Anroid分析Andfix原理手写实现

通过注解方式找到哪个类里面的哪个方法需要修复(友情提示:android 的Application.java是修复不了的)

 

利用android SDK dx.bat工具进行打包dex文件

Anroid分析Andfix原理手写实现

把编译好的dex文件放到手机的SD卡中,模拟一下网络下载到本地操作

加载dex文件

Anroid分析Andfix原理手写实现

 

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_;
}

demo地址:https://download.****.net/download/qq_23213991/10743205