一、Andfix的使用范围(与其他的比较)
图片参考:http://m.blog.csdn.net/alpha58/article/details/74854680
也就是说AndFix存在以下的缺陷:
① 不支持YunOS
② 无法添加新类和新的字段
③ 需要使用加固前的apk制作补丁,但是补丁文件很容易被反编译,也就是修改过的类源码容易泄露。
④ 使用加固平台可能会使热补丁功能失效(看到有人在360加固提了这个问题,自己还未验证)。
⑤ andfix不支持布局资源等的修改
⑥ 官网:AndFix supports Android version from 2.3 to 7.0, both ARM and X86 architecture, both Dalvik and ART runtime, both 32bit and 64bit.
⑦ 应用patch不需要重启。但由于从实现上直接跳过了类初始化,设置为初始化完毕,所以像是静态函数、静态成员、构造函数都会出现问题,复杂点的类Class.forname很可能直接就会挂掉。
⑧ AndFix的一个潜在问题:
加载一次补丁后,out.apatch文件会copy到getFilesDir目录下的/apatch文件夹中,在下次补丁更新时,会检测补丁是否已经添加在apatch文件夹下,已存在就不会复制加载sdcard的out.apatch。(后面会解决的)
二、自定义签名
参考:http://blog.csdn.net/nimasike/article/details/51457229
三、集成AndFix
1.在app的build.gradle 添加
compile ‘com.alipay.euler:andfix:0.5.0’
2.所需要自定义一个PacthManger.
因为上述存在的问题:加载一次补丁后,out.apatch文件会copy到getFilesDir目录下的/apatch文件夹中,在下次补丁更新时,会检测补丁是否已经添加在apatch文件夹下,已存在就不会复制加载sdcard的out.apatch。所需要自定义一个PacthManger.
public class MyPatchManager {
private static final String TAG = "PatchManager";
// patch extension
private static final String SUFFIX = ".apatch";
private static final String DIR = "apatch";
private static final String SP_NAME = "_andfix_";
private static final String SP_VERSION = "version";
/**
* context
*/
private final Context mContext;
/**
* AndFix manager
*/
private final AndFixManager mAndFixManager;
/**
* patch directory
*/
private final File mPatchDir;
/**
* patchs
*/
private final SortedSet<Patch> mPatchs;
/**
* classloaders
*/
private final Map<String, ClassLoader> mLoaders;
/**
* @param context
* context
*/
public MyPatchManager(Context context) {
mContext = context;
mAndFixManager = new AndFixManager(mContext);
mPatchDir = new File(mContext.getFilesDir(), DIR);
mPatchs = new ConcurrentSkipListSet<Patch>();
mLoaders = new ConcurrentHashMap<String, ClassLoader>();
}
/**
* initialize
*
* @param appVersion
* App version
*/
public void init(String appVersion) {
if (!mPatchDir.exists() && !mPatchDir.mkdirs()) {// make directory fail
Log.e(TAG, "patch dir create error.");
return;
} else if (!mPatchDir.isDirectory()) {// not directory
mPatchDir.delete();
return;
}
SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
Context.MODE_PRIVATE);
String ver = sp.getString(SP_VERSION, null);
if (ver == null || !ver.equalsIgnoreCase(appVersion)) {
cleanPatch();
sp.edit().putString(SP_VERSION, appVersion).commit();
} else {
initPatchs();
}
}
private void initPatchs() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
addPatch(file);
}
}
/**
* add patch file
*
* @param file
* @return patch
*/
private Patch addPatch(File file) {
Patch patch = null;
if (file.getName().endsWith(SUFFIX)) {
try {
patch = new Patch(file);
mPatchs.add(patch);
} catch (IOException e) {
Log.e(TAG, "addPatch", e);
}
}
return patch;
}
private void cleanPatch() {
File[] files = mPatchDir.listFiles();
for (File file : files) {
mAndFixManager.removeOptFile(file);
if (!FileUtil.deleteFile(file)) {
Log.e(TAG, file.getName() + " delete error.");
}
}
}
/**
* remove all patchs
*/
public void removeAllPatch() {
cleanPatch();
SharedPreferences sp = mContext.getSharedPreferences(SP_NAME,
Context.MODE_PRIVATE);
sp.edit().clear().commit();
}
/**
* load patch,call when plugin be loaded. used for plugin architecture.</br>
*
* need name and classloader of the plugin
*
* @param patchName
* patch name
* @param classLoader
* classloader
*/
public void loadPatch(String patchName, ClassLoader classLoader) {
mLoaders.put(patchName, classLoader);
Set<String> patchNames;
List<String> classes;
for (Patch patch : mPatchs) {
patchNames = patch.getPatchNames();
if (patchNames.contains(patchName)) {
classes = patch.getClasses(patchName);
mAndFixManager.fix(patch.getFile(), classLoader, classes);
}
}
}
/**
* load patch,call when application start
*
*/
public void loadPatch() {
mLoaders.put("*", mContext.getClassLoader());// wildcard
Set<String> patchNames;
List<String> classes;
for (Patch patch : mPatchs) {
patchNames = patch.getPatchNames();
for (String patchName : patchNames) {
classes = patch.getClasses(patchName);
mAndFixManager.fix(patch.getFile(), mContext.getClassLoader(),
classes);
}
}
}
/**
* load specific patch
*
* @param patch
* patch
*/
private void loadPatch(Patch patch) {
Set<String> patchNames = patch.getPatchNames();
ClassLoader cl;
List<String> classes;
for (String patchName : patchNames) {
if (mLoaders.containsKey("*")) {
cl = mContext.getClassLoader();
} else {
cl = mLoaders.get(patchName);
}
if (cl != null) {
classes = patch.getClasses(patchName);
mAndFixManager.fix(patch.getFile(), cl, classes);
}
}
}
public void addPatch(String path) throws IOException {
/*
*@Description :addPatch,重写这个方法,那是因为源码中的addPatch()方法,
* 在gradle里导入andfix会有个问题,是在原来的项目中,加载一次补丁后,
* out.apatch文件会copy到getFilesDir目录下的/apatch文件夹中,
* 在下次补丁更新时,会检测补丁是否已经添加在apatch文件夹下,
* 已存在就不会复制加载sdcard的out.apatch,
* 所以我们需要对框架中patch文件下的PatchManager类中的addPatch()方法进行修改
*@Author: gaogang6
*@Date : 2017/8/29 15:34
*@Params: [path]
*@Return: void
*/
File src = new File(path);
File dest = new File(mPatchDir, src.getName());
if (!src.exists()) {
throw new FileNotFoundException(path);
}
if (dest.exists()) {
Log.d(TAG, "patch [" + src.getName() + "] has be loaded.");
boolean deleteResult = dest.delete();
if (deleteResult)
Log.e(TAG, "patch [" + dest.getPath() + "] has be delete.");
else {
Log.e(TAG, "patch [" + dest.getPath() + "] delete error");
return;
}
}
FileUtil.copyFile(src, dest);// copy to patch's directory
Patch patch = addPatch(dest);
if (patch != null) {
loadPatch(patch);
}
}
}
3、自定义Application
import java.io.File;
import java.io.IOException;
import android.app.Application;
import android.os.Environment;
import android.util.Log;
/**
* sample application
*
* @author [email protected]
*
*/
public class MainApplication extends Application {
private static final String TAG = "euler";
private static final String APATCH_PATH = "/out.apatch";//补丁的文件
/**
* patch manager
*/
private MyPatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
// initialize
mPatchManager = new MyPatchManager(this);
mPatchManager.init("1.0");
Log.d(TAG, "inited.");
// load patch
mPatchManager.loadPatch();
Log.d(TAG, "apatch loaded.");
// add patch at runtime
try {
// 自己在sdcard中存放.apatch文件的位置
File file=new File(Environment.getExternalStorageDirectory().getAbsoluteFile()
+File.separator+"gaogang"+File.separator);
if (!file.exists()){
file.mkdir();
}
// 自己在sdcard中存放.apatch文件的位置
String patchFileString = Environment.getExternalStorageDirectory()
.getAbsolutePath()+File.separator+"gaogang"+ APATCH_PATH;
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
} catch (IOException e) {
Log.e(TAG, "打补丁出错了", e);
}
}
}
记住在AndroidManifest.xml文件中添加Application
四、如何使用
在实际中,.apatch文件最好是在Loading界面就通过网络下载补丁文件,然后存储到sdcard自己存放的那么目录下面。(Andoid6.0之后需要动态申请存储权限)。
(1)首先:编辑一个
然后使用Build->Build APK。将apk的名字命名为bug.apk
再随便修改一下
同理将名字命名为nobug.apk
(2)下载一个文件apkpatch.把之前生成的bug.apk和nobug.apk,还有打包所使用的keystore文件放到apkpatch-1.0.3目录下
打开cmd,进入到apkpatch-1.0.3目录下,输入如下指令
apkpatch.bat -f nobug.apk -t bug.apk -o out -k andfix.jks -p 111111 -a gaogang -e 111111
每个参数含义如下
-f 新版本的apk
-t 旧版本的apk
-o 输出apatch文件的文件夹,可以随意命名
-k 打包的keystore文件名
-p keystore的密码
-a keystore 用户别名
-e keystore 用户别名的密码
(3)安装有bug.apk
(4)点击显示:
(5)将out.apatch文件放入服务器
关闭了,再代开