前言
最近做文件下載緩存的時候,有這么一個需求,緩存文件有一個最大值限制,如果文件下載下來要超過緩存的最大值,那么就不進行下載.
我的方案
- 使用固定核心線程數的線程池執行下載任務
- 每次下載文件之前,先獲取文件長度,看當前文件大小加上本地已有的文件大小會不會超出最大緩存大小.
- 因為三個線程並行下載,可能三個線程同時走到判斷大小的位置,如果都判斷沒有超過最大值就進行下載,那么可能下載后就超出大小了.
- 為了解決第3點提到的問題,使用了
FileObserver
對緩存文件夾的相關事件進行觀察.如果下載完成后的總的文件大小超過了最大緩存size,就刪除剛剛下載的文件.
FileObserver的使用
初始化FileObserver
對象,復寫onEvent
方法,並開啟監聽,代碼片段如下.
private void initFileObserver() {
mObserver = new FileObserver(mCacheDir) {
@Override
public void onEvent(int event, @Nullable String path) {
switch (event) {
case FileObserver.CLOSE_WRITE:
Log.i(TAG, "onEvent: CLOSE_WRITE," + path);
File newFile = null;
if (path != null) {
newFile = new File(mCacheDir, path);
}
mExecutor.submit(new CountFileCallable(newFile));
break;
case FileObserver.DELETE:
Log.i(TAG, "onEvent: DELETE," + path);
mExecutor.submit(new CountFileCallable(null));
break;
default:
}
}
};
mObserver.startWatching();
}
FileObserver
的實現基本都是本地方法,大致是開啟一個線程對文件的各種事件監聽,java
層就只是實現了觀察者模式.
FileObserver
可以監聽的事件類型也很多,大概一般的文件操作都有對應的事件,如下
/** Event type: Data was read from a file */
public static final int ACCESS = 0x00000001;
/** Event type: Data was written to a file */
public static final int MODIFY = 0x00000002;
/** Event type: Metadata (permissions, owner, timestamp) was changed explicitly */
public static final int ATTRIB = 0x00000004;
/** Event type: Someone had a file or directory open for writing, and closed it */
public static final int CLOSE_WRITE = 0x00000008;
/** Event type: Someone had a file or directory open read-only, and closed it */
public static final int CLOSE_NOWRITE = 0x00000010;
/** Event type: A file or directory was opened */
public static final int OPEN = 0x00000020;
/** Event type: A file or subdirectory was moved from the monitored directory */
public static final int MOVED_FROM = 0x00000040;
/** Event type: A file or subdirectory was moved to the monitored directory */
public static final int MOVED_TO = 0x00000080;
/** Event type: A new file or subdirectory was created under the monitored directory */
public static final int CREATE = 0x00000100;
/** Event type: A file was deleted from the monitored directory */
public static final int DELETE = 0x00000200;
/** Event type: The monitored file or directory was deleted; monitoring effectively stops */
public static final int DELETE_SELF = 0x00000400;
/** Event type: The monitored file or directory was moved; monitoring continues */
public static final int MOVE_SELF = 0x00000800;
FileObserver
只傳遞一個文件路徑的構造函數就是監聽所有的事件,只要有對應的事件觸發,你就可以在onEvent
方法中收到回調.
這里我監聽了寫入關閉和刪除文件的事件,收到事件便提交了一個任務
private class CountFileCallable implements Callable<Void> {
private final File addFile;
CountFileCallable(File addFile) {
this.addFile = addFile;
}
@Override
public Void call() {
computeFileSize(addFile);
return null;
}
}
private void computeFileSize(File addFile) {
long totalSize = getTotalSize();
boolean accepted = totalSize < mCacheSize;
Log.i(TAG, "totalSize: " + totalSize + "-- mCacheSize: " + mCacheSize);
if (!accepted) {
//這里是保證不會讓緩存超出大小
if (addFile != null) {
addFile.delete();
Log.i(TAG, "delete file: " + addFile.getPath());
}
}
}
這個任務主要是計算當前緩存文件夾的大小,如果超過緩存最大size,就刪除最后添加的文件.整體的思路就是這樣的,看起來沒什么問題,這是改正后的版本.
我之前監聽的是CREATE事件,即文件創建事件,此時如果提交任務,文件可能還沒有下載完成,此時是不能計算出准確的緩存文件夾大小的,所以最后是超出了最大緩存大小.被測試提了bug.
總結
在使用不是很熟悉的api
時,還是得先看一遍源碼,至少得把主要方法看一遍,尤其是官方注釋文檔,其實文檔對每個事件的定義解釋得很清楚(當時沒看,想當然了),看不懂盡量使用翻譯工具,可以避免很多不必要的坑.