美文网首页热更新
Android热修复5、AndFix详解

Android热修复5、AndFix详解

作者: flynnny | 来源:发表于2021-03-30 00:49 被阅读0次

    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,极大限制了使用场景。

    相关文章

      网友评论

        本文标题:Android热修复5、AndFix详解

        本文链接:https://www.haomeiwen.com/subject/cuimhltx.html