Android 熱修復(fù)技術(shù)值_Robust(最簡單)

技術(shù)介紹
技術(shù) 介紹
熱更新 修改某處我們需要進行少量修改的地方或者Bug,修復(fù)快,時效性高
增量更新 原有app的基礎(chǔ)上只更新發(fā)生變化的地方,其余保持原樣
升級更新 在當(dāng)前的版本做了大的修改時,我們需要全部下載Apk進行升級
Robust的實現(xiàn)
流程:

1.集成 Robust,生成 apk。保存期間的混淆文件 mapping.txt,以及 Robust 生成記錄文件 methodMap.robust
2.使用注解 @Modify 或者方法 RobustModify.modify() 標(biāo)注需要修復(fù)的方法
3.開啟補丁插件,執(zhí)行生成 apk 命令,獲得補丁包 patch.jar
4.通過推送或者接口的形式,通知 app 有補丁,需要修復(fù) 5.加載補丁文件不需要重新啟動應(yīng)用

一、添加依賴:

1.添加 classpath 'com.meituan.robust:gradle-plugin:0.3.3'
classpath 'com.meituan.robust:auto-patch-plugin:0.3.3'到build.gradle中

buildscript {
    repositories {
    jcenter()
}
dependencies {
    classpath 'com.android.tools.build:gradle:2.2.2'
    classpath 'com.meituan.robust:gradle-plugin:0.3.3'
    classpath 'com.meituan.robust:auto-patch-plugin:0.3.3'
    // NOTE: Do not place your application dependencies here; they belong
    // in the individual module build.gradle files
}}

2.添加 compile 'com.meituan.robust:robust:0.3.3'到app-build.gradle-dependencies中

