AndFix简单实践

一、前言

终于毕业开始工作啦。感觉工作跟学校还是有挺大的区别的~还是学校比较自由,看到什么新鲜东西就可以随时折腾,工作相对就严肃很多了,毕竟为了求稳。
研究热修复技术是因为前两天组长突然跟我提起这个,建议可以学习一下。于是就有了这篇东西。(本篇文章仅对新手有用哈)

二、热修复原理

看了鸿洋大神的文章,大概清楚了热修复技术的原理。这边就简单介绍一下,我们需要知道的是Android的ClassLoader体系。主要分为PathClassLoader和DexClassLoader。DexClassLoader主要从jar包、apk文件中加载classes.dex文件,用来执行非安装的程序代码。而PathClassLoader主要用来加载已经安装到系统中的apk文件,Android系统也主要使用它来作为类加载器。在这俩类的父类BaseDexClassLoader里有个叫findClass的方法,源码如下:

#BaseDexClassLoader
@Override
protected Class<?> findClass(String name) throws ClassNotFoundException {
    Class clazz = pathList.findClass(name);

    if (clazz == null) {
        throw new ClassNotFoundException(name);
    }

    return clazz;
}

#DexPathList
public Class findClass(String name) {
    for (Element element : dexElements) {
        DexFile dex = element.dexFile;

        if (dex != null) {
            Class clazz = dex.loadClassBinaryName(name, definingContext);
            if (clazz != null) {
                return clazz;
            }
        }
    }

    return null;
}

#DexFile
public Class loadClassBinaryName(String name, ClassLoader loader) {
    return defineClass(name, loader, mCookie);
}
private native static Class defineClass(String name, ClassLoader loader, int cookie);

大概意思就是用类加载器遍历pathList集合,去加载对应的类。所以热修复的基本原理也就出来啦,我们可以在补丁中放上修复好的类文件,然后在加载的时候选择补丁中的类来代替原apk中的类文件。这样就可以达到修复bug的目的啦!

三、AndFix简单实践

