Android熱修復框架——AndFix
阿新 • • 發佈:2019-01-09
一直關注App的熱修復的技術發展,之前做的應用也沒用使用到什麼熱修復開源框架。在App的熱修復框架沒有流行之前,做的應用上線後發現一個小小的Bug,就要馬上發一個新的版本。今天看了熱修復技術,感覺挺好玩的,就實現了使用的全過程。下面記錄使用開源框架阿里巴巴的AndFix過程。
這裡說的不是熱修復怎麼實現修bug的原理,這裡說的是怎麼使用AndFix。如果你想了解更多的andFix實現原理,你可以參考下面的文章:
1.使用極光推送訊息到該應用的版本需要下載補丁,如果應用收到了訊息後,應用判斷當前的版本是否需要下載補丁。如果應用沒有收到訊息的通知,則下次啟動App的時候,獲取友盟線上引數來判斷是否需要下載補丁。
2.應用啟動的後,有則通過 Android 檔案下載file-downloader框架來下載到SD下並且通過使用AndFix來載入到應用中。
步驟
1.在gradle檔案中增加相應的依賴。這裡我使用thindownlaodmanager來下載補丁,使用極光推送來推送自定義訊息下載補丁通知,使用友盟線上引數來獲取補丁包的資訊。也許你會問為了修復一個補丁而增加這麼多的依賴,值得嗎?我認為還可以吧,因為我的專案一般會使用到這些AndFix的引入是: compile 'com.alipay.euler:andfix:[email protected]'
-
極光推送自定義訊息(自定義訊息有長度限制,所以補丁的下載url寫成拼接形式:站點+下載資源名稱)
定義相對應的Bean
public class MyApplication extends Application { private static final String TAG = "AndFix"; public static String VERSION_NAME=""; public static PatchManager mPatchManager; @Override public void onCreate() { super.onCreate(); try { PackageInfo mPackageInfo = this.getPackageManager().getPackageInfo(this.getPackageName(), 0); VERSION_NAME=mPackageInfo.versionName; } catch (PackageManager.NameNotFoundException e) { e.printStackTrace(); } JPushInterface.init(this); initAndFix(); } private void initAndFix() { mPatchManager=new PatchManager(this); mPatchManager.init(VERSION_NAME); Log.d(TAG, "initAndFix: inited."); mPatchManager.loadPatch(); } }
5.再加上推送推送的自定義內容的處理:(當推送訊息過來的時候應用處於執行狀態的時候,程式會處理訊息進行下載補丁包)
private PatchBean bean=new PatchBean();
private WeakHandler mHandler = new WeakHandler(new Handler.Callback() {
@Override
public boolean handleMessage(Message msg) {
Toast.makeText(MainActivity.this,(String)msg.obj,Toast.LENGTH_LONG).show();
if (msg.what == MSG_WHAT_DOWNLOAD){
String message = (String) msg.obj;
if (TextUtils.isEmpty(message)) return false;
try {
bean = GsonUtils.getInstance().parse(PatchBean.class, message);
RepairBugUtil.getInstance().comparePath(MainActivity.this, bean);
} catch (Exception e) {
e.printStackTrace();
}
}
return false;
}
});
//for receive customer msg from jpush server
private MessageReceiver mMessageReceiver;
public static final String MESSAGE_RECEIVED_ACTION = "MESSAGE_RECEIVED_ACTION";
public static final String KEY_MESSAGE = "message";
public void registerMessageReceiver() {
mMessageReceiver = new MessageReceiver();
IntentFilter filter = new IntentFilter();
filter.setPriority(IntentFilter.SYSTEM_HIGH_PRIORITY);
filter.addAction(MESSAGE_RECEIVED_ACTION);
registerReceiver(mMessageReceiver, filter);
}
public class MessageReceiver extends BroadcastReceiver {
@Override
public void onReceive(Context context, Intent intent) {
if (MESSAGE_RECEIVED_ACTION.equals(intent.getAction())) {
String message = intent.getStringExtra(KEY_MESSAGE);
Message msg = new Message();
msg.what = MSG_WHAT_DOWNLOAD;
msg.obj = message;
mHandler.sendMessage(msg);
}
}
}
6.補丁包的生成
- 下載AndFix的補丁生成工具:here
- 生成補丁的檔案需要的檔案有:原apk檔案,修復Bug後生成的新apk,簽名檔案。
在解壓apkpatch工具的目錄下,開啟命令列輸入以下命令生成補丁包。apkpatch -m <apatch_path...> -o <output> -k <keystore> -p <***> -a <alias> -e <***>
- 將生成的補丁放到指定伺服器上。
7.自己測試一下成不成啦~
程式碼
package com.vson.hotfix.utils;
import android.content.Context;
import android.os.Environment;
import android.text.TextUtils;
import android.util.Log;
import android.widget.Toast;
import com.vson.hotfix.MyApplication;
import com.vson.hotfix.PatchBean;
import com.vson.hotfix.SPConst;
import org.wlf.filedownloader.DownloadFileInfo;
import org.wlf.filedownloader.FileDownloadConfiguration;
import org.wlf.filedownloader.FileDownloader;
import org.wlf.filedownloader.listener.OnFileDownloadStatusListener;
import java.io.File;
import java.io.IOException;
/**
* Created by vson on 2016/3/11.
*/
public class RepairBugUtil {
private static final String TAG = "AndFix";
private static final int THREAD_COUNT = 3; //下載的執行緒數
private LocalPreferencesHelper mLocalPreferencesHelper;
private static class SingletonHolder {
public static final RepairBugUtil INSTANCE = new RepairBugUtil();
}
public static RepairBugUtil getInstance() {
return SingletonHolder.INSTANCE;
}
public void downloadAndLoad(final Context context,final PatchBean bean) {
// 1、建立Builder
FileDownloadConfiguration.Builder builder = new FileDownloadConfiguration.Builder(context);
// 2.配置Builder
// 配置下載檔案儲存的資料夾
builder.configFileDownloadDir(Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
"FileDownloader");
// 配置同時下載任務數量,如果不配置預設為2
builder.configDownloadTaskSize(3);
// 配置失敗時嘗試重試的次數,如果不配置預設為0不嘗試
builder.configRetryDownloadTimes(5);
// 開啟除錯模式,方便檢視日誌等除錯相關,如果不配置預設不開啟
builder.configDebugMode(true);
// 配置連線網路超時時間,如果不配置預設為15秒
builder.configConnectTimeout(25000);// 25秒
// 3、使用配置檔案初始化FileDownloader
FileDownloadConfiguration configuration = builder.build();
FileDownloader.init(configuration);
FileDownloader.start(SPConst.URL_PREFIX+bean.url);
FileDownloader.registerDownloadStatusListener(new OnFileDownloadStatusListener() {
@Override
public void onFileDownloadStatusWaiting(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusPreparing(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusPrepared(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusDownloading(DownloadFileInfo downloadFileInfo, float downloadSpeed, long remainingTime) {
}
@Override
public void onFileDownloadStatusPaused(DownloadFileInfo downloadFileInfo) {
}
@Override
public void onFileDownloadStatusCompleted(DownloadFileInfo downloadFileInfo) {
Toast.makeText(context, "下載成功", Toast.LENGTH_LONG).show();
// add patch at runtime
try {
// .apatch file path
String patchFileString = Environment.getExternalStorageDirectory().getAbsolutePath() + File.separator +
"FileDownloader"+ File.separator+bean.url;
MyApplication.mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
//複製且載入補丁成功後,刪除下載的補丁
File f = new File(patchFileString);
if (f.exists()) {
boolean result = new File(patchFileString).delete();
if (!result)
Log.e(TAG, patchFileString + " delete fail");
}
// mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, false);
} catch (IOException e) {
Log.e(TAG, "", e);
} catch (Throwable throwable) {
}
}
@Override
public void onFileDownloadStatusFailed(String url, DownloadFileInfo downloadFileInfo, FileDownloadStatusFailReason failReason) {
Toast.makeText(context, "下載失敗", Toast.LENGTH_LONG).show();
}
});
}
public void comparePath(Context context, PatchBean RemoteBean) throws Exception {
if (mLocalPreferencesHelper == null) {
mLocalPreferencesHelper = new LocalPreferencesHelper(context, SPConst.SP_NAME);
}
String pathInfo = mLocalPreferencesHelper.getString(SPConst.PATH_INFO);
final PatchBean localBean = null; //GsonUtils.getInstance().parseIfNull(PatchBean.class, pathInfo);
//遠端的應用版本跟當前應用的版本比較
if (MyApplication.VERSION_NAME.equals(RemoteBean.app_v)) {
//遠端的應用版本跟本地儲存的應用版本一樣,但補丁不一樣,則需要下載重新
/**
*第一種情況:當本地記錄的Bean為空的時候(剛安裝的時候可能為空)並且遠端的Bean的path_v不為空的時候需要下載補丁。
* 第二種情況:當本地記錄的path_v和遠端Bean的path_v不一樣的時候需要下載補丁。
*/
if (localBean == null && !TextUtils.isEmpty(RemoteBean.path_v)
|| localBean.app_v.equals(RemoteBean.app_v) &&
!localBean.path_v.equals(RemoteBean.path_v)) {
downloadAndLoad(context, RemoteBean);//
String json = GsonUtils.getInstance().parse(RemoteBean);
mLocalPreferencesHelper.saveOrUpdate(SPConst.PATH_INFO, json);
} /*else {
mLocalPreferencesHelper.saveOrUpdate(SPConst.IsHavePathDownLoad, false);
}*/
}
}
}