天天看點

AndFix解析——(上)

阿裡巴巴前一段時間開源了他們用來解決線上緊急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