前言

2018年過去了,很久之前就希望自己可以潛心研究源碼,研究技術,但是空閑時間不是看電影,就是玩遊戲都沒有認真看技術方面的東西感覺很內疚,2019年一定要好好的研究端正態度,認真學習技術。

為什麼要三級緩存

有時候Android應用中要獲取比較大的數據,比如說圖片流,短視頻流等,如果每次都從網路上去請求,那麼響應速度很慢的,用戶體驗不好。

二級緩存

如果把伺服器拉下來的數據保存在本地資料庫中,在伺服器數據並沒有發生改變的時候,直接從本地中獲取數據,這就是Android中的二級緩存,比直接每次從伺服器中拉取數據多了本地的存儲。二級緩存原理如下圖:

由上圖可知,在二級緩存中,Android中的activity請求數據時,都是先從本地資料庫中拿數據,當然activity並不知道管獲取的數據是從本地資料庫中還是伺服器中,本地數據和伺服器數據可以統一為數據源。當伺服器數據有改變的時候會從伺服器中拉取數據,但是拉取的數據先存在本地資料庫中然後再由本地資料庫返回給activity。

二級緩存缺點

二級緩存的缺點是每次activity的數據都要從本地資料庫中獲取,雖然從本地資料庫中獲取的數據速度要比從伺服器獲取的速度快,但是每次讀寫資料庫進行的IO操作也是很花費時間的。

三級緩存

三級緩存在二級緩存的基礎上加了一個內存。從伺服器獲取的資料庫除了存在本地資料庫中,同時在內存中也保存一份,這樣當activity請求數據時可以先從內存中獲取數據,如果內存中沒有數據,或者內存中數據已經髒了的情況下,取本地資料庫中的數據。當本地資料庫的數據也髒了的情況下取伺服器數據。取回的數據存一份本地資料庫,存一份內存中。三級緩存原理如下圖:

上面的圖畫的比較亂,流程是:當activity要請求數據時

  1. 先檢查內存中緩存數據如果內存中有數據並且數據不臟時直接返回內存中的數據。
  2. 如果內存中無數據並且數據不為臟時向本地資料庫中請求數據,並且將請求的數據寫入到內存中,再將內存中的數據返回。
  3. 如果內存和本地資料庫中都沒有數據返回,也就是內存中無數據並且數據為臟時,向伺服器請求數據,伺服器返回的數據,保存到本地資料庫並且保存一份到內存,最後將內存中的數據返回。

三級緩存實現

由三級緩存的原理可以實現三級緩存的框架,數據的來源有三個地方,內存,本地資料庫,伺服器但是應用層並不關心數據來自哪裡,所以要定義一個數據倉庫,裡面處理數據邏輯,當activity請求數據時直接由數據倉庫來返回數據。

定義數據倉庫之前可以定義一個數據來源介面,這個介面定義數據的操作,比如插入數據,刪除數據,返回數據等。類圖:

數據來源介面:TasksDataSource

public interface TasksDataSource {

interface LoadTasksCallback {

void onTasksLoaded(List<Task> tasks);//獲取數據成功

void onDataNotAvailable();//無數據或者獲取數據失敗
}//多個任務獲取數據回調

interface GetTaskCallback {

void onTaskLoaded(Task task);//獲取數據成功

void onDataNotAvailable();//無數據或者獲取數據失敗
}//獲取單個任務回調

void getTasks(@NonNull LoadTasksCallback callback);

void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);

void saveTask(@NonNull Task task);//保存數據

void refreshTasks();//刷新數據

void deleteAllTasks();//刪除全部數據

void deleteTask(@NonNull String taskId);//刪除單個任務數據
}

上面定義了數據源類TasksDataSource在這個類中定義了獲取任務或者任務組的回調,已經獲取任務,存儲任務,刪除任務,刷新任務的方法。其中的task是自定義的應用中要用到的數據類型,這裡定義的比較簡單:

public final class Task {

@PrimaryKey
@NonNull
@ColumnInfo(name = "entryid")
private final String mId;//任務Id也是唯一鍵

@Nullable
@ColumnInfo(name = "title")
private final String mTitle;//任務標題

@Nullable
@ColumnInfo(name = "description")
private final String mDescription;//任務描述

public Task(@Nullable String title, @Nullable String description,
@NonNull String id) {
mId = id;
mTitle = title;
mDescription = description;
}

}

因為Task要寫入到本地資料庫,所以mId作為唯一鍵。 有了數據來源再定義本地數據倉庫,數據倉庫向activity提供數據,並且實現上面的三級緩存原理。數據倉庫中處理三種來源的數據,並將最後的結果返回。 數據倉庫TasksRepository

public class

