1.概述
上一期讲到,我们获取到用户手中上线的崩溃信息上传到服务器后该怎么办?如果直接发布版本要用户去下载肯定不乐意。这一期我们来看一下怎么去打补丁就是大家口中所说的热修复,什么是热修复?这里就不做介绍了就是不重新安装apk就能修复Bug。 上次说这周需要讲解NDK有些哥们就说会蒙B,阿里的解决方案里面的确涉及到了NDK,我就做简单的讲解后面等我们分享完C和C++,视频编解码和直播推流我们再来详细介绍,这里就只做简单的了解,况且我们会有自己的修复方案,那个就纯Java代码了。
视频讲解:http://pan.baidu.com/s/1i5woOtR
相关文章:
2.热补丁介绍
2.1 基本介绍 我们先去github上面了解它https://github.com/alibaba/AndFix
请原谅我的盗图行为,英文解释我就不贴了我自己也看不懂,大致的意思的就是要想尽一切办法给导致我们崩溃的那个类的某个方法去动手术把它弄好,而这里就有一个概念那就AndFix.apatch补丁用来修复方法,接下来我们看看到底是怎么实现的。 2.2 生成apatch包 假如我们收到了用户上传的崩溃信息,我们改完需要修复的Bug,这个时候就会有一个新的的apk我们就叫它为new.apk,原来的那个有Bug的apk你也有我们就叫它old.apk。这个时候我们就可以利用生成一个xxxx.apatch包用于修复Bug。
命令是:apkpatch.bat -f -t -o -k -p <*> -a -e <*>
-f : 没有Bug的新版本apk -t : 有bug的旧版本apk -o : 生成的补丁文件所放的文件夹 -k : 签名打包密钥 -p : 签名打包密钥密码 -a : 签名密钥别名 -e : 签名别名密码(这样一般和密钥密码一致)
我的是这样子:
apkpatch.bat -f new.apk -t old.apk -o out -k joke.jks -p 240336124 -a 内涵段子 -e 240336124
2.3 修复apatch包
怎么获取apatch包呢?我们肯定是请求接口获取下载我们的修复好的apatch包,当然有可能没有就太好了佛祖保佑。下载下来之后我们就可以调用方法进行修复了,我们可以暂时放在本地测试一下,这些代码肯定之前就得写好:
/** * Created by Darren on 2017/2/8. * Email: 240336124@qq.com * Description: BaseApplication */public class BaseApplication extends Application { // Patch管理类 public static PatchManager mPatchManager; @Override public void onCreate() { super.onCreate(); // 捕捉崩溃信息 ExceptionCrashHandler.getInstance().init(this); // Ali热修复 try { mPatchManager = new PatchManager(this); // 初始化patch版本 String pkName = this.getPackageName(); String versionName = getPackageManager().getPackageInfo(pkName, 0).versionName; // 初始化版本名称 mPatchManager.init(versionName); // 加载之前的patch mPatchManager.loadPatch(); } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } }}复制代码
/** * Created by Darren on 2017/2/8. * Email: 240336124@qq.com * Description: 主页面MainActivity */public class MainActivity extends BaseSkinActivity { @Override protected void initData() { // 获取上次的崩溃信息 File crashFile = ExceptionCrashHandler.getInstance().getCrashFile(); // 上传到服务器,后面再说....... } @Override protected void initView() { } @Override protected void setContentView() { setContentView(R.layout.activity_main); } @Override protected void initTitle() { } @Onclick(R.id.test) public void test(View view) { // 没有修复之前会报异常闪退 Toast.makeText(this, Utils.test(), Toast.LENGTH_LONG).show(); } @Onclick(R.id.ali_fix) public void aliHotFix(View view) { try { // 测试 目前暂且放在本地 String patchFileString = Environment.getExternalStorageDirectory()+"/fix.apatch"; Log.e("TAG", patchFileString); // 修复apatch,不需要重启可立即生效 BaseApplication.mPatchManager.addPatch(patchFileString); Toast.makeText(this, "Bug修复成功", Toast.LENGTH_LONG).show(); } catch (Exception e) { e.printStackTrace(); Toast.makeText(this, "Bug修复失败", Toast.LENGTH_LONG).show(); } }}复制代码
运行的效果就在最上面,如果直接点击测试会报异常闪退,当我们点击完阿里开源热修复后再次点击测试,发现不闪退了。接下来的内容你可能要看不懂了请做好准备,但是也不要紧这里只做简单介绍实在不行可以看视频讲解。
3.Art和Dalvik层原理解析
把fix.aptach包解压反编译里面的dex文件就是这个样子的,由此可见它其实是把两个apk进行比较然后找出里面不一样的方法,然后打成一个xxx..aptach包其实就是一个压缩文件,我们究竟怎么去获取dex里面的所有方法呢?解压后其实里面还有一个很重要的文件META-INF\PATCH.MF文件:Manifest-Version: 1.0Patch-Name: newCreated-Time: 13 Feb 2017 08:19:23 GMTFrom-File: new.apkTo-File: old.apkPatch-Classes: // 所有的类都会在这里,有多个就会显示多个,需要用java代码把他解析出来拿到这个内容......com.example.administrator.exceptioncrashhandler.Utils_C FCreated-By: 1.0 (ApkPatch)复制代码
BaseApplication.mPatchManager.addPatch(patchFileString)这里面的Java代码我就不贴了,自己尝试着看看Java源码然后找到Native层的替换方法就当锻炼吧,这里我直接找部分很重要的C代码出来:
static void replaceMethod(JNIEnv* env, jclass clazz, jobject src, jobject dest) { if (isArt) { art_replaceMethod(env, src, dest); } else { dalvik_replaceMethod(env, src, dest); }}复制代码
由于Android4.4后才用的Art虚拟机,之前的系统都是Dalvik虚拟机,因此Natice层写了2个方法,对不同的系统做不同的处理方式,我就只贴Dalvik部分了都类似,只不过Art需要版本判断不同Api的版本有所不同。
extern jboolean __attribute__ ((visibility ("hidden"))) dalvik_setup( JNIEnv* env, int apilevel) { // libdvm.so 加载系统的so库,视频里我们再去看一张图 void* dvm_hand = dlopen("libdvm.so", RTLD_NOW); if (dvm_hand) { 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; } jclass clazz = env->FindClass("java/lang/reflect/Method"); jClassMethod = env->GetMethodID(clazz, "getDeclaringClass", "()Ljava/lang/Class;"); return JNI_TRUE; } else { return JNI_FALSE; }}extern void __attribute__ ((visibility ("hidden"))) dalvik_replaceMethod( JNIEnv* env, jobject src, jobject dest) { jobject clazz = env->CallObjectMethod(dest, jClassMethod); ClassObject* clz = (ClassObject*) dvmDecodeIndirectRef_fnPtr( dvmThreadSelf_fnPtr(), clazz); clz->status = CLASS_INITIALIZED; 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;}复制代码
看到libdvm.so没有?本来打算还贴一张图,想想还是算了内容有点多等周末视频再去详细讲吧。其实看不懂也不要紧会用就行,下一期我们会有自己的解决方案那是纯Java代码,但是需要去了解类的加载机制需要去看大量的源码。
视频讲解:http://pan.baidu.com/s/1i5woOtR
相关文章: