Android热修复5、AndFix详解
Android热修复1、class文件与dex文件解析https://www.jianshu.com/p/dea6a368944d
Android热修复2、虚拟机深入讲解https://www.jianshu.com/p/17f7843e09bc
Android热修复3、ClassLoader原理讲解https://www.jianshu.com/p/e3970180a002
Android热修复4、热修复简单讲解https://www.jianshu.com/p/1691685aeedf
Android热修复5、AndFix详解https://www.jianshu.com/p/1cfad3d1079a
Android热修复6、Tinker详解及两种方式接入https://www.jianshu.com/p/0ae5c0c259d1
Android热修复7、引入热修复后代码及版本管理https://www.jianshu.com/p/cd5104a6205c
AndFix的基本介绍
AndFix是取代发布android应用修复bug的方式;
AndFix支持android2.3-7.0,x86,Davlik和art,32bit和64bit(但是特定机型还是不兼容);
AndFix生成差异包后缀名是.apatch;
AndFix只能针对方法中产生的bug,而不能新增类新增方法
AndFix执行流程及核心原理
方法替换:AndFix根据自己的annotation判断哪些包需要被替换,通过native方法来替换的。(适配ART和Davlik)
26.png使用AndFix完成线上bug修复
1集成阶段
gradle中添加AndFix依赖
27.png在代码中完成对AndFix初始化(封装)
新建andfix目录(与activity同级)下新建AndFixPatchManager.java
/**
*@function 管理AndFix所有api
*/
public class AndFixPatchManager{
private static AndFixPatchManager mInstance=null;
private static PatchManager mPatchManaget=null;
public static AndFixPatchManager getInstance(){
if(mInstance == null){
synchronized(AndFixManager.class){
if(mInstance ==null){
mInstance = new AndFixPatchManager();
}
}
}
return mInstance;
}
//初始化AndFix方法
public void initPatch(Context context){
mPatchManager=new PatchManager(context);
mPatchManager.init(Utils.getVersionName(context));
mPatchManager.loadPatch();//通常在oncreate中load patch
}
//加载我们的patch文件
public void addPatch(String path){
try{
if(mPatchManager !=null){
mPatchManager.addPatch(path);
}
}catch(Exception e){
e.printStackTrace();
}
}
}
public class Utils{
/**
*获取应用程序的versionname
*/
public static String getVersionName(Context context){
String versionName="1.0.0";
try{
PackageManager pm = context.getPackageManager();
PackageInfo pi = pm.getPackageInfo(context.getPackageName(),0);
versionName = pi.versionName;
}catch(Exception e ){
e.printStackTrace()
}
return versionName;
}
public static void printLog(){
String error = null;
Log.e("renzhiqiang",error);//报错代码!!!
}
}
在application下初始化AndFixPatchManager(别忘了引用application):
28.png2准备阶段
build一个有bug的old apk并安装到手机:
public class MainActivity extends AppCompatActivity{
private static final String FILE_END=".apatch";
private String mPatchDir;
@Override
protected void onCreate(Budle savedInsatnceState){
super.onCreate(savedInsatanceState);
setContentView(R.layout.activity_main);
//小米三下是/storage/emulated/0/Android/data/com.imocc/cache/apatch/
mPatchDir = getExternalCatcheDir().getAbsolutePath()+"/apach";
//创建文件夹
File file = new File(mPatchDir );
if(file==null||!file.exists()){
file.mkdir();
}
}
//ui是两个按钮,一个产生bug,一个修复bug
//点击产生
public void createBug(View view){
Utils.printLog();
}
//点击修复
public void fixBug(View view){
AndFixPatchManager.getInstace().addPatch(getPatchName());
}
//构造patch文件名
private String getPatchName(){
return mPatchDir.concat("imooc").concat(FILE_END);
}
}
build apk(带签名,为了生成patch用):
29.png用 ./gradlew assembleRelease 创建release版本
30.png记得保存一下apk。
3patch生成阶段
apkpatch命令及参数详解
官网上可下载apkpatch文件夹
只有上面三个是自带的,下面都是自己加的。
apkpatch命令:./apkpatch.sh
32.png两个命令:上面的生成apatch用的。下面是合并多个apatch用的。
分析问题解决bug后,build一个new apk:
35.png使用apkpatch命令生成apk apatch包:
33.png 34.png重命名为imooc.apatch
push到手机,安装apatch;
使用户已经安装的应用load我们的apatch文件;
load成功后验证bug是否修复。
总结
实际中不可能通过apk push这种方式,可以用下载功能取代。
是否可以将Andfix模块组件化,为以后复用。
AndFix组件化(更方便使用、无感知修复bug)
组件化步骤
-》发现bug生成apatch
-》将apach下发到用户手机存储系统(下载模块)
-》利用AndFix完成patch安装,解决bug
时序图
36.png这里的AndFix写到了一个server里
AndFix组件化实现(上)
在andfix下创建AndFixService
37.png/**
*@function: 1检查patch文件,2下载patch文件3加载下好的patch文件
*/
public class AndFixService extends Service{
private static final String TAG="AndFixService";
private static final String FILE_END=".apatch";
private static final int UPDATE_PATCH= 0x02;
private static final int DOWNLOAD_PATCH= 0x01;
private String mPatchFileDir;
private String mPatchFile;
pricate BasePatch mBasePatchInfo;
private Handler mHandler = new Handler(){
@Override
public void handleMessage(Message msg){
switch(msg.what){
case UPDATE_PATCH:
checkPatchUpdate();
break;
case DOWNLOAD_PATCH:
dowmloadPatch();
break;
}
}
}
@Overrride
public IBinder onBind(Intent intent){
return null
}
@Overrride
public void onCreate(){
super.onCreate();
init();
}
//完成文件目录的构造
private void init(){
mPatchFileDir=getExternalCacheDir().getAbsolutePath()+"/apatch/";
File patchDir = new File(mPatchFileDir);
try{
if(patchDir ==null||!patchDir.exists()){
patchDir .mkDir();
}
}catch(Exception e ){
e.printStackTrace();
stopSelf();
}
}
@Override
public int onStartCommand(Intent intent ,int flags,int startId){
mHandler.sendEmptyMessage(UPDATE_PATCH);
return super.START_NOT_STICKY;//如果被回收,不会自动重启
}
//检查服务器是否有patch文件
private void checkPatchUpdate(){
RequestCenter.requestPatchUpdateInfo(new DisposeDataListener(){
@OVerride
public void onSuccess(Object responseObj){
mBasePatchInfo=(BasePatch)responseObj;
if(!TextUtils.isEnpty(mBasePatchInfo.data.downloadUrl)){
//下载patch文件
mHandler.sendEmptyMessage(DOWNLOAD_PATCH);
}else{
stopSelf();
}
}
@OVerride
public void onFailure(Object responseObj){
stopSelf();
}
});
}
//完成patch文件下载
private void dowmloadPatch(){
//初始化文件路径
mPatchFile = mPatchFileDir.concat(String.calueOf(System.currentTimeMills()))
.concat(FILE_END);
RequestCenter.downloadFile(mBasePatchInfo.data.downloadUrl,mPatchFile,new DisposeDownloadListener(){
@Override
public void onProcess(int progress){
Log.d(TAG,"current pro:"+progress);
}
@Override
public void onSucess(Object responseObj){
//将下好的patch文件添加到andfix中
AndFixPatchManager.getInstance().addPatch(mPatchFile);
}
@Override
public void onFailure(Object responseObj){
stopSelf();
}
});
}
}
public class RequestCenter{
//根据擦书发送所有post请求
public static void postRequset(String url,RequestParams params,DisposeDataListener listener,){
CommonOkHttpClient.get(CommonRequest.createGetRequest(url,params),new DisposeDataListener );
}
//询问是否有patch可更新
public static void requestPatchUpdateInfo(DisposeDataListener listener){
RequestCenter.postRequest(HttpConstant.UPDATE_PATCH_URL,null,listener,BasePatch.class);
}
}
//用来发送get、post 最终调用okhttp
public class CommonOkHttpClient{
private static final int TIME_OUT=30;
private static OkHttpClient mOkhttpClient;
static{
OkHttpClient.Builder okHttpClientBuilder = new OkHttpClient.Builder();
okHttpClientBuilder.hostnameVerifier(
return true;
);
//为所有请求添加请求头,看个人需求
okHttpClientBuilder.addInterceptor(new Interceptor(){
@Override
public Respnse intercept(Chain chain) throws IOException{
Request request= chain.request()
.newBuilder()
.addHeader("User_Agent","Imooc-Mobile")//标明发送本次请求的客户端
.build();
return chain.proceed(request);
}
});
okHttpClientBuilder.cookieJar(new SimpleCookieJar());
okHttpClientBuilder.connectTimeOut(TIME_OUT,TimeUnit.SECONDS);
okHttpClientBuilder.readTimeOut(TIME_OUT,TimeUnit.SECONDS);
okHttpClientBuilder.writeTimeOut(TIME_OUT,TimeUnit.SECONDS);
okHttpClientBuilder.followRedirects(true);
okHttpClientBuilder.sslSocketFactory(HttpsUtils.initSSLSocketFactory(),HttpsUtils...);
mOkHttpClient = okHttpClientBuilder.build();
}
public static OkHttpClient getOkHttpClient(){
return mOkHttpClient ;
}
//通过构造好的request callback发送请求
public static Call get(Request request,DisposeDataHandle handle){
Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallBack(handle));
return call;
}
public static Call post(Request request,DisposeDataHandle handle){
Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonJsonCallBack(handle));
return call;
}
public static Call downloadFile(Request request,DisposeDataHandle handle){
Call call = mOkHttpClient.newCall(request);
call.enqueue(new CommonFileCallBack(handle));
return call;
}
}
public calss BasePatch implements Serializable{
public int ecode;
public String emsg;
public PatchInfo data;
}
public calss PatchInfo implements Serializable{
public String downloadUrl;//不为空表示有更新
public String versionName;//本次patch版本号
public String patchMessage;//本次patch相关信息,比如做了哪些改动。
}
CommonFileCallBack通过流的循环实现文件下载
AndFix源码讲解
从 new PatchManager(context);开始:
几个重要的成员变量:
AndFixManager mAndFixManager :真正负责修复的对象
SortedSet<Patch> mPatchs 排序后的patch文件
再来看我们调用的第一个方法init方法:
39.png如果不同表明应用升级,然后删除我们的patch文件,更新sp;没有升级则initPatch()即添加到mPatchs里:
40.png 41.png要将普通文件转化成Patch文件(其实就是对文件解析)
42.png要将普通文件转化成Patch文件后都添加到了list中,init就结束了,即对patch文件的删除和添加,如果应用版本升级了,就删除;没有则添加
addpatch() 添加一个patch 最终调用loadpatch()方法(所以addpath能完成修复)
43.png我们来看loadpatch()和loadpatch(Patch patch):
44.png对所有patch fix
45.png对一个patch fix。
来看 mAndFixManager.fix 方法:
46.png 47.png最终到了native方法,是c层对dex操作完成最终方法替换。
总结
1首先将我们的文件转化成patch;
2通过class名字将patch 里面,转化成class字节码dexfile;
3有了dexfile,寻找字节码中背注解的方法后,通过jni完成方法替换、bug修复。
优点:原理简单、集成简单、使用简单,即时生效。
缺点:只能修复方法级别的bug,极大限制了使用场景。