dependencies {
    compile fileTree(include: ['*.jar'], dir: 'libs')
    androidTestCompile('com.android.support.test.espresso:espresso-     core:2.2.2', {
    exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.0.0'
compile 'com.meituan.robust:robust:0.3.3'
testCompile 'junit:junit:4.12'}

3.添加兩種模式到app-build.gradle中,具體看注解

 apply plugin: 'com.android.application'
 apply plugin: 'auto-patch-plugin'  //生成插件是打開
//apply plugin: 'robust'//生成Apk時打開

4.在app-build.gradle打開混淆,如果不打開混淆,編譯Apk時將不生成mipping.txt文件,Robust更新會報錯,找不到此文件,具體看后面

 buildTypes {
    release {
        minifyEnabled true
        proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
        signingConfig signingConfigs.RoubstDemo
    }
}

注意:程序需要簽名,不簽名會導(dǎo)致安裝Apk時提示文件損壞,簽名可參考此篇文章Android-熱修復(fù)技術(shù)之AndFix

二、添加robust.xml到app根目錄中,看注解:
<?xml version="1.0" encoding="utf-8"?>
<resources>

<switch>
    <!--true代表打開Robust,請注意即使這個值為true,Robust也默認只在Release模式下開啟-->
    <!--false代表關(guān)閉Robust,無論是Debug還是Release模式都不會運行robust-->
    <turnOnRobust>true</turnOnRobust>
    <!--<turnOnRobust>false</turnOnRobust>-->

    <!--是否開啟手動模式,手動模式會去尋找配置項patchPackname包名下的所有類,自動的處理混淆,然后把patchPackname包名下的所有類制作成補丁-->
    <!--這個開關(guān)只是把配置項patchPackname包名下的所有類制作成補丁,適用于特殊情況,一般不會遇到-->
    <!--<manual>true</manual>-->
    <manual>false</manual>

    <!--是否強制插入插入代碼,Robust默認在debug模式下是關(guān)閉的,開啟這個選項為true會在debug下插入代碼-->
    <!--但是當(dāng)配置項turnOnRobust是false時,這個配置項不會生效-->
    <!--<forceInsert>true</forceInsert>-->
    <forceInsert>false</forceInsert>

    <!--是否捕獲補丁中所有異常,建議上線的時候這個開關(guān)的值為true,測試的時候為false-->
    <catchReflectException>true</catchReflectException>
    <!--<catchReflectException>false</catchReflectException>-->

    <!--是否在補丁加上log,建議上線的時候這個開關(guān)的值為false,測試的時候為true-->
    <!--<patchLog>true</patchLog>-->
    <patchLog>false</patchLog>

    <!--項目是否支持progaurd-->
    <proguard>true</proguard>
    <!--<proguard>false</proguard>-->

    <!--項目是否支持ASM進行插樁,默認使用ASM,推薦使用ASM,Javaassist在容易和其他字節(jié)碼工具相互干擾-->
    <useAsm>true</useAsm>
    <!--<useAsm>false</useAsm>-->
</switch>

<!--需要熱補的包名或者類名,這些包名下的所有類都被會插入代碼-->
<!--這個配置項是各個APP需要自行配置,就是你們App里面你們自己代碼的包名,
這些包名下的類會被Robust插入代碼,沒有被Robust插入代碼的類Robust是無法修復(fù)的-->
<packname name="hotfixPackage">
    <name>com.ffcs.z.robustdemo</name>
</packname>

<!--不需要Robust插入代碼的包名,Robust庫不需要插入代碼,如下的配置項請保留,還可以根據(jù)各個APP的情況執(zhí)行添加-->
<exceptPackname name="exceptPackage">
</exceptPackname>

<!--補丁的包名,請保持和類PatchManipulateImp中fetchPatchList方法中設(shè)置的補丁類名保持一致( setPatchesInfoImplClassFullName("com.meituan.robust.patch.PatchesInfoImpl")),
各個App可以獨立定制,需要確保的是setPatchesInfoImplClassFullName設(shè)置的包名是如下的配置項,類名必須是:PatchesInfoImpl-->
<patchPackname name="patchPackname">
    <name>com.ffcs.z.robustdemo</name>
</patchPackname>

<!--自動化補丁中,不需要反射處理的類,這個配置項慎重選擇-->
<noNeedReflectClass name="classes no need to reflect">

</noNeedReflectClass>
</resources>

注意:其中需要修改的為packname 、patchPackname 兩個包名

robust

robust配置完成

三、基本應(yīng)用程序

需要開啟的權(quán)限:

<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE" />
<uses-permission android:name="android.permission.MOUNT_UNMOUNT_FILESYSTEMS" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

1.MainActivity

 package com.ffcs.z.robustdemo;


import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import com.meituan.robust.PatchExecutor;


public class MainActivity extends AppCompatActivity {

Button btn;
Button seconde;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    init();


}

private void init() {
    btn = (Button) findViewById(R.id.btn);
    seconde= (Button) findViewById(R.id.btn_seconde);
    btn.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            new PatchExecutor(getApplicationContext(), new PatchManipulateImp(), new RobustCallBackSample()).start();
        }
    });
    seconde.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View view) {
            startActivity(new Intent(MainActivity.this,SecondeActivity.class));
        }
    });
}}

聲明兩個按鈕,一個更新按鈕,一個跳轉(zhuǎn)有bug的頁面,其中需要注意的是更新所使用的方法為PatchExecutor,參數(shù)一、當(dāng)前上下文,參數(shù)二,關(guān)聯(lián)patch.jar的信息,參數(shù)三、回調(diào)

PatchManipulateImp .java

package com.ffcs.z.robustdemo;
import android.content.Context;
import android.os.Environment;
import android.util.Log;
import com.meituan.robust.Patch;
import com.meituan.robust.PatchManipulate;
import com.meituan.robust.RobustApkHashUtils;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.util.ArrayList;
import java.util.List;