目前已有的热修复技术有很多种,这里就选用AndFix作为示例。首先去github上下载AndFix源码,解压导入工程
工程结构目录
(这里我删掉了samples、docs和tools文件夹,无任何影响)我们需要用这个工程生成aar文件作为依赖,我们只需要rebuild下project就可以了。生成的aar文件在build文件夹下的output里。
之后就可以新建自己的工程啦,将生成的aar文件放置在libs目录下,并在build.gradle里进行如下配置:

 repositories {
        flatDir {
        dirs 'libs'
    }

compile(name:'andfix0.5.0', ext:'aar')

完整配置如图:
这里写图片描述
待所有配置完成后,点击这里写图片描述重新编译一次工程,完成后,若成功可在External Libraries内看到我们的相关依赖。
这里写图片描述
至此我们所有的准备工作都已完成,可以开始尝试使用AndFix了,首先根据官方文档

这里写图片描述

我们需要在Application中进行初始化操作,并且一开始就loadPath

public class MyApplication extends Application {

    public PatchManager mPatchManager;

    @Override
    public void onCreate() {
        super.onCreate();

        mPatchManager = new PatchManager(this);
        mPatchManager.init(VERSION_NAME);

        mPatchManager.loadPatch();
    }
}

然后我们在需要的地方进行addPath(path)操作就可以了。path为补丁存放路径,我暂时将他放在了根目录上

private static final String APATCH_PATH = "/fix.apatch"; // 下载下来的apatch的路径

String patchFilePath = Environment.getExternalStorageDirectory()
                    .getAbsolutePath() + APATCH_PATH;
try {
     mPatchManager.addPatch(patchFilePath);
    } catch (IOException e) {
        e.printStackTrace();
    }

接下来就要生成补丁文件进行试验了,在MainActivity里就放了一个TextView,设置文本为”old version”,这就是我们的1.0版本。接下来打上签名,这里顺便为像我这样的新手们普及下如何签名,点击Build->Generate Signed APK
签名工具
点击Create New
创建签名

依次填写
签名相关

完成后应该是这样的
这里写图片描述

输入两个密码后点击next

签名

选择release版,点击finish就会完成签名,在对应的目录下就能找到签名后的apk,重命名为1.apk。接下来我们将textview中的文本设为”new version”,再按照上述步骤生成新的签名apk,重命名为2.apk。

接下来就要用阿里提供的apkpatch工具生成补丁文件了,首先把两个补丁文件和签名文件放到tools文件下,然后用cmd指令进入到tools目录下,输入例如如下指令:
apkpatch -f 2.apk -t 1.apk -o output -k abc.jks -p 123456 -a abc -e 1234567

 usage: 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.

完成后如下图所示
这里写图片描述
完成后我们可以在目录下的output文件夹里找到一个以.apatch结尾的文件,这就是我们需要的补丁文件。
现在可以来验证我们的热修复是否有效了,先将1.0apk安装至手机,然后将补丁文件放置手机根目录,这里我推荐用adb指令,简单举例

adb push D:\out.apatch /system/sdcard0/

之后再打开应用,发现已经变成”new version”了。
这里写图片描述 这里写图片描述

四、使用WampServer构建局域网服务器

到这里我们已经感受到andfix的效果了,为了更好地模拟真实打补丁的环境,我打算让手机从服务器上下载补丁文件进行热修复,这里同学推荐使用WampServer构建了局域网的服务器,不得不说WampServer用起来确实很方便。
首先去官网http://www.wampserver.com/ 下载WampServer,安装完成后,启动WampServer,看不惯英文的同学可以右键->language->chinese。
在浏览器中输入localhost,如果显示下图就说明成功了
服务器初步完成

然后左键打开www目录,我们把补丁文件拷贝到这里,为了更好的模拟,我将补丁重新命名为1.1.apatch,图中的test.php为我们的接口
www目录

test.php源码

<?php
    echo '{"downloadUrl":"http://192.168.1.100/1.1.apatch","version":"1.1"}';
 ?>

至此,我们服务器环境已经搭建完成。用手机或者另一台电脑访问这个文件(记得关闭防火墙),如果访问不成功,将apache中的httpd-vhost.conf配置如下

# Virtual Hosts
#

<VirtualHost *:80>
    ServerName localhost
    DocumentRoot D:/wamp64/www
    <Directory  "D:/wamp64/www/">
        Options +Indexes +Includes +FollowSymLinks +MultiViews
        AllowOverride All
        Require all granted
    </Directory>
</VirtualHost>
#

如果还是不行,可以上网查查httpd的一些配置,基本上目的就是允许其他地址进行访问。

上述都完成后,我们开始对客户端进行代码修改,这里使用xutils框架进行补丁下载。大概逻辑是这样的,我们将从服务器给的接口中获取一个补丁版本号,如果该版本号与本地读取的不同,则下载补丁,补丁下载并修复完成后删除补丁并将最新的版本号写入本地SP中。具体代码如下:

package com.example.administrator.andfixdemo.application;

import android.app.Application;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Environment;

import com.alipay.euler.andfix.patch.PatchManager;
import com.example.administrator.andfixdemo.Bean.PatchBean;
import com.example.administrator.andfixdemo.Constants.DownloadConstants;
import com.example.administrator.andfixdemo.utils.SPUtils;
import com.google.gson.Gson;

import org.xutils.common.Callback;
import org.xutils.http.RequestParams;
import org.xutils.x;

import java.io.File;
import java.io.IOException;


/**
 * Created by yaochen on 2016/9/22.
 */

public class MyApplication extends Application {

    public static final String TAG = "MyApplication";

    private static final String UPDATE_URL = "http://192.168.1.100/test.php";

    private static final String BASE_PATH = Environment.getExternalStorageDirectory().getAbsolutePath() + "/";

    private static final String SUFFIX = ".apatch";

    public static String VERSION_NAME = "";

    public static PatchManager mPatchManager;

    private String currentVersion;

    private String latestVersion;

    private String patchFilePath;

    private Gson gson;

    @Override
    public void onCreate() {
        super.onCreate();

        x.Ext.init(this);
        x.Ext.setDebug(false);

        gson = new Gson();

        try {
            PackageInfo packageInfo = getPackageManager()
                    .getPackageInfo(getPackageName(), 0);
            VERSION_NAME = packageInfo.versionName;
        } catch (PackageManager.NameNotFoundException e) {
            e.printStackTrace();
        }
        initAndFix();
        checkPatch();

    }

    private void initAndFix() {
        mPatchManager = new PatchManager(this);
        mPatchManager.init(VERSION_NAME);

        mPatchManager.loadPatch();
    }

    private void checkPatch() {
        final RequestParams params = new RequestParams(UPDATE_URL);

        x.http().get(params, new Callback.CommonCallback<String>() {

            @Override
            public void onSuccess(String s) {
//                Log.d(TAG, "onSuccess");
                PatchBean patch = gson.fromJson(s, PatchBean.class);
                // 获取当前版本号
                currentVersion = (String) SPUtils.get(x.app(), null, DownloadConstants.PATCH_VERSION, VERSION_NAME);
//                Log.d(TAG, "currentVersion:" + currentVersion);
                if (!currentVersion.equals(patch.getVersion())) { // 如果不是最新版本则进行补丁下载
                    latestVersion = patch.getVersion();  // 暂存最新版本号
                    downloadPatch(patch.getVersion(), patch.getDownloadUrl());
                }
            }

            @Override
            public void onError(Throwable throwable, boolean b) {
//                Log.d(TAG, "onError:" + throwable.getMessage());
            }

            @Override
            public void onCancelled(CancelledException e) {
//                Log.d(TAG, "onCancelled");
            }

            @Override
            public void onFinished() {
//                Log.d(TAG, "onFinished");
            }
        });
    }

    private void downloadPatch(String patchVersion, String downloadUrl) {
        RequestParams requestParams = new RequestParams(downloadUrl);
        requestParams.setSaveFilePath(BASE_PATH + patchVersion + SUFFIX);
//        Log.d(TAG, "path:" + BASE_PATH + patchVersion + SUFFIX);
        x.http().get(requestParams, new Callback.ProgressCallback<File>() {

            @Override
            public void onWaiting() {
//                Log.d(TAG, "onWaiting");
            }

            @Override
            public void onStarted() {
//                Log.d(TAG, "onStarted");
            }

            @Override
            public void onLoading(long total, long current, boolean isDownloading) {
//                Log.d(TAG, "onStarted");
            }

            @Override
            public void onSuccess(File file) {

//                Log.d(TAG, "onSuccess");

                patchFilePath = BASE_PATH + latestVersion + SUFFIX;

                try {
                    mPatchManager.addPatch(patchFilePath);
                } catch (IOException e) {
                    e.printStackTrace();
                }

                File downloadFile = new File(patchFilePath);
                if (downloadFile.exists()) {
                    downloadFile.delete();
                }

                // 存储最新版本号
                SPUtils.put(x.app(), null, DownloadConstants.PATCH_VERSION, latestVersion);
            }

            @Override
            public void onError(Throwable throwable, boolean b) {
//                Log.d(TAG, "onError");
            }

            @Override
            public void onCancelled(CancelledException e) {
//                Log.d(TAG, "onCancelled");
            }

            @Override
            public void onFinished() {
//                Log.d(TAG, "onFinished");
            }
        });
    }
}

至此,我们已经完成了从服务器下载补丁并且进行热修复的过程。
源码地址 http://download.csdn.net/detail/cc65431362/9641293

评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值