一、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文件放入服務器
關閉了,再代開