/**
  * Created by mivanzhang on 17/2/27.
  * We recommend you rewrite your own PatchManipulate class ,adding      your special patch Strategy,in the demo we just load the patch directly
  *   Pay attention to the difference of patch's LocalPath and patch's    TempPath
  *    We recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
  *    我們推薦繼承PatchManipulate實現(xiàn)你們App獨特的A補丁加載策略,其中setLocalPath設(shè)置補丁的原始路徑,這個路徑存儲的補丁是加密過得,setTempPath存儲解密之后的補丁,是可以執(zhí)行的jar文件
*setTempPath設(shè)置的補丁加載完畢即刻刪除,如果不需要加密和解密補丁,兩者沒有啥區(qū)別 */

 public class PatchManipulateImp extends PatchManipulate {
/***
 * connect to the network ,get the latest patches
 * l聯(lián)網(wǎng)獲取最新的補丁
 * @param context
 *
 * @return
 */
@Override
protected List<Patch> fetchPatchList(Context context) {
    //將app自己的robustApkHash上報給服務(wù)端,服務(wù)端根據(jù)robustApkHash來區(qū)分每一次apk build來給app下發(fā)補丁
    //apkhash is the unique identifier for  apk,so you cannnot patch wrong apk.
    String robustApkHash = RobustApkHashUtils.readRobustApkHash(context);
    Log.i("PatchManipulateImp","robustApkHash :" + robustApkHash);
    //connect to network to get patch list on servers
    //在這里去聯(lián)網(wǎng)獲取補丁列表
    Patch patch = new Patch();
    patch.setName("123");
    //we recommend LocalPath store the origin patch.jar which may be encrypted,while TempPath is the true runnable jar
    //LocalPath是存儲原始的補丁文件,這個文件應(yīng)該是加密過的,TempPath是加密之后的,TempPath下的補丁加載完畢就刪除,保證安全性
    //這里面需要設(shè)置一些補丁的信息,主要是聯(lián)網(wǎng)的獲取的補丁信息。重要的如MD5,進行原始補丁文件的簡單校驗,以及補丁存儲的位置,這邊推薦把補丁的儲存位置放置到應(yīng)用的私有目錄下,保證安全性
    patch.setLocalPath(Environment.getExternalStorageDirectory().getPath()+ File.separator+"robust"+File.separator + "patch");

    //setPatchesInfoImplClassFullName 設(shè)置項各個App可以獨立定制,需要確保的是setPatchesInfoImplClassFullName設(shè)置的包名是和xml配置項patchPackname保持一致,而且類名必須是:PatchesInfoImpl
    //請注意這里的設(shè)置
    patch.setPatchesInfoImplClassFullName("com.ffcs.z.robustdemo.PatchesInfoImpl");
    List  patches = new ArrayList<Patch>();
    patches.add(patch);
    return patches;
}

/**
 *
 * @param context
 * @param patch
 * @return
 *
 * you can verify your patches here
 */
@Override

protected boolean verifyPatch(Context context, Patch patch) {

    //do your verification, put the real patch to patch
    //放到app的私有目錄
    patch.setTempPath(context.getCacheDir()+ File.separator+"robust"+File.separator + "patch");
    Log.i("PatchManipulateImp","verifyPatch :" + context.getCacheDir()+ File.separator+"robust"+File.separator + "patch");
    //in the sample we just copy the file
    try {
        copy(patch.getLocalPath(), patch.getTempPath());
    }catch (Exception e){
        e.printStackTrace();
        throw new RuntimeException("copy source patch to local patch error, no patch execute in path "+patch.getTempPath());
    }

    return true;
}
public void copy(String srcPath,String dstPath) throws IOException {
    Log.i("PatchManipulateImp","copy :"+srcPath );
    File src=new File(srcPath);
    if(!src.exists()){
        throw new RuntimeException("source patch does not exist ");
    }
    File dst=new File(dstPath);
    if(!dst.getParentFile().exists()){
        dst.getParentFile().mkdirs();
    }
    InputStream in = new FileInputStream(src);
    try {
        OutputStream out = new FileOutputStream(dst);
        try {
            // Transfer bytes from in to out
            byte[] buf = new byte[1024];
            int len;
            while ((len = in.read(buf)) > 0) {
                out.write(buf, 0, len);
            }
        } finally {
            out.close();
        }
    } finally {
        in.close();
    }
}
/**
 *
 * @param patch
 * @return
 *
 * you may download your patches here, you can check whether patch is in the phone
 */
@Override
protected boolean ensurePatchExist(Patch patch) {
    return true;
}}

