前言:
前幾天去面試凰棉,有一家公司問到會不會mvp供屉,說說對他的理解讳侨,你的項目中是是如何實現(xiàn)的呵萨,然而我只是看了很多講mvp的文章,并沒有真正的在項目中用過跨跨,畢竟也都是小項目......然后的然后就讓我現(xiàn)場寫mvp.....
推薦一篇通俗易懂的文章
http://www.reibang.com/p/389c9ae1a82c
之前也看了很多的文章潮峦,但是看得越多就越亂囱皿,看到最后也沒有真正弄懂mvp到底是怎么個回事,結(jié)合google的todomvp和這篇文章忱嘹,大致理清了mvp是咋回事了嘱腥。
什么是mvp?
- mvp就是model/view/presenter三者
- 關(guān)系:presenter相當(dāng)于是model和view之前的橋梁拘悦,他倆是不能直接交互的齿兔,得通過presenter來連接。
- 那這樣的話础米,view只能和presenter交互分苇,那怎么才能在view中操作presenter呢?只需要把presenter作為參數(shù)傳給view就行了屁桑;那要在presenter中操作view怎么辦呢医寿,也是同樣的方法,只需要把view傳給view就行了蘑斧。因為要在presenter中獲取數(shù)據(jù)靖秩,所以還需要在Presenter中傳入model。
既然說presenter是中間層竖瘾,那在哪實現(xiàn)的presenter呢沟突?
這個是當(dāng)時問到我的一個題目。準(zhǔn)確的說應(yīng)該是在Activity中創(chuàng)建的presenter實例捕传,將view和model作為參數(shù)傳過去了惠拭。
什么是view?
根據(jù)google官方的例子乐横,view其實是Fragment求橄,Activity只是一個全局的控制者,負(fù)責(zé)創(chuàng)建view以及presenter實例葡公。
view和presenter是怎么關(guān)聯(lián)的罐农?
在presenter的構(gòu)造函數(shù)中調(diào)用view的setPresenter(this)就可以將二者綁定了。
前面做了這么多鋪墊催什,接下來分析一下google官方demo(參考上面的鏈接分析的):
-
功能頁面:
主頁.jpg詳情頁.jpg
-
項目結(jié)構(gòu)
項目結(jié)構(gòu).png -
代碼分析
- 在最底下有兩個基類:
public interface BasePresenter {
//presenter開始獲取數(shù)據(jù)并調(diào)用view中方法改變界面顯示涵亏,其調(diào)用時機(jī)是在Fragment類的onResume方法中。
void start();
}
public interface BaseView<T> {
//view要持有presenter的引用蒲凶,在將presenter實例傳入view中气筋,其調(diào)用時機(jī)是presenter實現(xiàn)類的構(gòu)造函數(shù)中。
void setPresenter(T presenter);
}
- 定義了契約類(接口)
/**
* 契約類來統(tǒng)一管理view與presenter的所有的接口
*/
public interface TaskDetailContract {
interface View extends BaseView<Presenter> {
//定義了該界面(功能)中所有的UI狀態(tài)情況
// 設(shè)置數(shù)據(jù)加載狀態(tài)
void setLoadingIndicator(boolean active);
// 處理task加載失敗的情況
void showMissingTask();
// 隱藏待辦事項title
void hideTitle();
// 顯示待辦事項title
void showTitle(String title);
// 隱藏待辦事項的描述
void hideDescription();
// 顯示待辦事項的描述
void showDescription(String description);
//顯示待辦事項的狀態(tài)旋圆,是否已完成
void showCompletionStatus(boolean complete);
//顯示要編輯的待辦事項
void showEditTask(String taskId);
//顯示已刪除的待辦事項
void showTaskDeleted();
//顯示已完成的待辦事項
void showTaskMarkedComplete();
//顯示未完成的點半事項
void showTaskMarkedActive();
//是否未完成
boolean isActive();
}
interface Presenter extends BasePresenter {
//定義了該界面(功能)中所有的用戶操作事件
// 修改待辦事項
void editTask();
// 刪除待辦事項
void deleteTask();
// 標(biāo)記完成
void completeTask();
// 標(biāo)記未完成
void activateTask();
}
}
這樣寫了之后宠默,這個功能有哪些功能操作,以及ui上會有哪些變化都很清楚
- View
public class TaskDetailFragment extends Fragment implements TaskDetailContract.View {
private TaskDetailContract.Presenter mPresenter;
.....
@Override
public void setPresenter(@NonNull TaskDetailContract.Presenter presenter) {
//判空灵巧,在Presenter中會將view和presenter進(jìn)行綁定搀矫,這里是檢查傳入的presenter是否為空
mPresenter = checkNotNull(presenter);
}
......
@Override
public void onResume() {
super.onResume();
//開始獲取model中的數(shù)據(jù)
mPresenter.start();
}
......
@Override
public void hideDescription() {
//隱藏待辦事件的描述
mDetailDescription.setVisibility(View.GONE);
}
......
}
TaskDetailFragment 作為view層抹沪,主要負(fù)責(zé)ui相關(guān)的狀態(tài)更新,實現(xiàn)了契約類中的View接口后,在相關(guān)的代碼中實現(xiàn)具體的內(nèi)容瓤球。
- Presenter
Activity在mvp中的作用:
/**
* Activity在項目中是一個全局的控制者融欧,負(fù)責(zé)創(chuàng)建view以及presenter實例,并將二者聯(lián)系起來卦羡。
*/
public class TaskDetailActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState){
//創(chuàng)建view
TaskDetailFragment taskDetailFragment = (TaskDetailFragment) getSupportFragmentManager()
.findFragmentById(R.id.contentFrame);
//創(chuàng)建presenter實例
new TaskDetailPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
taskDetailFragment);
}
}
創(chuàng)建Presenter
/**
* 作為Presenter層噪馏,實現(xiàn)了該接口,如此 TaskDetailPresenter 則只關(guān)注業(yè)務(wù)層的邏輯相關(guān)绿饵,UI的更新只需調(diào)用View的狀態(tài)方法欠肾。
*/
public class TaskDetailPresenter implements TaskDetailContract.Presenter {
/**
* 構(gòu)造函數(shù),presenter作為中間層鏈接model和view蝴罪,為了相互交互董济,則要持有他們的引用
* @param taskId : 待辦事項id
* @param tasksRepository : model
* @param taskDetailView : view
*/
public TaskDetailPresenter(@Nullable String taskId,
@NonNull TasksRepository tasksRepository,
@NonNull TaskDetailContract.View taskDetailView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null!");
// 保持對View(TaskDetailFragment)的引用
mTaskDetailView = checkNotNull(taskDetailView, "taskDetailView cannot be null!");
// 使View(TaskDetailFragment)也保持對自身(TaskDetailPresenter)的引用
mTaskDetailView.setPresenter(this);
}
@Override
public void start() {
//開始獲取數(shù)據(jù)
openTask();
}
}
- Model層:
model層中主要是數(shù)據(jù)的一些操作步清,數(shù)據(jù)的獲取要门,數(shù)據(jù)的添加,數(shù)據(jù)的刪除等
在Presenter的start()中獲取數(shù)據(jù):
/**
* 獲取數(shù)據(jù)
*/
private void openTask() {
// 判空處理
if (Strings.isNullOrEmpty(mTaskId)) {
//加載數(shù)據(jù)的時候廓啊,如果taskId為空欢搜,則調(diào)用view的加載失敗的情況
mTaskDetailView.showMissingTask();
return;
}
// 更新狀態(tài),加載成功的時候,設(shè)置加載的狀態(tài)谴轮,
mTaskDetailView.setLoadingIndicator(true);
// modele,獲取該條Task數(shù)據(jù)
mTasksRepository.getTask(mTaskId, new TasksDataSource.GetTaskCallback() {
@Override
public void onTaskLoaded(Task task) {
//數(shù)據(jù)加載成功
// View已經(jīng)被用戶回退(已銷毀)
if (!mTaskDetailView.isActive()) {
return;
}
// 獲取到task數(shù)據(jù)炒瘟,并更新UI
mTaskDetailView.setLoadingIndicator(false);
if (null == task) {
//如果沒有加載到task,則顯示加載失敗的ui
mTaskDetailView.showMissingTask();
} else {
//否則第步,顯示這個待辦事項
showTask(task);
}
}
@Override
public void onDataNotAvailable() {
// 顯示數(shù)據(jù)獲取失敗時的狀態(tài)
if (!mTaskDetailView.isActive()) {
return;
}
mTaskDetailView.showMissingTask();
}
});
}
再看一下TasksRepository的getTask():
@Override
public void getTask(@NonNull final String taskId, @NonNull final GetTaskCallback callback) {
// 判空處理
checkNotNull(taskId);
checkNotNull(callback);
// 獲取緩存數(shù)據(jù)
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.
// 從本地數(shù)據(jù)源(SQLite數(shù)據(jù)庫)中獲取,沒有獲取到疮装,再從網(wǎng)絡(luò)數(shù)據(jù)源獲取
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);
// 成功,則回調(diào)
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
// 失敗粘都,則從遠(yuǎn)程數(shù)據(jù)源(網(wǎng)絡(luò))中獲取
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);
// 回調(diào)成功時的方法
callback.onTaskLoaded(task);
}
@Override
public void onDataNotAvailable() {
// 回調(diào)失敗時的方法
callback.onDataNotAvailable();
}
});
}
});
}
- 分析Model(TasksRepositor)代碼實現(xiàn):
- TasksDataSource 接口分析:
里面包含了所有對數(shù)據(jù)的操作
- TasksDataSource 接口分析:
public interface TasksDataSource {
//加載數(shù)據(jù)的回調(diào)
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
//獲取數(shù)據(jù)的回調(diào)
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
/**
* 獲取所有task
* @param callback
*/
void getTasks(@NonNull LoadTasksCallback callback);
/**
* 根據(jù)id獲取單個task
* @param taskId
* @param callback
*/
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
/**
* 保存task
* @param task
*/
void saveTask(@NonNull Task task);
/**
* 標(biāo)記為完成
* @param task
*/
void completeTask(@NonNull Task task);
/**
* 標(biāo)記為完成
* @param taskId
*/
void completeTask(@NonNull String taskId);
/**
* 標(biāo)記為未完成
* @param task
*/
void activateTask(@NonNull Task task);
/**
* 標(biāo)記為未完成
* @param taskId
*/
void activateTask(@NonNull String taskId);
/**
* 刪除所有已完成task
*/
void clearCompletedTasks();
/**
* 刷新task
*/
void refreshTasks();
/**
* 刪除所有task
*/
void deleteAllTasks();
/**
* 刪除單個task
* @param taskId
*/
void deleteTask(@NonNull String taskId);
}
- TasksRepository實現(xiàn)了TasksDataSource接口廓推,維護(hù)了兩個數(shù)據(jù)源。一個是本地(SQLite數(shù)據(jù)庫)翩隧,一個是遠(yuǎn)程(網(wǎng)絡(luò)服務(wù)器)樊展,他們都是TasksDataSource類型。
可以把TasksRepository理解為一個大糧倉堆生,里面又有2個小糧倉专缠,有我的糧食庫,也有你的糧食庫淑仆,我們需要糧食的時候都要到對應(yīng)的糧食庫中取涝婉。所有的糧食庫都實現(xiàn)TasksDataSource接口
//遠(yuǎn)程數(shù)據(jù)源
private final TasksDataSource mTasksRemoteDataSource;
//本地數(shù)據(jù)源
private final TasksDataSource mTasksLocalDataSource;
- TasksRepository是一個單例模式,在構(gòu)造函數(shù)中要有兩個數(shù)據(jù)源:
private static TasksRepository INSTANCE = null;
// 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;
}
- 實現(xiàn)接口中的方法:
......
@Override
public void deleteTask(@NonNull String taskId) {
//根據(jù)id刪除Task
① mTasksRemoteDataSource.deleteTask(checkNotNull(taskId));
② mTasksLocalDataSource.deleteTask(checkNotNull(taskId));
mCachedTasks.remove(taskId);
}
......
- ①和②中又執(zhí)行了各自的操作
- 這時候會想到一個問題蔗怠,這些數(shù)據(jù)到底是在哪里進(jìn)行操作的呢?
繼續(xù)看TasksLocalDataSource這個類墩弯,這是本地數(shù)據(jù)源
public class TasksLocalDataSource implements TasksDataSource {
private static volatile TasksLocalDataSource INSTANCE;
// 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;
}
}
這個類是一個單例省骂,實現(xiàn)了TasksDataSource ,剛剛在上面說過"本地數(shù)據(jù)源"就相當(dāng)于一個小糧倉最住,要從里面取糧食就要實現(xiàn)TasksDataSource 這個接口才行钞澳。
然后實現(xiàn)接口后做具體的實現(xiàn):
......
@Override
public void saveTask(@NonNull final Task task) {
checkNotNull(task);
Runnable saveRunnable = new Runnable() {
@Override
public void run() {
//在數(shù)據(jù)庫中插入數(shù)據(jù)
mTasksDao.insertTask(task);
}
};
mAppExecutors.diskIO().execute(saveRunnable);
}
......
@Override
public void completeTask(@NonNull final Task task) {
Runnable completeRunnable = new Runnable() {
@Override
public void run() {
//在數(shù)據(jù)庫中更新數(shù)據(jù)
mTasksDao.updateCompleted(task.getId(), true);
}
};
mAppExecutors.diskIO().execute(completeRunnable);
}
......
- 知道了這個是對本地數(shù)據(jù)庫的具體實現(xiàn)后,那在哪里調(diào)用的它呢涨缚?
按住TasksLocalDataSource 轧粟,找到它在Injection這個類中用到了,接下來看一下Injection:
public class Injection {
/**
* 返回的是model
* @param context
* @return
*/
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
//創(chuàng)建數(shù)據(jù)庫
ToDoDatabase database = ToDoDatabase.getInstance(context);
//創(chuàng)建TasksRepository :遠(yuǎn)程數(shù)據(jù)源脓魏、本地數(shù)據(jù)源
return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(new AppExecutors(), database.taskDao()));
}
}
它的代碼不多兰吟,提供了一個靜態(tài)方法供外部調(diào)用,而這個方法居然返回的是 TasksRepository ,根據(jù)上面的分析茂翔,知道他相當(dāng)于是model混蔼,為什么要這么寫呢?
- 接下來看 Injection 的這個方法在哪里使用了珊燎?
//創(chuàng)建presenter實例
new TaskDetailPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
taskDetailFragment);
原來在一開始創(chuàng)建Presenter的時候就使用了惭嚣,Presenter要連接view和model,所有就要傳入他們的引用悔政。
現(xiàn)在整個邏輯就很清楚了晚吞。
- 本地數(shù)據(jù)源分析完了,還有一個遠(yuǎn)程數(shù)據(jù)源谋国,接下來看一下 TasksRemoteDataSource這個類槽地。和上面一樣,他也要實現(xiàn) TasksDataSource接口:
public class TasksRemoteDataSource implements TasksDataSource {
private static TasksRemoteDataSource INSTANCE;
// Prevent direct instantiation.
private TasksRemoteDataSource() {}
public static TasksRemoteDataSource getInstance() {
if (INSTANCE == null) {
INSTANCE = new TasksRemoteDataSource();
}
return INSTANCE;
}
}
其余的和本地數(shù)據(jù)源一樣芦瘾,只是它是從服務(wù)端讀取數(shù)據(jù)
- 總結(jié):
1.寫一個契約類管理所有的view和presenter接口捌蚊,這樣所有的方法和ui狀態(tài)都會很清楚了
2.Acitivity或Fragment相當(dāng)于是View層,要實現(xiàn)契約類中的view接口近弟,并在對應(yīng)方法中寫具體的實現(xiàn)缅糟,因為view層可以和Presenter層交互,所以得在View層拿到Presenter藐吮,在setPresenter()方法中可以將綁定的Presenter傳進(jìn)來溺拱,比如點擊了刪除按鈕,就要調(diào)用Presnter中的刪除事件谣辞。
3.Model層主要是對數(shù)據(jù)的一些操作迫摔,可以寫一個接口,里面寫上對數(shù)據(jù)的一些操作泥从,model實現(xiàn)這個接口后做具體的實現(xiàn)(進(jìn)行本地數(shù)據(jù)源和遠(yuǎn)程數(shù)據(jù)源的增刪改查)
4.Presenter是中間層句占,在創(chuàng)建的時候要傳入View和model的引用,在Presenter的構(gòu)造方法中通過 view.setPresenter(this)傳入當(dāng)前Presenter進(jìn)行綁定
over.....