谁能给我提供关于如何检索文件夹中最近打开的文件列表的指导方针?
在我的应用程序中,我有一个包含.epub
文件的文件夹。我想显示用户最近阅读/打开书籍的排序列表。
这个问题其实并不那么简单。有两个原因——历史记录的持久性和IO阻塞操作。
事实上,为了确保历史的持久性,我们可以使用一些解决方案:
- 数据库(似乎最合理,但需要最多的努力)
- 内部文件(应该是最简单的方法)
- 共享偏好
所以,我使用了第二种方法。在内存中,我只保留ArrayList<Uri>
,并将其保存到文件中,我将其转换为List<String>
,因为android.net.Uri
不支持序列化。在内部内存中,我使用ObjectIOStream
保存序列化对象。
所以,看看代码:
public class FileHistory {
private static final String FILE_NAME = "file-history-v1";
private static final int HISTORY_SIZE = 20;
@NonNull
private final Context mAppContext;
// This is a executor where I can post any runnable
// and all of them will be executed in one pipeline
// keeping posting order.
@NonNull
private final OneThreadExecutor mExecutor;
@Nullable
private ArrayList<Uri> mInternalFilesHistory;
@NonNull
private MutableLiveData<List<Uri>> mFilesHistory = new MutableLiveData<>();
public FileHistory(@NonNull final Context appContext,
@NonNull final OneThreadExecutor executor) {
this.mAppContext = appContext;
this.mExecutor = executor;
loadHistory();
}
public void addEntry(@NonNull final Uri entry) {
if (mInternalFilesHistory == null) {
// The fileHistory is not ready yet.
// Schedule adding entry as next task of serial executor.
mExecutor.execute(() -> addEntry(entry));
return;
}
// Remove entry if exists and add it as first element.
CollectionUtils.removeFirst(mInternalFilesHistory, uri -> uri.equals(entry));
mInternalFilesHistory.add(0, entry);
if (mInternalFilesHistory.size() > HISTORY_SIZE) {
ArrayList<Uri> trimmed = new ArrayList<>(HISTORY_SIZE + 1);
trimmed.addAll(mInternalFilesHistory.subList(0, HISTORY_SIZE));
mInternalFilesHistory = trimmed;
}
mExecutor.execute(this::rePostHistory);
mExecutor.execute(this::saveToInternalStorage);
}
@NonNull
public MutableLiveData<List<Uri>> getFilesHistory() {
return mFilesHistory;
}
private void loadHistory() {
mExecutor.execute(this::loadFromInternalStorage);
mExecutor.execute(this::rePostHistory);
}
private void rePostHistory() {
if (mInternalFilesHistory != null) {
mFilesHistory.postValue(Collections.unmodifiableList(mInternalFilesHistory));
}
}
@SuppressWarnings("unchecked")
@WorkerThread
private void loadFromInternalStorage() {
try {
FileInputStream fis = mAppContext.openFileInput(FILE_NAME);
ObjectInputStream ois = new ObjectInputStream(fis);
ArrayList<String> entries = (ArrayList<String>) ois.readObject();
List<Uri> mapped = CollectionUtils.map(entries, Uri::parse);
if (mInternalFilesHistory == null) {
mInternalFilesHistory = new ArrayList<>(HISTORY_SIZE + 1);
} else {
mInternalFilesHistory.clear();
}
mInternalFilesHistory.addAll(mapped);
fis.close();
ois.close();
} catch (Exception ex) {
mInternalFilesHistory = new ArrayList<>(HISTORY_SIZE + 1);
}
}
@WorkerThread
private void saveToInternalStorage() {
try {
FileOutputStream fis = mAppContext.openFileOutput(FILE_NAME, Context.MODE_PRIVATE);
ObjectOutputStream oos = new ObjectOutputStream(fis);
if (mInternalFilesHistory == null) {
mInternalFilesHistory = new ArrayList<>();
}
List<String> converted = CollectionUtils.map(mInternalFilesHistory, Uri::toString);
oos.writeObject(converted);
fis.close();
oos.close();
} catch (IOException ignored) {
}
}
}
可以看到,内部存储用于保存该文件。因此,不需要添加任何额外的权限。同步是通过使用executor来确保的,executor将一个接一个地执行所有请求,因此即使IO很慢,也会保存顺序或请求。
我们不阻塞线程的IO操作,因为所有使用IO的操作都在WorkerThread
上。关于结果,我们将通过LiveData
从android.arch.
得到通知。在我看来,这是最简单的解决方案。如果我们需要保存统计数据,日期等,我们可以保存List<MyHistoryEntry>
,只要MyHistoryEntry
是可序列化的。
作为一个更好的方法,我建议使用数据库(更容易迁移等)。