前言
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 classTasksRepository 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;}//获取任务@Overridepublic void getTasks(@NonNull final LoadTasksCallback callback) {checkNotNull(callback);// Respond immediately with cache if available and not dirtyif (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() {@Overridepublic void onTasksLoaded(List<Task> tasks) {refreshCache(tasks);callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));}@Overridepublic void onDataNotAvailable() {getTasksFromRemoteDataSource(callback);}});}}//保存任务@Overridepublic 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 dateif (mCachedTasks == null) {mCachedTasks = new LinkedHashMap<>();}mCachedTasks.put(task.getId(), task);}//通过taskid获取任务@Overridepublic void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {checkNotNull(taskId);checkNotNull(callback);Task cachedTask = getTaskWithId(taskId);// Respond immediately with cache if availableif (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() {@Overridepublic void onTaskLoaded(Task task) {// Do in memory cache update to keep the app UI up to dateif (mCachedTasks == null) {mCachedTasks = new LinkedHashMap<>();}mCachedTasks.put(task.getId(), task);callback.onTaskLoaded(task);}@Overridepublic void onDataNotAvailable() {mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {@Overridepublic void onTaskLoaded(Task task) {// Do in memory cache update to keep the app UI up to dateif (mCachedTasks == null) {mCachedTasks = new LinkedHashMap<>();}mCachedTasks.put(task.getId(), task);callback.onTaskLoaded(task);}@Overridepublic void onDataNotAvailable() {callback.onDataNotAvailable();}});}});}//刷新任务@Overridepublic void refreshTasks() {mCacheIsDirty = true;}//删除全部任务@Overridepublic void deleteAllTasks() {mTasksRemoteDataSource.deleteAllTasks();mTasksLocalDataSource.deleteAllTasks();if (mCachedTasks == null) {mCachedTasks = new LinkedHashMap<>();}mCachedTasks.clear();}//删除任务@Overridepublic 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() {@Overridepublic void onTasksLoaded(List<Task> tasks) {refreshCache(tasks);refreshLocalDataSource(tasks);callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));}@Overridepublic 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 doesn\'t exist* or the table is empty.*/@Overridepublic void getTasks(@NonNull final LoadTasksCallback callback) {Runnable runnable = new Runnable() {@Overridepublic void run() {final List<Task> tasks = mTasksDao.getTasks();mAppExecutors.mainThread().execute(new Runnable() {@Overridepublic 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} isn\'t* found.*/@Overridepublic void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {Runnable runnable = new Runnable() {@Overridepublic void run() {final Task task = mTasksDao.getTaskById(taskId);mAppExecutors.mainThread().execute(new Runnable() {@Overridepublic void run() {if (task != null) {callback.onTaskLoaded(task);} else {callback.onDataNotAvailable();}}});}};mAppExecutors.diskIO().execute(runnable);}@Overridepublic void saveTask(@NonNull final Task task) {checkNotNull(task);Runnable saveRunnable = new Runnable() {@Overridepublic void run() {mTasksDao.insertTask(task);}};mAppExecutors.diskIO().execute(saveRunnable);}@Overridepublic void completeTask(@NonNull final Task task) {Runnable completeRunnable = new Runnable() {@Overridepublic void run() {mTasksDao.updateCompleted(task.getId(), true);}};mAppExecutors.diskIO().execute(completeRunnable);}@Overridepublic 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.}@Overridepublic void activateTask(@NonNull final Task task) {Runnable activateRunnable = new Runnable() {@Overridepublic void run() {mTasksDao.updateCompleted(task.getId(), false);}};mAppExecutors.diskIO().execute(activateRunnable);}@Overridepublic 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.}@Overridepublic void clearCompletedTasks() {Runnable clearTasksRunnable = new Runnable() {@Overridepublic void run() {mTasksDao.deleteCompletedTasks();}};mAppExecutors.diskIO().execute(clearTasksRunnable);}@Overridepublic void refreshTasks() {// Not required because the {@link TasksRepository} handles the logic of refreshing the// tasks from all the available data sources.}@Overridepublic void deleteAllTasks() {Runnable deleteRunnable = new Runnable() {@Overridepublic void run() {mTasksDao.deleteTasks();}};mAppExecutors.diskIO().execute(deleteRunnable);}@Overridepublic void deleteTask(@NonNull final String taskId) {Runnable deleteRunnable = new Runnable() {@Overridepublic void run() {mTasksDao.deleteTaskById(taskId);}};mAppExecutors.diskIO().execute(deleteRunnable);}@VisibleForTestingstatic 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;}@Overridepublic void getTasks(@NonNull LoadTasksCallback callback) {callback.onTasksLoaded(Lists.newArrayList(TASKS_SERVICE_DATA.values()));}@Overridepublic void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback) {Task task = TASKS_SERVICE_DATA.get(taskId);callback.onTaskLoaded(task);}@Overridepublic void saveTask(@NonNull Task task) {TASKS_SERVICE_DATA.put(task.getId(), task);}@Overridepublic void completeTask(@NonNull Task task) {Task completedTask = new Task(task.getTitle(), task.getDescription(), task.getId(), true);TASKS_SERVICE_DATA.put(task.getId(), completedTask);}@Overridepublic void completeTask(@NonNull String taskId) {// Not required for the remote data source.}@Overridepublic void activateTask(@NonNull Task task) {Task activeTask = new Task(task.getTitle(), task.getDescription(), task.getId());TASKS_SERVICE_DATA.put(task.getId(), activeTask);}@Overridepublic void activateTask(@NonNull String taskId) {// Not required for the remote data source.}@Overridepublic 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.}@Overridepublic void deleteTask(@NonNull String taskId) {TASKS_SERVICE_DATA.remove(taskId);}@Overridepublic void deleteAllTasks() {TASKS_SERVICE_DATA.clear();}@VisibleForTestingpublic void addTasks(Task... tasks) {for (Task task : tasks) {TASKS_SERVICE_DATA.put(task.getId(), task);}}}