首页 > 代码库 > 和android热修复AndFix技术亲密接触

和android热修复AndFix技术亲密接触

每次回家都偷懒,不想整理一下,今天周末,强迫自己整理下,内容一定很全。

前言

随着app版本升级迭代,难免有些bug会出现,用户升级新版的代价较高,如果能给app打热补丁,热更新掉app的bug,岂不更好。

Andfix

andfix是阿里的一个热修复框架,更新至今,已经相对完善了,可以满足我们日常需求。它有很多优点,比如:
1.热修复免重启app
2.更新包小
3.支持360加固(很多blog上说不支持,其实是支持的,下文会介绍怎么用)
至于缺点吗,我不说,哈哈。
下图为热修复图解
技术分享

使用方式

1.在android studio里添加依赖

compile ‘com.alipay.euler:andfix:0.5.0@aar‘

1.在Application的onCreate方法中初始化andfix

   // 初始化patch管理类
   mPatchManager = new PatchManager(this);
   // 初始化patch版本
   String appVersion = "1.0";
   try {
       appVersion = getPackageManager().getPackageInfo(getPackageName(), 0).versionName;//获取app版本号
   } catch (PackageManager.NameNotFoundException e) {
       e.printStackTrace();
   }
   mPatchManager.init(appVersion);
   // 加载已经添加到PatchManager中的patch
   mPatchManager.loadPatch();

在热修复版本时,appversion不用变。

页面布局

为了方便测试,主页面放3个button,第一个用来显示是否有bug,第二个用来从网络上下载热修复包(需自建web服务),然后热修复,第三个用来从本地文件中加载热修复包(无需自建web服务)。

技术分享

代码

public class MainActivity extends AppCompatActivity {
    private MyApplication app;
    private static final String LOCAL_NAME = "local.apatch";//本地SD卡中的更新文件[2选1]
    private static final String NET_NAME = "net.apatch";//网络上的更新文件[2选1]
    private final String url="http://192.168.1.2/net.apatch";
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        //用于模拟是否有bug
        findViewById(R.id.button2).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                Toast.makeText(MainActivity.this, "出现bug", Toast.LENGTH_SHORT).show();
//              Toast.makeText(MainActivity.this, "bug已经修复", Toast.LENGTH_SHORT).show();
              //[第二个版本注释第一行,取消第二行注释]
            }
        });
        //网络下载并热修复
        findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                fix();
            }
        });
        //本地热修复
        findViewById(R.id.button3).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                update();
            }
        });
    }
    .....

更新的两个方法

    //本地热更新
    private void update() {
        String patchFileStr = Environment.getExternalStorageDirectory().getAbsolutePath() +File.separator+ LOCAL_NAME;
        try {
            MyApplication.mPatchManager.addPatch(patchFileStr);
        } catch (IOException e) {
            e.printStackTrace();
        }
    }
    //网络下载热更新
    private void fix() {
        new Thread(){
            @Override
            public void run() {
                try {
                    String patchFileStr = Environment.getExternalStorageDirectory().getAbsolutePath();
                    downLoadFromUrl(url, NET_NAME, patchFileStr);
                    MyApplication.mPatchManager.addPatch(patchFileStr);//热修复
                } catch (Exception e) {
                    e.printStackTrace();
                }
            }
        }.start();
    }

为了减少代码量,这里网络请求没有使用框架,而是自己写的网络请求方法

