热更新

Android热修复5、AndFix详解

2021-03-30  本文已影响0人  flynnny

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,而不能新增类新增方法

25.png

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.png

2准备阶段

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文件夹

31.png

只有上面三个是自带的,下面都是自己加的。

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文件

38.png

再来看我们调用的第一个方法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,极大限制了使用场景。

上一篇 下一篇

猜你喜欢

热点阅读