RobustCallBackSample.java

package com.ffcs.z.robustdemo;

import android.util.Log;
import com.meituan.robust.Patch;
import com.meituan.robust.RobustCallBack;

 public class RobustCallBackSample implements RobustCallBack {


@Override
public void onPatchListFetched(boolean result, boolean isNet) {
    Log.i("RobustCallBack", "onPatchListFetched result: " + result);
}

@Override
public void onPatchFetched(boolean result, boolean isNet, Patch patch) {
    Log.i("RobustCallBack", "onPatchFetched result: " + result);
    Log.i("RobustCallBack", "onPatchFetched isNet: " + isNet);
    Log.i("RobustCallBack", "onPatchFetched patch: " + patch.getName());
}

@Override
public void onPatchApplied(boolean result, Patch patch) {
    Log.i("RobustCallBack", "onPatchApplied result: " + result);
    Log.i("RobustCallBack", "onPatchApplied patch: " + patch.getName());

}

@Override
public void logNotify(String log, String where) {
    Log.i("RobustCallBack", "logNotify log: " + log);
    Log.i("RobustCallBack", "logNotify where: " + where);
}

@Override
public void exceptionNotify(Throwable throwable, String where) {
    Log.e("RobustCallBack", "exceptionNotify where: " + where, throwable);
}}

2.SecondeActivity.java 第二個界面 聲明了一個TextView控件

  package com.ffcs.z.robustdemo;

  import android.support.v7.app.AppCompatActivity;
  import android.os.Bundle;
  import android.widget.TextView;
  import com.meituan.robust.patch.annotaion.Modify;

public class SecondeActivity extends AppCompatActivity {

TextView t;

@Override
@Modify
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_seconde);
    t= (TextView) findViewById(R.id.text);//        t.setText("未修改");
      t.setText("已經(jīng)修改");
}}
四、編譯生成Apk(PS:也就是有Bug(SecondeActivity.java中的TextView為“未修改”))

打開美團生成Apk模式

     apply plugin: 'com.android.application'
   //apply plugin: 'auto-patch-plugin'  //生成插件是打開
     apply plugin: 'robust'//生成Apk時打開

編譯指令:gradlew clean assembleRelease --stacktrace --no-daemon

編譯有Bug的Apk
編譯后的工程項目app->build->outputs

創(chuàng)建app/robust文件夾 將outputs/release中的mipping.txt和outputs/robust中的methodsMap.robust文件考到app/robust文件下

mipping.txt:該文件列出了原始的類,方法和字段名與混淆后代碼間的映射。這個文件很重要,可以用它來翻譯被混淆的代碼
methodsMap.robust:該文件在打補丁的時候用來區(qū)別到底哪些方法需要被修復(fù),所以有它才能打補丁

五、生成補丁包

1.打開美團補丁模式 app-build.gradle:

  apply plugin: 'com.android.application'
  apply plugin: 'auto-patch-plugin'  //生成插件是打開
//apply plugin: 'robust'//生成Apk時打開

2.修改Bug

image.png

此處除了Modify還有Add等,有需要的可以了解一下

3.編譯程序

編譯結(jié)果
生成更新包位置

將更新包考到手機robust文件夾中即可

更新時請按更新按鈕

注意:手機需要手動開啟存儲權(quán)限,沒有開啟會報:未找到...../robust/patch.jar

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結(jié)合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。

相關(guān)閱讀更多精彩內(nèi)容

友情鏈接更多精彩內(nèi)容