2018年過去了,很久之前就希望自己可以潛心研究源碼,研究技術,但是空閑時間不是看電影,就是玩遊戲都沒有認真看技術方面的東西感覺很內疚,2019年一定要好好的研究端正態度,認真學習技術。
有時候Android應用中要獲取比較大的數據,比如說圖片流,短視頻流等,如果每次都從網路上去請求,那麼響應速度很慢的,用戶體驗不好。
如果把伺服器拉下來的數據保存在本地資料庫中,在伺服器數據並沒有發生改變的時候,直接從本地中獲取數據,這就是Android中的二級緩存,比直接每次從伺服器中拉取數據多了本地的存儲。二級緩存原理如下圖:
由上圖可知,在二級緩存中,Android中的activity請求數據時,都是先從本地資料庫中拿數據,當然activity並不知道管獲取的數據是從本地資料庫中還是伺服器中,本地數據和伺服器數據可以統一為數據源。當伺服器數據有改變的時候會從伺服器中拉取數據,但是拉取的數據先存在本地資料庫中然後再由本地資料庫返回給activity。
二級緩存的缺點是每次activity的數據都要從本地資料庫中獲取,雖然從本地資料庫中獲取的數據速度要比從伺服器獲取的速度快,但是每次讀寫資料庫進行的IO操作也是很花費時間的。
三級緩存在二級緩存的基礎上加了一個內存。從伺服器獲取的資料庫除了存在本地資料庫中,同時在內存中也保存一份,這樣當activity請求數據時可以先從內存中獲取數據,如果內存中沒有數據,或者內存中數據已經髒了的情況下,取本地資料庫中的數據。當本地資料庫的數據也髒了的情況下取伺服器數據。取回的數據存一份本地資料庫,存一份內存中。三級緩存原理如下圖:
上面的圖畫的比較亂,流程是:當activity要請求數據時
由三級緩存的原理可以實現三級緩存的框架,數據的來源有三個地方,內存,本地資料庫,伺服器但是應用層並不關心數據來自哪裡,所以要定義一個數據倉庫,裡面處理數據邏輯,當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(); } } }); } };
@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); } };
@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); } } }
推薦閱讀: