淺析MVP中model層設計【從零開始搭建android框架系列(7)】
更多及時技術資訊,歡迎關注我的微博 :Anthony
原文鏈接:Anthony的簡書博客
推薦鏈接:安卓架構文章合集
1 前言
在本系列文章從零開始搭建android框架系列之前我多次提到了官方mvp項目的構建。並應用到了項目MVPCommon中。但是細心的你肯定都會發現,之前的文章都在整體上對MVP 的使用進行了說明,卻對其中的model層一言帶過。包括數據也是大多采用假數據。
使用了MVP,我們肯定不會再像以前網絡訪問數據,SharedPreference保存數據,本地數據庫保存,緩存數據等的處理分散於每個activity或者fragment之間。數據的獲取、存儲、數據狀態變化都將是是Model層的任務。RxJava,Retrofit,EventBus,SqlBrite等技術都會在後續得到分析和使用。
下面的文章將會從一些優秀的模板代碼分析出發,研究MVP中model層的設計,後續的文章將會在這些基礎上繼續分析和使用。
這裏整理網絡上也有不少關於這方面優秀文章,將會在參考資料中給出。
2 google官方mvp中model層設計
之前兩篇文章分別對官方google官方架構MVP解析與實戰和Google官方MVP+Dagger2架構詳解項目進行了解析,並沒有對其中的model層進行分析,這裏單獨抽取出來和大家共同學習一下。其實該項目中Model層最大的特點是被賦予了數據獲取的職責,與我們平常Model層只定義javabean,實體對象截然不同。實例中,數據的獲取、存儲、數據狀態變化都是Model層的任務,Presenter會根據需要調用該層的數據處理邏輯並在需要時將回調傳入。
我們來看TaskDetailPresenter 的 start() 方法:
@Override
public void start() {
openTask();
}
private void openTask() {
// 判空處理
if (null == mTaskId || mTaskId.isEmpty()) {
mTaskDetailView.showMissingTask();
return;
}
// 更新狀態
mTaskDetailView.setLoadingIndicator(true);
// 獲取該條Task數據
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
// View已經被用戶回退
if (!mTaskDetailView.isActive()) {
return;
}
// 獲取到task數據,並更新UI
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
mTaskDetailView.showMissingTask();
} else {
showTask(task);
}
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
// 顯示數據獲取失敗時的狀態
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
可以看到的是presenter中調用了mTaskDetailView.setLoadingIndicator(true);
中更新view層的狀態之後,接着調用mTasksRepository.getTask......
獲取數據,並且在數據獲取成功和失敗後,分別處理回調方法onTaskLoaded
,onDataNotAvailable
,並在其中更新view。將數據的操作完全交給TasksRepository
處理。
我們接着看 TasksRepository 中的getTask() 方法,
@Singleton
public class TasksRepository implements TasksDataSource {
......
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
@Inject
TasksRepository(@Remote TasksDataSource tasksRemoteDataSource,
@Local TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = tasksRemoteDataSource;
mTasksLocalDataSource = tasksLocalDataSource;
}
......
/**
* Gets tasks from local data source (sqlite) unless the table is new or empty. In that case it
* uses the network data source. This is done to simplify the sample.
*/
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
checkNotNull(taskId);
checkNotNull(callback);
Task cachedTask = getTaskWithId(taskId);
// 緩存獲取數據,並回調
if (cachedTask != null) {
callback.onTaskLoaded(cachedTask);
return;
}
// 本地獲取數據
mTasksLocalDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
//本地獲取數據失敗,使用網絡數據
mTasksRemoteDataSource.getTask(taskId, new GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
callback.onDataNotAvailable();
}
});
}
});
}
}
我們發現 TasksRepository 維護了兩個數據源,一個是本地mTasksLocalDataSource
,一個是遠程mTasksRemoteDataSource
。從getTask
方法中可以看到這裏首先從緩存獲取數據,如果獲取成功則直接回調·callback.onTaskLoaded(cachedTask);`,接着從本地獲取,獲取失敗後再從網絡獲取
,這也和處理圖片的三級緩存策略一樣。
我們發現TasksRepository類都實現了 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 completeTask(@NonNull Task task);
void completeTask(@NonNull String taskId);
void activateTask(@NonNull Task task);
void activateTask(@NonNull String taskId);
void clearCompletedTasks();
void refreshTasks();
void deleteAllTasks();
void deleteTask(@NonNull String taskId);
}
可以發現我們正是在這裏TasksDataSource
中定義了內部接口GetTaskCallback
。從而實現TasksRepository
中數據獲取的時候的回調。
爲了簡化整個模板代碼的操作,這裏只在
getTasks()
和getTask()
方法中添加了回調並不是說其他地方不需要數據的回調。
還記得之前的Google官方MVP+Dagger2架構詳解文章講解的整個實例app的TasksRepositoryComponent
在整個應用的Application中初始化。
@Singleton
@Component(modules = {TasksRepositoryModule.class, ApplicationModule.class})
public interface TasksRepositoryComponent {
TasksRepository getTasksRepository();
}
我們可以看到我們將 TasksRepositoryModule
放在mock下,做mock測試,看下圖:
我們也在TasksRepositoryModule
中提供了數據的實例,也正是在mock中提供了假數據FakeTasksRemoteDataSource
替換了TasksRemoteDataSource
.
@Module
public class TasksRepositoryModule {
@Singleton
@Provides
@Local
TasksDataSource provideTasksLocalDataSource(Context context) {
return new TasksLocalDataSource(context);
}
@Singleton
@Provides
@Remote
TasksDataSource provideTasksRemoteDataSource() {
return new FakeTasksRemoteDataSource();
}
}
到這裏也就完成了整個數據model層的提供,我們也可以看到Dagger2再次顯示了它的威力。
總結:
最後,我們再來看這張圖。Fragment作爲View,View和Presenter通過Activity來進行關聯,Presenter對數據的調用是通過TasksRepository來完成的,而TasksRepository維護着它自己的數據源和實現。
到這裏你肯定明白了爲什麼我們需要Repository層了---->屏蔽底層細節。
上層(activity/fragment/presenter)不需要知道數據的細節(或者說 - 數據源),來自於網絡、數據庫,亦或是內存等等。如此,一來上層可以不用關心細節,二來底層可以根據需求修改,不會影響上層,兩者的分離用可以幫助協同開發。
3 android-boilerplate 中model層的設計
如果你關注安卓架構,之前肯定關注過Android Application Architecture(翻譯文章Android應用架構-
小鄧子的簡書)這篇文章。其中的示例項目android-boilerplate也是一個基於MVP架構的框架,加入EventBus(Otto),RxJava,Retrofit,SqlBrite等。其中的model層有很多值得借鑑的地方。這裏再從這整個架構圖來學習一下相應的思路。
View(視圖)層:Activities,Fragment以及ViewGroup等在這一層。處理用戶的交互和輸入事件,並且觸發Presenter中的相應操作。
Presenter層 :Presenters 訂閱(subscibe) RxJava的
Observables
,負責處理訂閱週期,處理由DataManager
提供的數據,並調用View層中的相應方法展示數據。Model (數據)層: 負責獲取,保存,緩存以及修改數據。負責與本地數據庫,其他數據存儲,
restful APIs
,以及第三方SDKs 交互。在此架構中,Model層被劃分爲兩個部分:許多helpers類和一個DataManager
.helpers類的數量在不同的工程中不盡相同,但是每個都有自己的功能。比如:通過SharedPreferences
與數據進行交互的PreferHelper
,通過SqlBrite提供與數據庫交互的DatabaseHelper
,DataManager
結合並且轉化不同的Helpers類爲Rx操作符,向Presenter層提供Observables
類型的數據(provide meaningful data to the Presenter),並且同時處理數據的併發操作(group actions that will always happen together.)。這一層也包含實際的model類,用於定義當前數據架構。
因此,我們可以看到的優點是:
1 Activity和Fragment變得非常輕量。他們唯一的職責就是建立/更新UI和處理用戶事件。因此,他們變得更容易維護。
2 RxJava的Observable和操作符避免了嵌套回調的出現,同時如果數據model層出現數據錯誤,我們也會在presenter層得到處理,在presenter層調用相應的view層的方法顯示錯誤信息。
3 現在我們通過模擬View Layer可以很容易的編寫出單元測試。之前這些代碼是View Layer的一部分,所以我們很難對它進行單元測試。整個架構變得測試友好。
4 如果DataManager變得臃腫,我們可以通過轉移一些代碼到Presenter來緩解這個問題。
5 通過引入 Event Bus(事件總線,這個項目使用的是otto)。它允許我們在Data Layer中發送事件,以便View Layer中的多個組件都能夠訂閱到這些事件。比如DataManager
中的退出登錄方法可以發送一個事件,訂閱這個事件的多個Activity在接收到該事件後就能夠更改它們的UI視圖,從而顯示一個登出狀態。
6 這裏的DataManager
也扮演了官方示例項目中Respository
的作用,屏蔽底層細節。
7 引入Dagger2 ,添加依賴注入,實現組件的重用,也就使得測試變得更加容易.
未完待續......
參考鏈接:
1 從零開始的Android新項目5 - Repository層(上) Retrofit、Repository組裝
2 從零開始的Android新項目6 - Repository層(下) Realm、緩存、異常處理
3 android architecture architecture guidelines
4 Android應用架構- 小鄧子的簡書
5 Android官方MVP架構項目解析
6 從零開始搭建android框架系列
7 google官方架構MVP解析與實戰
8 Google官方MVP+Dagger2架構詳解
9 Android Application Architecture
10 完美的安卓 model 層架構(上)
11 完美的安卓 model 層架構(下)