前言
目前市面上对于热修复一线互联网企业大概分为三家: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_;
}
demo地址:https://download.csdn.net/download/qq_23213991/10743205