阿裡巴巴前一段時間開源了他們用來解決線上緊急bug的一款Android庫——
AndFix
對Android開發者來說真是一個很好的消息。
基于自己的經驗,太長的文字很少有人可以一口氣看下來的,是以我打算分成多篇來分析 這是這個庫解析的第一篇,
我們先看一下其中的Demo代碼,其中調用加載庫的代碼如下所示:
/**
* 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 PatchManager mPatchManager;
@Override
public void onCreate() {
super.onCreate();
// initialize
mPatchManager = new PatchManager(this);
mPatchManager.init("1.0");
Log.d(TAG, "inited.");
// load patch
mPatchManager.loadPatch();
Log.d(TAG, "apatch loaded.");
// add patch at runtime
try {
// .apatch file path
String patchFileString = Environment.getExternalStorageDirectory()
.getAbsolutePath() + APATCH_PATH;
mPatchManager.addPatch(patchFileString);
Log.d(TAG, "apatch:" + patchFileString + " added.");
} catch (IOException e) {
Log.e(TAG, "", e);
}
}
}
可以看到代碼中首先通過PatchManager的構造函數初始化了
PatchManager
對象,
PatchManager構造函數
那麼
PatchManager
對象裡面都有什麼呢,我們深入其中了解一下。
public PatchManager(Context context) {
this.mContext = context;
this.mAndFixManager = new AndFixManager(this.mContext);
this.mPatchDir = new File(this.mContext.getFilesDir(), "apatch");
this.mPatchs = new ConcurrentSkipListSet();
this.mLoaders = new ConcurrentHashMap();
}
原來維持了一個Context對象的引用,初始化了AndFixManager對象,mPatchDir對象為存放Patch檔案的檔案夾,初始化了Patch的集合,還有持有ClassLoader的Map
其中一些内容的聲明如下:
private final Context mContext;
private final AndFixManager mAndFixManager;
private final File mPatchDir;
private final SortedSet<Patch> mPatchs;
private final Map<String, ClassLoader> mLoaders;
我們對構造函數的分析在這裡就結束了,我們先不深入的跟進Patch和AndFixManager這兩個類了。
PatchManager init(String version)
接下來分析
PatchManager
類中的
init(String version)
函數, 先來看代碼
public void init(String appVersion) {
//如果mPatchDir不存在,則建立檔案夾,如果建立失敗,則列印Log
if(!this.mPatchDir.exists() && !this.mPatchDir.mkdirs()) {
Log.e("PatchManager", "patch dir create error.");
} else if(!this.mPatchDir.isDirectory()) {
//如果遇到同名的檔案,則将該同名檔案删除
this.mPatchDir.delete();
} else {
//在該檔案下放入一個名為_andfix_的SharedPreferences檔案,
SharedPreferences sp = this.mContext.getSharedPreferences("_andfix_", );
String ver = sp.getString("version", (String)null);
if(ver != null && ver.equalsIgnoreCase(appVersion)) {
this.initPatchs();
} else {
this.cleanPatch();
sp.edit().putString("version", appVersion).commit();
}
}
}
接下來我們分析上面代碼中的如下代碼
//如果從_andfix_這個檔案擷取的ver不是null,而且這個ver和外部初始化時傳進來的版本号一緻
if(ver != null && ver.equalsIgnoreCase(appVersion)) {
this.initPatchs();
} else {
this.cleanPatch();
sp.edit().putString("version", appVersion).commit();
}
先看else内的内容,else裡執行力
cleanPatch()
和把外部初始化的時候傳進來的版本号放入SharedPreferences裡。
cleanPatch()
做了什麼操作呢,我們跟進去看一看
private void cleanPatch() {
//擷取mPatchDir目錄下所有檔案
File[] files = this.mPatchDir.listFiles();
File[] arr$ = files;
int len$ = files.length;
for(int i$ = ; i$ < len$; ++i$) {
File file = arr$[i$];
//将此檔案從OptFile檔案夾删除
this.mAndFixManager.removeOptFile(file);
//這個方法的作用就是如果file是檔案,則删除它,如果file是檔案夾,則将它和它裡面的檔案都删除
if(!FileUtil.deleteFile(file)) {
Log.e("PatchManager", file.getName() + " delete error.");
}
}
}
如源碼中的注釋,就是删除了之前在那兩個檔案夾下的所有的更新檔檔案。
現在來分析一下
this.initPatchs()
做了什麼事
private void initPatchs() {
File[] files = this.mPatchDir.listFiles();
File[] arr$ = files;
int len$ = files.length;
for(int i$ = ; i$ < len$; ++i$) {
File file = arr$[i$];
this.addPatch(file);
}
}
代碼很簡單,就是把mPatchDir檔案夾下的檔案作為參數傳給了
addPatch(File)
方法 那
this.addPatch(file)
做了什麼呢
//把擴充名為.apatch的檔案傳給Patch做參數,初始化對應的Patch,
//并把剛初始化的Patch加入到我們之前看到的Patch集合mPatchs中
private Patch addPatch(File file) {
Patch patch = null;
//擴充名是否為".apatch"
if(file.getName().endsWith(".apatch")) {
try {
patch = new Patch(file);
this.mPatchs.add(patch);
} catch (IOException var4) {
Log.e("PatchManager", "addPatch", var4);
}
}
return patch;
}
上面的代碼很好了解,此時,我們已經完整的走下來了
init(String version)
這個方法。 再次出現了Patch這個類,但是我們依然要把它放在一邊,因為由于篇幅限制,第一篇不會分析這個類。
接下來,我們繼續跟着demo走,會執行兩個方法,一個
mPatchManager.loadPatch()
,一個
mPatchManager.addPatch(patchFileString)
,
loadPatch()
方法是這個庫執行替換的核心方法,我會在以後單獨寫一篇文章來分析,是以,在這篇文章的最後,我們跟進
addPatch(String)
這個方法一看究竟
public void addPatch(String path) throws IOException {
File src = new File(path);
File dest = new File(this.mPatchDir, src.getName());
if(dest.exists()) {
//在mPatchDir檔案夾下存在該檔案,則AndFixManager移除該檔案
this.mAndFixManager.removeOptFile(dest);
}
//将檔案從src複制到dest,隻不過阿裡用了NIO來複制檔案
FileUtil.copyFile(src, dest);
//調用了另外一個addPatch方法,以檔案作為參數
Patch patch = this.addPatch(dest);
if(patch != null) {
//同樣調用了loadPatch(Patch)方法
this.loadPatch(patch);
}
}
簡單來說,上面的方法就是将更新檔檔案複制到/data/data/{包名}/apatch 目錄内,如果在OptFile檔案夾中存在,則删除。 然後調用另外一個
addPatch(File)
方法,然後
loadPatch()
下面,我們來看一下重載的
addPatch(File)
方法
private Patch addPatch(File file) {
Patch patch = null;
if(file.getName().endsWith(".apatch")) {
try {
patch = new Patch(file);
//把從file檔案生成的patch加入到mPatchs這個Set中
this.mPatchs.add(patch);
} catch (IOException var4) {
Log.e("PatchManager", "addPatch", var4);
}
}
return patch;
}
該方法主要做的事情在注釋中即可了解,到這裡,第一篇分析就結束了。
原文位址: http://yunair.github.io/blog/2015/09/25/AndFix-%E8%A7%A3%E6%9E%90(%E4%B8%8A).html