一、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 sanping.li@alipay.com * */ 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文件放入服务器 关闭了,再代开
大兄弟,项目下载地址在这里