人笨學(xué)的慢啊。。抓緊學(xué)習(xí)MVP
網(wǎng)上MVP的學(xué)習(xí)資料也是多如牛毛,來看看谷歌爸爸是怎么教我們MVP的吧
項(xiàng)目介紹
Google把這個項(xiàng)目命名為:Android架構(gòu)藍(lán)圖。
這個項(xiàng)目也是金誠先生推薦的2017年Android百大框架排行榜中的一個
android-architecture
一句話介紹:google提供的Android當(dāng)下各種基本框架
上榜理由:看完它彤悔,mvp,mvvm都將入切瓜砍菜索守,秋風(fēng)掃落葉一般...
github https://github.com/googlesamples/android-architecture
作者:google
項(xiàng)目的目的是通過展示各種架構(gòu)app的不同方式來幫助開發(fā)者解決架構(gòu)問題晕窑。項(xiàng)目中通過不同的架構(gòu)概念及方式實(shí)現(xiàn)了功能相同的app。你可以用示例來當(dāng)做參考卵佛,或是干脆拿來當(dāng)做創(chuàng)建app項(xiàng)目的基礎(chǔ)杨赤。項(xiàng)目中,希望大家能把關(guān)注點(diǎn)集中到代碼結(jié)構(gòu)截汪、整體架構(gòu)疾牲、可測試性、可維護(hù)性這四個方面衙解。當(dāng)然實(shí)現(xiàn)app有很多種方式阳柔,千萬不要把它當(dāng)做定式。
項(xiàng)目中有哪些示例
目前已經(jīng)穩(wěn)定的示例有
- todo-mvp(mvp基礎(chǔ)架構(gòu)示例)
- todo?mvp?clean (基于mvp基礎(chǔ)架構(gòu)項(xiàng)目蚓峦,使用了clean架構(gòu)的概念)
- todo?mvp?dagger (基于mvp基礎(chǔ)架構(gòu)項(xiàng)目舌剂,使用了dagger2進(jìn)行依賴注入)
- todo?mvp?rxjava (基于mvp基礎(chǔ)架構(gòu)項(xiàng)目济锄,使用了rxjava2進(jìn)行依賴注入)
- todo-mvp-databinding(基于mvp基礎(chǔ)架構(gòu)項(xiàng)目,使用了數(shù)據(jù)綁定組件)
仍在進(jìn)展中的示例有
- dev?todo?mvp?tablet (Adds a master and detail view for tablets.)
- dev?todo?mvvm?rxjava (Based on the todo-rxjava sample, this version incorporates the Model?View?ViewModel pattern.)
- dev?todo?mvvm?live (Uses lifecycle-aware Architecture Components (except Room), and the Data Binding library with an MVVM architecture.)
- dev?todo?mvp?room (Uses Room, the persistence library from the Architecture Components, as a local data source with an MVP architecture.)
- dev-todo-mvp-kotlin (Conversion of todo-mvp to Kotlin.)
顯而易見的霍转,本渣只能從todo-mvp(mvp基礎(chǔ)架構(gòu)示例)項(xiàng)目中探索谷歌爸爸告訴我們的荐绝,對MVP架構(gòu)的最本源的揭示。
todo-mvp
應(yīng)用程序的名字是todo-mvp(待辦清單-mvp)谴忧,為此項(xiàng)目中的其他示例提供了基礎(chǔ)很泊。該樣本旨在:
- 提供基本的Model-View-Presenter(MVP)架構(gòu)角虫,而不使用任何架構(gòu)框架沾谓。
- 作為比較和對比本項(xiàng)目其他樣本的參考點(diǎn)。
todo-mvp示例使用以下依賴關(guān)系:
- 常見的Android支持庫 - com.android.support戳鹅。*命名空間中的軟件包提供向后兼容性和其他功能均驶。
- Android測試支持庫 - 用于支持UI測試的框架,使用Espresso和AndroidJUnitRunner枫虏。
- Mockito - 用于實(shí)施單元測試的框架妇穴。
- Guava - 谷歌的一組核心庫,通常用于Android應(yīng)用程序隶债。
設(shè)計(jì)app
該應(yīng)用程序由四個UI頁面組成:
- Tasks - 用于管理任務(wù)列表腾它。
- TaskDetail - 用于讀取或刪除任務(wù)。
- AddEditTask - 用于創(chuàng)建或編輯任務(wù)死讹。
- Statistics - 顯示與任務(wù)相關(guān)的統(tǒng)計(jì)信息瞒滴。
在這個應(yīng)用程序以及其他基于它的版本中,每個功能頁面都使用以下類和接口:
- 一個Activity用來管理fragment和presenter的創(chuàng)建;
- 一個定義View和Presenter接口的Contract接口;
- 一個實(shí)現(xiàn)了View接口的Fragment;
- 一個實(shí)現(xiàn)了Presenter接口的presenter.
曾經(jīng)的架構(gòu)
追溯到2012年我們的代碼庫使用的是基本結(jié)構(gòu)赞警,那個時候我們沒有使用任何第三方網(wǎng)絡(luò)類庫妓忍,而且AsyncTask也是我們的好朋友。當(dāng)時的架構(gòu)可以大致表示為下圖愧旦。
代碼被劃分為兩層結(jié)構(gòu):
- Data Layer(數(shù)據(jù)層)負(fù)責(zé)從REST API或者持久數(shù)據(jù)存儲區(qū)檢索和存儲數(shù)據(jù)世剖;
- View Layer(視圖層)的職責(zé)是處理并將數(shù)據(jù)展示在UI上。
APIProvider提供了一些方法笤虫,使Activity和Fragment能夠很容易的實(shí)現(xiàn)與REST API的數(shù)據(jù)交互旁瘫。這些方法使用URLConnection和AsyncTask在一個單獨(dú)的線程內(nèi)執(zhí)行網(wǎng)絡(luò)請求,然后通過回調(diào)將結(jié)果返回給Activity琼蚯。
按照同樣的方式境蜕,CacheProvider 所包含的方法負(fù)責(zé)從SharedPreferences和SQLite數(shù)據(jù)庫檢索和存儲數(shù)據(jù)。同樣使用回調(diào)的方式凌停,將結(jié)果傳回Activity粱年。
存在的問題:
- 使用這種結(jié)構(gòu),最主要的問題在于View Layer持有太多的職責(zé)罚拟。Activitty和Fragment變得非常龐大并且難以維護(hù)台诗。
MVP架構(gòu)
MVP 是如何建立起關(guān)系來的完箩?
首先,M 只在 P 中使用拉队,與 V 無關(guān)弊知,因此 M 只要傳入 P 中即可。
P 與 V 之間的關(guān)系是這樣的:V 和 P 互相保存對方的實(shí)例粱快。V 在需要進(jìn)行數(shù)據(jù)操作邏輯的時候不自己做秩彤,而是交給 P 來做,P 完成之后調(diào)用 V 中的方法實(shí)現(xiàn)界面更新事哭。
所以PRESENTER 的作用是承擔(dān)業(yè)務(wù)邏輯和相應(yīng)的UI邏輯漫雷。 而View層幾乎沒有任何邏輯操作,它只是將presenter 的命令轉(zhuǎn)換為UI操作鳍咱,并且監(jiān)聽用戶的操作降盹,然后傳遞給Presenter 。
可以看到這里的View指的是Fragment谤辜,一是因?yàn)镚oogle推薦使用Fragment而不是Activity來顯示內(nèi)容蓄坏,二是Fragment作為View既能解決layout作為View導(dǎo)致的雞肋,也不會使Activity功能太過膨脹(這里Activity是一個總體的Controller丑念,讓Fragment和Presenter進(jìn)行連接)
左邊那塊就是Model了涡戳,Presenter先到內(nèi)存中的緩存進(jìn)行查詢,如果沒有脯倚,才到本地?cái)?shù)據(jù)源或者遠(yuǎn)程數(shù)據(jù)源請求
項(xiàng)目包結(jié)構(gòu)
除BaseView渔彰、BasePresenter兩個接口,其他package都以業(yè)務(wù)功能來劃分的:
addedittask —— 添加任務(wù)
data —— 數(shù)據(jù)源
statistics —— 任務(wù)統(tǒng)計(jì)
taskdetail —— 任務(wù)詳情
tasks —— 任務(wù)列表
util —— 工具類
項(xiàng)目MVP實(shí)現(xiàn)方式
這節(jié)我們就具體來看官方示例到底是如何實(shí)現(xiàn)mvp的挠将。這里我們先看下總體的輪廓胳岂,關(guān)于項(xiàng)目中業(yè)務(wù)代碼我們僅列出了添加任務(wù)頁(addedittask )的相關(guān)類,其他業(yè)務(wù)代碼類似舔稀。(手繪乳丰,諸兄勿棄)
基類
Presenter基類:
public interface BasePresenter {
void start();
}
// 例子中這個start()方法都在Fragment的onResume()中調(diào)用,作用是presenter開始獲取數(shù)據(jù)并調(diào)用view中方法改變界面顯示内贮。
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
View基類:
public interface BaseView<T> {
void setPresenter(T presenter);
}
BaseView中含方法setPresenter产园,該方法作用是在將presenter實(shí)例傳入view中,其調(diào)用時機(jī)是presenter實(shí)現(xiàn)類的構(gòu)造函數(shù)中夜郁。
Contract 契約類
不同于其他的MVP項(xiàng)目什燕,官方的MVP架構(gòu)中都定義有xxContract契約類,把P層和V層的接口統(tǒng)一寫在契約類中竞端,能夠更清晰的看到在Presenter層和View層中有哪些功能屎即,方便我們以后的維護(hù)。
public interface AddEditTaskContract {
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
void showTasksList();
//設(shè)置標(biāo)題
void setTitle(String title);
void setDescription(String description);
boolean isActive();
}
interface Presenter extends BasePresenter {
void saveTask(String title, String description);
void populateTask();
boolean isDataMissing();
}
}
addedittask功能模塊分析
來具體看一個模塊
AddEditTaskActivity —— Activity
AddEditTaskContract —— 在這里定義了兩個子接口View和Presenter,和他們的方法技俐。實(shí)現(xiàn)了BaseView乘陪、BasePresenter兩個接口
AddEditTaskFragment —— 實(shí)現(xiàn)AddEditTaskContract.View接口
AddEditTaskPresenter —— 實(shí)現(xiàn)AddEditTaskContract.Presenter接口
從Acitivty入手
public class AddEditTaskActivity extends AppCompatActivity {
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
// Set up the toolbar.
...
// Add Fragment
...
// Create the presenter
mAddEditTaskPresenter = new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment,
shouldLoadDataFromRepo);
}
}
我們可以看到一共做了三件事,初始化toolbar雕擂,添加fragment啡邑,創(chuàng)建一個presenter
在創(chuàng)建presenter的時候,把fragment傳了進(jìn)去井赌,實(shí)現(xiàn)了V和P的綁定
因此Activity在項(xiàng)目中是一個全局的控制者谤逼,負(fù)責(zé)創(chuàng)建view以及presenter實(shí)例,并將二者聯(lián)系起來仇穗。
View實(shí)現(xiàn)
AddEditTaskFragment 中通過實(shí)現(xiàn)BaseView中的setPresenter()流部,將Presenter和View關(guān)聯(lián)起來。
@Override
public void onResume() {
super.onResume();
mPresenter.start();
}
@Override
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
//fab屬于view控件仪缸,也歸fragment控制
FloatingActionButton fab =
(FloatingActionButton) getActivity().findViewById(R.id.fab_edit_task_done);
fab.setImageResource(R.drawable.ic_done);
fab.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
//響應(yīng)點(diǎn)擊事件 分發(fā)給P層
mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
}
});
}
- Fragment作為每一個View接口的實(shí)現(xiàn), 主要負(fù)責(zé)數(shù)據(jù)顯示和在用戶交互時調(diào)用Presenter, 但是例子代碼中也是有一些直接操作的部分, 比如點(diǎn)擊開啟另一個Activity, 點(diǎn)擊彈出菜單(菜單項(xiàng)的點(diǎn)擊仍然是調(diào)用presenter的方法)贵涵。
- View接口中定義的方法多為showXXX()方法列肢。
@Override
public boolean isActive() {
return isAdded();
}
- 在Presenter中數(shù)據(jù)回調(diào)的方法中, 先檢查View.isActive()是否為true, 來保證對Fragment的操作安全恰画。
Presenter實(shí)現(xiàn)
AddEditTaskPresenter是AddEditTaskContract.Presenter的實(shí)現(xiàn)
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mIsDataMissing = shouldLoadDataFromRepo;
mAddTaskView.setPresenter(this);
}
- 從Presenter的構(gòu)造函數(shù)可以看出,傳入了TasksRepository 和addEditTaskFragment瓷马,也就是M和V拴还。
- 構(gòu)造中先用guava的checkNotNull() 檢查是否為空, 然后賦值到字段; 之后再調(diào)用View的setPresenter()方法把Presenter傳回View中引用。
- New Presenter的操作是在每一個Activity的onCreate()里做的: 先添加了Fragment(View), 然后把它作為參數(shù)傳給了Presenter. 這里并沒有存Presenter的引用欧聘。
@Override
public void start() {
if (!isNewTask() && mIsDataMissing) {
populateTask();
}
}
@Override
public void populateTask() {
if (isNewTask()) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mTasksRepository.getTask(mTaskId, this);
}
- Presenter的start()方法在Fragment 的onResume() 的時候調(diào)用, 這時候通過M層的mTasksRepository 新建或者修改數(shù)據(jù); 其他方法均對應(yīng)于用戶在UI上的交互操作片林。
Model實(shí)現(xiàn)細(xì)節(jié)
該項(xiàng)目中Model層最大的特點(diǎn)是被賦予了數(shù)據(jù)獲取的職責(zé),與我們平常Model層只定義實(shí)體對象截然不同怀骤。實(shí)例中费封,數(shù)據(jù)的獲取、存儲蒋伦、數(shù)據(jù)狀態(tài)變化都是Model層的任務(wù)弓摘,Presenter會根據(jù)需要調(diào)用該層的數(shù)據(jù)處理邏輯并在需要時將回調(diào)傳入。
- Model是在P構(gòu)造的時候傳入痕届,繼承的接口是TasksDataSource韧献,實(shí)例是TaskRepository
TasksDataSource是一個接口. 接口中定義了Presenter查詢數(shù)據(jù)的回調(diào)接口, 還有一些增刪改查的方法。
public interface TasksDataSource {
// 加載數(shù)據(jù)的回調(diào)
interface LoadTasksCallback {
//成功回調(diào)
void onTasksLoaded(List<Task> tasks);
//失敗回調(diào)
void onDataNotAvailable();
}
// 獲取數(shù)據(jù)的回調(diào)
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
void getTasks(@NonNull LoadTasksCallback callback);
//通過id獲得數(shù)據(jù)
void getTask(@NonNull String taskId, @NonNull GetTaskCallback callback);
//保存task
void saveTask(@NonNull Task task);
...
}
- Model只有一個類研叫, 即TasksRepository锤窑, 它還是一個單例。
它由手動實(shí)現(xiàn)的注入類Injection類提供:
public class Injection {
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(context));
}
}
TasksRepository的構(gòu)造如下:
private TasksRepository(@NonNull TasksDataSource tasksRemoteDataSource,
@NonNull TasksDataSource tasksLocalDataSource) {
mTasksRemoteDataSource = checkNotNull(tasksRemoteDataSource);
mTasksLocalDataSource = checkNotNull(tasksLocalDataSource);
}
TaskRepository中首先定義了兩個數(shù)據(jù)源嚷炉,一個負(fù)責(zé)數(shù)據(jù)庫操作mTasksLocalDataSource渊啰,另一個負(fù)責(zé)網(wǎng)絡(luò)數(shù)據(jù)mTasksRemoteDataSoure。TaskRepository類中還有一個內(nèi)存緩存的實(shí)現(xiàn)申屹。
TasksLocalDataSource是TasksDataSource接口的實(shí)現(xiàn)绘证,里面是一些對數(shù)據(jù)庫的增刪改查的操作走搁。
而在TasksDataSource的兩個內(nèi)部接口LoadTasksCallback和GetTaskCallback是Model層的回調(diào)接口。真正實(shí)現(xiàn)是在Presenter層迈窟∷街玻可以把成功獲取到的數(shù)據(jù)傳遞給Presenter層,比如AddEditTaskPresenter就實(shí)現(xiàn)了TasksDataSource.GetTaskCallback接口车酣。
AddEditTaskPresenter的start() 方法-->執(zhí)行了Model的getTask() 方法曲稼。
public void getTasks(@NonNull final LoadTasksCallback callback) {
// 判空處理
checkNotNull(callback);
// 從內(nèi)存緩存中讀取數(shù)據(jù)
if (mCachedTasks != null && !mCacheIsDirty) {
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
return;
}
if (mCacheIsDirty) {
// 如果緩存是臟數(shù)據(jù),那么從網(wǎng)絡(luò)獲取數(shù)據(jù)
getTasksFromRemoteDataSource(callback);
} else {
// 查詢本地存儲(如果有)湖员。 如果沒有贫悄,查詢網(wǎng)絡(luò)。
mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
@Override
public void onTasksLoaded(List<Task> tasks) {
refreshCache(tasks);
//在P中通過回調(diào)得到M層查詢到的數(shù)據(jù)
callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
}
@Override
public void onDataNotAvailable() {
getTasksFromRemoteDataSource(callback);
}
});
}
}
總結(jié)
回顧整理娘摔,總結(jié)一下:
- Activity作為管理者窄坦,負(fù)責(zé)創(chuàng)建Fragment(V)和Presenter(P),而Presenter(P)的創(chuàng)建需要用到Fragment(V)和Repository(M)凳寺,F(xiàn)ragment(V)已經(jīng)被Activity創(chuàng)建了鸭津。
- Contract 作為契約類,能夠更清晰的看到在Presenter層和View層中有哪些功能肠缨,方便我們以后的維護(hù)逆趋。
- Fragment 作為V,負(fù)責(zé)的就是接收數(shù)據(jù)晒奕,更新界面闻书。
- Repository 作為M,負(fù)責(zé)的是對數(shù)據(jù)的處理和回調(diào)脑慧,通過依賴注入的形式創(chuàng)建魄眉,并且Repository(M)可以同時操作遠(yuǎn)程數(shù)據(jù)和本地?cái)?shù)據(jù),而且M中沒有V的引用闷袒,而和P的聯(lián)系則是通過callback坑律,可以再看下官方給的圖。
- Presenter 作為P霜运,V向它請求數(shù)據(jù)脾歇,然后P再向M請求數(shù)據(jù),通過回調(diào)得到數(shù)據(jù)之后在調(diào)用V進(jìn)行界面的更新淘捡。
看完發(fā)現(xiàn)藕各,好像還真的是一大堆的接口啊=钩<た觥!
參考文章
http://www.reibang.com/p/d54f82848bea
http://blog.csdn.net/jjwwmlp456/article/details/54869179
http://blog.csdn.net/u013400743/article/details/52205445
http://www.reibang.com/p/8ca27934c6e6
http://blog.csdn.net/card361401376/article/details/51518605
https://mp.weixin.qq.com/s?__biz=MzA4MjA0MTc4NQ==&mid=404088059&idx=3&sn=78dafacbca09b0d7345344c3eef24aff#rd
http://www.reibang.com/p/0bf5289ee8ce
http://www.reibang.com/p/389c9ae1a82c
http://blog.csdn.net/ljd2038/article/details/51477475