TasksRepository implements TasksDataSource {

private static TasksRepository INSTANCE = null;

private final TasksDataSource mTasksRemoteDataSource;//伺服器數據來源

private final TasksDataSource mTasksLocalDataSource;//本地資料庫數據源

Map<String, Task> mCachedTasks;//內存緩存

boolean mCacheIsDirty = false;//標記緩存數據是否臟

// Prevent direct instantiation.
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}

public static TasksRepository getInstance(TasksDataSource tasksRemoteDataSource,
TasksDataSource tasksLocalDataSource) {
if (INSTANCE == null) {
INSTANCE = new TasksRepository(tasksRemoteDataSource, tasksLocalDataSource);
}
return INSTANCE;
}

public static void destroyInstance() {
INSTANCE = null;
}
//獲取任務
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);

// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}

if (mCacheIsDirty) {
// If the cache is dirty we need to fetch new data from the network.
getTasksFromRemoteDataSource(callback);
} else {
// Query the local storage if available. If not, query the network.
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}

@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
//保存任務
@Override
public void saveTask(@NonNull Task task) {
checkNotNull(task);
mTasksRemoteDataSource.saveTask(task);
mTasksLocalDataSource.saveTask(task);

// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
}
//通過taskid獲取任務
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
checkNotNull(taskId);
checkNotNull(callback);

Task cachedTask = getTaskWithId(taskId);

// Respond immediately with cache if available
if (cachedTask != null) {
callback.onTaskLoaded(cachedTask);
return;
}

// Load from server/persisted if needed.

// Is the task in the local data source? If not, query the network.
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}

@Override
public void onDataNotAvailable() {
mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// Do in memory cache update to keep the app UI up to date
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.put(task.getId(), task);
callback.onTaskLoaded(task);
}

@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
});
}
//刷新任務
@Override
public void refreshTasks() {
mCacheIsDirty = true;
}
//刪除全部任務
@Override
public void deleteAllTasks() {
mTasksRemoteDataSource.deleteAllTasks();
mTasksLocalDataSource.deleteAllTasks();

if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.clear();
}
//刪除任務
@Override
public void deleteTask(@NonNull String taskId) {
mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
mTasksLocalDataSource.deleteTask(checkNotNull(taskId));

mCachedTasks.remove(taskId);
}
//從伺服器獲取數據
private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks);
refreshLocalDataSource(tasks);
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}

@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
//刷新緩存數據
private void refreshCache(List<Task> tasks) {
if (mCachedTasks == null) {
mCachedTasks = new LinkedHashMap<>();
}
mCachedTasks.clear();
for (Task task : tasks) {
mCachedTasks.put(task.getId(), task);
}
mCacheIsDirty = false;
}
//刷新本地資料庫
private void refreshLocalDataSource(List<Task> tasks) {
mTasksLocalDataSource.deleteAllTasks();
for (Task task : tasks) {
mTasksLocalDataSource.saveTask(task);
}
}

}

本地數據來源類TasksLocalDataSource

public class TasksLocalDataSource implements TasksDataSource {

private static volatile TasksLocalDataSource INSTANCE;

private TasksDao mTasksDao;

private AppExecutors mAppExecutors;

// Prevent direct instantiation.
private TasksLocalDataSource(@NonNull AppExecutors appExecutors,
@NonNull TasksDao tasksDao) {
mAppExecutors = appExecutors;
mTasksDao = tasksDao;
}

public static TasksLocalDataSource getInstance(@NonNull AppExecutors appExecutors,
@NonNull TasksDao tasksDao) {
if (INSTANCE == null) {
synchronized (TasksLocalDataSource.class) {
if (INSTANCE == null) {
INSTANCE = new TasksLocalDataSource(appExecutors, tasksDao);
}
}
}
return INSTANCE;
}

/**
* Note: {@link LoadTasksCallback#onDataNotAvailable()} is fired if the database doesnt exist
* or the table is empty.
*/
@Override
public void getTasks(@NonNull final LoadTasksCallback callback) {
Runnable runnable = new Runnable() {
@Override
public void run() {
final List<Task> tasks = mTasksDao.getTasks();
mAppExecutors.mainThread().execute(new Runnable() {
@Override
public void run() {
if (tasks.isEmpty()) {
// This will be called if the table is new or just empty.
callback.onDataNotAvailable();
} else {
callback.onTasksLoaded(tasks);
}
}
});
}
};

mAppExecutors.diskIO().execute(runnable);
}

/**
* Note: {@link GetTaskCallback#onDataNotAvailable()} is fired if the {@link Task} isnt
* found.
*/
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
Runnable runnable = new Runnable() {
@Override
public void run() {
final Task task = mTasksDao.getTaskById(taskId);

mAppExecutors.mainThread().execute(new Runnable() {
@Override
public void run() {
if (task != null) {
callback.onTaskLoaded(task);
} else {
callback.onDataNotAvailable();
}
}
});
}
};

mAppExecutors.diskIO().execute(runnable);
}