/**
     * 从网络Url中下载文件
     *
     * @param urlStr
     * @param fileName
     * @param savePath
     * @throws IOException
     */
    public static void downLoadFromUrl(String urlStr, String fileName, String savePath) throws IOException {
        URL url = new URL(urlStr);
        HttpURLConnection conn = (HttpURLConnection) url.openConnection();
        //设置超时间为3秒
        conn.setConnectTimeout(3 * 1000);
        //防止屏蔽程序抓取而返回403错误
        conn.setRequestProperty("User-Agent", "Mozilla/4.0 (compatible; MSIE 5.0; Windows NT; DigExt)");

        //得到输入流
        InputStream inputStream = conn.getInputStream();
        //获取自己数组
        byte[] getData = http://www.mamicode.com/readInputStream(inputStream);"hljs-comment">//文件保存位置
        File saveDir = new File(savePath);
        if (!saveDir.exists()) {
            saveDir.mkdir();
        }
        File file = new File(saveDir + File.separator + fileName);
        FileOutputStream fos = new FileOutputStream(file);
        fos.write(getData);
        if (fos != null) {
            fos.close();
        }
        if (inputStream != null) {
            inputStream.close();
        }
        System.out.println("info:" + url + " download success");
    }

    /**
     * 从输入流中获取字节数组
     *
     * @param inputStream
     * @return
     * @throws IOException
     */
    public static byte[] readInputStream(InputStream inputStream) throws IOException {
        byte[] buffer = new byte[1024];
        int len = 0;
        ByteArrayOutputStream bos = new ByteArrayOutputStream();
        while ((len = inputStream.read(buffer)) != -1) {
            bos.write(buffer, 0, len);
        }
        bos.close();
        return bos.toByteArray();
    }

编译

项目编译需要自备签名文件,如果没有的话,自己创建一个
技术分享


技术分享


生成含bug的apk

将生成出的app-release.apk文件重命名为bug.apk

生成解决bug的apk

技术分享
把出现bug的那条toast注释掉,改为bug已修复的toast
再次编译将生成的app-release.apk文件重命名为fixedbug.apk

生成apatch

这里需要去下载工具

https://github.com/alibaba/AndFix

tools文件夹里是工具,下载到本地
技术分享
顺便将刚刚两个bug.apk和fixedbug.apk以及签名文件放入文件夹
因为我们只有一个fixedbug.apk所以我们用下面的生成代码

apkpatch -f <new> -t <old> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
 -a,--alias <alias>     keystore entry alias.
 -e,--epassword <***>   keystore entry password.
 -f,--from <loc>        new Apk file path.
 -k,--keystore <loc>    keystore path.
 -n,--name <name>       patch name.
 -o,--out <dir>         output dir.
 -p,--kpassword <***>   keystore password.
 -t,--to <loc>          old Apk file path.

具体做法

1.先打开cmd或者终端,cd到当前目录下
2.敲入

apkpatch-1.0.3/apkpatch.sh -f fixedbug.apk  -t bug.apk  -o Out -k a.jks -p 123456 -a 654321 -e 654321

技术分享
3.这个时候这个out文件夹下apatch文件就是热更新包
技术分享
4.将此文件改名net.apatch放入www目录下
保证刚刚的http://192.168.1.2/net.apatch路径可以访问

开始测试

先安装bug.apk到手机上

1.打开ddms
技术分享
2.将net.apatch复制一份命名为local.apatch拉入手机中
技术分享
可以顺便将bug.apk也放入手机,方便安装(或其它方式安装bug.apk)
3.打开app,运行如图
技术分享
出现bug


4.点击本地文件热修复,然后再次点击显示
技术分享
提示bug已修复
至此从本地文件热修复演示完成
5.卸载app,重新安装然后点击显示,出现bug,点击网络下载热修复,再次点击显示,提示bug已修复(同上图)

总结

至此两种方法热修复均解决了appbug,代码不复杂,需要源码请留言。

补充

当团队协作时,多个小组生成出多个apatch文件,可以使用工具合并

apkpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
 -a,--alias <alias>     keystore entry alias.
 -e,--epassword <***>   keystore entry password.
 -k,--keystore <loc>    keystore path.
 -m,--merge <loc...>    path of .apatch files.
 -n,--name <name>       patch name.
 -o,--out <dir>         output dir.
 -p,--kpassword <***>   keystore password.

混淆代码

-keep class * extends java.lang.annotation.Annotation
-keepclasseswithmembernames class * {
    native <methods>;
}
<script type="text/javascript"> $(function () { $(‘pre.prettyprint code‘).each(function () { var lines = $(this).text().split(‘\n‘).length; var $numbering = $(‘
    ‘).addClass(‘pre-numbering‘).hide(); $(this).addClass(‘has-numbering‘).parent().append($numbering); for (i = 1; i <= lines; i++) { $numbering.append($(‘
  • ‘).text(i)); }; $numbering.fadeIn(1700); }); }); </script>

    和android热修复AndFix技术亲密接触