從Google的todo-mvp源碼中學習MVP模式
MVP : Model-View-Presenter
MVP模式可以說是現在開發模式中的mvp了。MVP模式也是mvvm模式的一個基礎。
Google在Github上面公佈了一組官方的mvp samples:Google todo-mvp
通過對以下幾個例子的源碼分析,來深入學習MVP模式。
Sample | Description |
---|---|
todo‑mvp | 最基礎的MVP模式用例 |
todo‑mvp‑clean | 使用了 Clean Architecture 的概念 |
todo‑mvp‑dagger | 使用Dagger 2進行依賴注入 |
todo‑mvp‑rxjava | 使用Rxjava 2進行併發操作 |
todo‑mvvm‑databinding | 通過databinding來轉變成mvvm框架 |
上述的幾個例子都是基於第一個todo-mvp這個例子的基礎改變而成的。
首先來對比一下MVP模式和MVC模式。
MVC模式:在安卓中,Activity即作爲Controller又作爲View的一部分,這就導致了代碼耦合。
MVP模式:就是解決上述的問題,把Activity和XML頁面都僅作爲View,Controller的功能以及操作Model都交給了Presenter。通過Presenter把View和Model徹底分開。
todo‑mvp
先看看官方項目介紹中的結構圖:
其中Fragment作爲View的部分,Presenter是一個自定義的類
而左邊的一塊,Repository就是Model部分了。
我們的分析先從整體結構入手,然後局部源碼分析:
首先看一下項目結構:
這裏主要看main目錄下的文件結構:
其中tasks,addedittask,statistics,taskdetail這4個目錄都是獨立且相似的。
因此我們就分析tasks目錄下的4個主要文件(這4個文件組成了MVP模式中一個頁面實現的標準結構):
TasksActivity: 初始化View(TasksFragment)和Presenter(TasksPresenter)
TasksContract: 接口,裏面有兩個內部接口View接口和Presenter接口。
TaskFragment: MVP模式,View的部分(implements TasksContract.View)
TaskPresenter: MVP模式,Presenter的部分(implements TasksContract.Presenter)
我們在看看data目錄下的文件:
TasksDataSource:Model操作的接口,定義了Model的相關操作
TasksRepository:操作Model的一個實例,實現了TasksDataSource接口,裏面還包含了兩個成員:mTasksRemoteDataSource,mTasksLocalDataSource,分別代表本地Model操作和遠程Model操作(從網絡獲取數據)
Task:一個具體的Model
我們再從一個簡單的UML圖中看看結構:
我們可以清晰的看到View和Presenter分別持有對方的接口對象,這樣子就能互相調用。但是View對於Model層是完全被隔斷的。
Presenter同時持有Model和View的對象。
而Model對Presenter和View都是沒有感知的。
看完了宏觀的結構,我們從代碼層次微觀的更細緻的分析一下:
1,首先從TasksActivity的onCreate()方法開始:
//TasksActivity.java
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.tasks_act);
// Set up the toolbar.
……………………………………………………
// Set up the navigation drawer.
……………………………………………………
TasksFragment tasksFragment =
(TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
if (tasksFragment == null) {
// Create the fragment
tasksFragment = TasksFragment.newInstance();
ActivityUtils.addFragmentToActivity(
getSupportFragmentManager(), tasksFragment, R.id.contentFrame);
}
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
// Load previously saved state, if available.
if (savedInstanceState != null) {
TasksFilterType currentFiltering =
(TasksFilterType) savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);
mTasksPresenter.setFiltering(currentFiltering);
}
}
在onCreate()方法中,主要創建了TasksFragment和TasksPresenter,並對保存在savedInstanceState的數據進行處理。其中以下這行代碼是最重要的:
mTasksPresenter = new TasksPresenter(
Injection.provideTasksRepository(getApplicationContext()), tasksFragment);
通過TasksPresenter的構造方法,把Model View Presenter聯繫在了一起。
其中Injection是通過依賴注入的方式構造了一個TasksRepository對象,具體如何構造先不管他。只要知道TasksPresenter通過這個TasksRepository對象來操作Model的。
//TasksPresenter.java
public class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
…………………………
public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) {
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
mTasksView = checkNotNull(tasksView, "tasksView cannot be null!");
mTasksView.setPresenter(this);
}
…………………………
}
在TasksPresenter的構造函數中,把TasksRepository和TasksFragment賦值給自己的成員變量。並調用TasksFragment的setPresenter方法,把自己的引用交給他。這樣子,MVP三者就互相關聯了。
然後就開始正式的運作了:
2,在剛纔的onCreate方法中提交了Fragment自然會運行到TasksFragment的生命週期onCreateView方法:
//TasksFragment.java
public class TasksFragment extends Fragment implements TasksContract.View {
private TasksContract.Presenter mPresenter;
private TasksAdapter mListAdapter;
private View mNoTasksView;
private ImageView mNoTaskIcon;
……………………
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
…………………………
}
這裏就撿去了一個關鍵代碼onResume()方法,在該生命週期中,調用了mPresenter的start()方法,該方法時實現的BasePresenter接口的一個方法。
在TasksFragment.java的代碼中,我們可以發現所有和數據相關的操作都是交給了mPresenter成員變量去實現。在TasksFragment.java中只剩下了獲取view對象,view對象的一些基本設置。
3,我們知道這個mPresenter對象的實例是TasksPresenter對象,因此,我們查看TasksPresenter中的start()方法:
最終調用了loadTasks方法,
在loadTasks方法中,TasksPresenter 通過mTasksRepository成員變量,調用mTasksRepository.getTasks()方法去獲取Model中的數據。其中的參數是一個callback,就是告訴Model倉庫,獲取到了Model以後,需要使用TasksPresenter的processTasks方法去處理獲取到的數據。
ublic class TasksPresenter implements TasksContract.Presenter {
private final TasksRepository mTasksRepository;
private final TasksContract.View mTasksView;
……………………
@Override
public void start() {
loadTasks(false);
}
@Override
public void loadTasks(boolean forceUpdate) {
// Simplification for sample: a network reload will be forced on first load.
loadTasks(forceUpdate || mFirstLoad, true);
mFirstLoad = false;
}
/**
* @param forceUpdate Pass in true to refresh the data in the {@link TasksDataSource}
* @param showLoadingUI Pass in true to display a loading icon in the UI
*/
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
if (showLoadingUI) {
mTasksView.setLoadingIndicator(true);
}
if (forceUpdate) {
mTasksRepository.refreshTasks();
}
// The network request might be handled in a different thread so make sure Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
List<Task> tasksToShow = new ArrayList<Task>();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
EspressoIdlingResource.decrement(); // Set app as idle.
}
// We filter the tasks based on the requestType
for (Task task : tasks) {
switch (mCurrentFiltering) {
case ALL_TASKS:
tasksToShow.add(task);
break;
case ACTIVE_TASKS:
if (task.isActive()) {
tasksToShow.add(task);
}
break;
case COMPLETED_TASKS:
if (task.isCompleted()) {
tasksToShow.add(task);
}
break;
default:
tasksToShow.add(task);
break;
}
}
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
if (showLoadingUI) {
mTasksView.setLoadingIndicator(false);
}
processTasks(tasksToShow);
}
……………………
}
4,首先判斷緩存中是否有數據且緩存數據不髒(沒被修改過),如果有數據則直接通過callback提交回Presenter中,
如果數據已經不是最新數據了(被修改過了),通過從遠程獲取最新的數據,如果緩存中沒數據,但數據是最新的,那麼直接從本地讀取數據
//TasksRepository.java
public class TasksRepository implements TasksDataSource {
private static TasksRepository INSTANCE = null;
private final TasksDataSource mTasksRemoteDataSource;
private final TasksDataSource mTasksLocalDataSource;
…………………………………………………………
@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);
}
});
}
}
………………………………………………………………
}
總結:
1,創建presenter和view的接口
2,在activity中創建fragment作爲view
3,在activity中創建presenter,並與之前的view互相綁定
4,在fragment的onresume生命週期中通過presenter初始化數據。即從model中獲取數據。
todo‑mvp-clean
同樣先看看官方的配圖:
我們發現就在Presenter和Model的中間,多了一塊Domain。
該項目是根據Clean Architecture的原則來實現的一個MVP模式的項目。
Clean Architecture簡單的說就是一個分層模型,外層擁有內層對象的引用,因此外層可以通過內層暴露的方法進行需求的操作。而內層對外層是一無所知的。
這篇文章http://www.jianshu.com/p/cba6663435c7有個不錯的解說。
我們主要來看看 這個多出來的Domain到底是什麼。
Domian其實就是所有數據操作的集合。簡單的說,就是增刪改查這些操作。
每一個Use case就是一種操作。
來看一下項目結構目錄:
對比例一的項目結構,我們可以發現 多出了一個domain文件目錄,多出了UseCase抽象類,UseCaseHandler類,UseCaseScheduler接口和UseCaseThreadPoolScheduler類。
再來對比一下一個簡單的UML圖:
我們可以發現Domain部分,就是由UseCase(具體任務),UseCaseHandler(任務分發處理器),UseCaseThreadPoolScheduler(任務調度執行器)三個部分構成的。
UseCase抽象類:每一個具體任務(操作)都繼承自這個抽象類,要實現executeUseCase抽象方法,通過泛型約定了這個具體任務的請求參數類型(request)和響應數據類型(response)。
UseCaseHandler類:Usecase任務的處理器,分發和處理每一個不同的UseCase,把UseCase交給調度執行器(UseCaseScheduler)去執行。
UseCaseThreadPoolScheduler類:調度執行器,實現了UseCaseScheduler接口,裏面主要包含一個線程池,通過線程池去執行UseCase任務。
domain文件目錄:裏面主要有一個useCase文件夾。裏面每一個XxxTask都是繼承自UseCase抽象類。
接着看一下源代碼是怎麼執行的:
1,同樣從TasksActivity的onCreate方法開始,其中不同的就是創建TasksPresenter的時候,參數變多了,把所有UseCase和UseCaseHandler都通過參數的方式,傳遞給TaskPresenter,這樣TaskPresenter就可以用UseCaseHandler來處理UseCase任務。
//TaskActivity.java
// Create the presenter
mTasksPresenter = new TasksPresenter(
Injection.provideUseCaseHandler(),
tasksFragment,
Injection.provideGetTasks(getApplicationContext()),
Injection.provideCompleteTasks(getApplicationContext()),
Injection.provideActivateTask(getApplicationContext()),
Injection.provideClearCompleteTasks(getApplicationContext())
);
2,同樣在TasksFragment的onResume生命週期中調用剛纔的TasksPresenter的start()接口方法,最終將調用TasksFragment的loadTasks方法。
//TasksPresenter.java
/**
* @param forceUpdate Pass in true to refresh the data in the {@link TasksDataSource}
* @param showLoadingUI Pass in true to display a loading icon in the UI
*/
private void loadTasks(boolean forceUpdate, final boolean showLoadingUI) {
if (showLoadingUI) {
mTasksView.setLoadingIndicator(true);
}
GetTasks.RequestValues requestValue
= new GetTasks.RequestValues(forceUpdate,
mCurrentFiltering);
mUseCaseHandler.execute(mGetTasks, requestValue,
new UseCase.UseCaseCallback<GetTasks.ResponseValue>() {
@Override
public void onSuccess(GetTasks.ResponseValue response) {
List<Task> tasks = response.getTasks();
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
if (showLoadingUI) {
mTasksView.setLoadingIndicator(false);
}
processTasks(tasks);
}
@Override
public void onError() {
// The view may not be able to handle UI updates anymore
if (!mTasksView.isActive()) {
return;
}
mTasksView.showLoadingTasksError();
}
});
}
該方法中主要是通過UseCaseHandler對象來處理UseCase任務。mUseCaseHandler的execute方法的參數分別是指定的任務GetTasks(UseCase),該任務涉及的請求參數,及其執行完任務以後的回調函數。
3,mUseCaseHandler的execute方法會調用mUseCaseScheduler(UseCaseScheduler)的execute接口方法。execute方法的參數是一個Runable對象,是爲了能在接下來的線程中進行調用。這個Runable裏面主要就是調用了UseCase的run方法。然後,mUseCaseScheduler的具體實例是UseCaseThreadPoolScheduler對象。在UseCaseThreadPoolScheduler中會創建一個ThreadPoolExecutor,最後的執行就是交給這個ThreadPoolExecutor線程池對象來執行的。
//UseCaseHandler.java
public class UseCaseHandler {
private static UseCaseHandler INSTANCE;
private final UseCaseScheduler mUseCaseScheduler;
public UseCaseHandler(UseCaseScheduler useCaseScheduler) {
mUseCaseScheduler = useCaseScheduler;
}
public <T extends UseCase.RequestValues, R extends UseCase.ResponseValue> void execute(
final UseCase<T, R> useCase, T values, UseCase.UseCaseCallback<R> callback) {
useCase.setRequestValues(values);
useCase.setUseCaseCallback(new UiCallbackWrapper(callback, this));
// The network request might be handled in a different thread so make sure
// Espresso knows
// that the app is busy until the response is handled.
EspressoIdlingResource.increment(); // App is busy until further notice
mUseCaseScheduler.execute(new Runnable() {
@Override
public void run() {
useCase.run();
// This callback may be called twice, once for the cache and once for loading
// the data from the server API, so we check before decrementing, otherwise
// it throws "Counter has been corrupted!" exception.
if (!EspressoIdlingResource.getIdlingResource().isIdleNow()) {
EspressoIdlingResource.decrement(); // Set app as idle.
}
}
});
}
………………
}
//UseCaseThreadPoolScheduler.java
public class UseCaseThreadPoolScheduler implements UseCaseScheduler {
ThreadPoolExecutor mThreadPoolExecutor;
@Override
public void execute(Runnable runnable) {
mThreadPoolExecutor.execute(runnable);
}
}
4,因此,最終會在一個新線程中調用UseCase的run方法。run方法中會執行真正實現類的executeUseCase方法,在executeUseCase方法中我們可以看到與例一中相似的代碼,去請求Model中的數據。
//UseCase.java
public abstract class UseCase<………………> {
void run() {
executeUseCase(mRequestValues);
}
protected abstract void executeUseCase(Q requestValues);
}
//GetTasks.java
public class GetTasks extends UseCase<GetTasks.RequestValues, GetTasks.ResponseValue> {
private final TasksRepository mTasksRepository;
……………………
@Override
protected void executeUseCase(final RequestValues values) {
if (values.isForceUpdate()) {
mTasksRepository.refreshTasks();
}
mTasksRepository.getTasks(new TasksDataSource.LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
TasksFilterType currentFiltering = values.getCurrentFiltering();
TaskFilter taskFilter = mFilterFactory.create(currentFiltering);
List<Task> tasksFiltered = taskFilter.filter(tasks);
ResponseValue responseValue = new ResponseValue(tasksFiltered);
getUseCaseCallback().onSuccess(responseValue);
}
@Override
public void onDataNotAvailable() {
getUseCaseCallback().onError();
}
});
}
}
因此,實際上,就多封裝了一層Domain層,Domain層進行多種任務的調度和管理。