@Override
public void saveTask(@NonNull final Task task) {
checkNotNull(task);
Runnable saveRunnable = new Runnable() {
@Override
public void run() {
mTasksDao.insertTask(task);
}
};
mAppExecutors.diskIO().execute(saveRunnable);
}

@Override
public void completeTask(@NonNull final Task task) {
Runnable completeRunnable = new Runnable() {
@Override
public void run() {
mTasksDao.updateCompleted(task.getId(), true);
}
};

mAppExecutors.diskIO().execute(completeRunnable);
}

@Override
public void completeTask(@NonNull String taskId) {
// Not required for the local data source because the {@link TasksRepository} handles
// converting from a {@code taskId} to a {@link task} using its cached data.
}

@Override
public void activateTask(@NonNull final Task task) {
Runnable activateRunnable = new Runnable() {
@Override
public void run() {
mTasksDao.updateCompleted(task.getId(), false);
}
};
mAppExecutors.diskIO().execute(activateRunnable);
}

@Override
public void activateTask(@NonNull String taskId) {
// Not required for the local data source because the {@link TasksRepository} handles
// converting from a {@code taskId} to a {@link task} using its cached data.
}

@Override
public void clearCompletedTasks() {
Runnable clearTasksRunnable = new Runnable() {
@Override
public void run() {
mTasksDao.deleteCompletedTasks();

}
};

mAppExecutors.diskIO().execute(clearTasksRunnable);
}

@Override
public void refreshTasks() {
// Not required because the {@link TasksRepository} handles the logic of refreshing the
// tasks from all the available data sources.
}

@Override
public void deleteAllTasks() {
Runnable deleteRunnable = new Runnable() {
@Override
public void run() {
mTasksDao.deleteTasks();
}
};

mAppExecutors.diskIO().execute(deleteRunnable);
}

@Override
public void deleteTask(@NonNull final String taskId) {
Runnable deleteRunnable = new Runnable() {
@Override
public void run() {
mTasksDao.deleteTaskById(taskId);
}
};

mAppExecutors.diskIO().execute(deleteRunnable);
}

@VisibleForTesting
static void clearInstance() {
INSTANCE = null;
}
}

伺服器數據來源類:FakeTasksRemoteDataSource

public class FakeTasksRemoteDataSource implements TasksDataSource {

private static FakeTasksRemoteDataSource INSTANCE;

private static final Map<String, Task> TASKS_SERVICE_DATA = new LinkedHashMap<>();

// Prevent direct instantiation.
private FakeTasksRemoteDataSource() {}

public static FakeTasksRemoteDataSource getInstance() {
if (INSTANCE == null) {
INSTANCE = new FakeTasksRemoteDataSource();
}
return INSTANCE;
}

@Override
public void getTasks(@NonNull LoadTasksCallback callback) {
callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));
}

@Override
public void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {
Task task = TASKS_SERVICE_DATA.get(taskId);
callback.onTaskLoaded(task);
}

@Override
public void saveTask(@NonNull Task task) {
TASKS_SERVICE_DATA.put(task.getId(), task);
}

@Override
public void completeTask(@NonNull Task task) {
Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);
TASKS_SERVICE_DATA.put(task.getId(), completedTask);
}

@Override
public void completeTask(@NonNull String taskId) {
// Not required for the remote data source.
}

@Override
public void activateTask(@NonNull Task task) {
Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());
TASKS_SERVICE_DATA.put(task.getId(), activeTask);
}

@Override
public void activateTask(@NonNull String taskId) {
// Not required for the remote data source.
}

@Override
public void clearCompletedTasks() {
Iterator<Map.Entry<String, Task>> it = TASKS_SERVICE_DATA.entrySet().iterator();
while (it.hasNext()) {
Map.Entry<String, Task> entry = it.next();
if (entry.getValue().isCompleted()) {
it.remove();
}
}
}

public void refreshTasks() {
// Not required because the {@link TasksRepository} handles the logic of refreshing the
// tasks from all the available data sources.
}

@Override
public void deleteTask(@NonNull String taskId) {
TASKS_SERVICE_DATA.remove(taskId);
}

@Override
public void deleteAllTasks() {
TASKS_SERVICE_DATA.clear();
}

@VisibleForTesting
public void addTasks(Task... tasks) {
for (Task task : tasks) {
TASKS_SERVICE_DATA.put(task.getId(), task);
}
}
}

喜歡點擊+關注哦

推薦閱讀:

相關文章