簡(jiǎn)單介紹
在日常的開(kāi)發(fā)當(dāng)中贡定,經(jīng)常會(huì)遇到需求的變動(dòng)沐扳。這個(gè)東西是真的難以避免的莉掂,所以對(duì)于產(chǎn)品的基礎(chǔ)框架就比較重要了皇型。Google大大在I/O大會(huì)上提出來(lái)了Android開(kāi)發(fā)方式是屬于MVP的,即Model+View+Presenter型檀。
另外在GitHub上面放出來(lái)了一個(gè)樣例命浴,供開(kāi)發(fā)者學(xué)習(xí),這里可以看到整個(gè)項(xiàng)目的簡(jiǎn)介贱除。
Google通過(guò)一個(gè)TODO類(lèi)的APP講解了如何使用一些第三方庫(kù),已經(jīng)如何基于他們構(gòu)建自己的APP的框架媳溺。
目前的狀態(tài)
Stable samples
Sample | Description |
---|---|
todo?mvp | Demonstrates a basic Model?View?Presenter (MVP) architecture and provides a foundation on which the other samples are built. This sample also acts as a reference point for comparing and contrasting the other samples in this project. |
todo?mvp?loaders | Fetches data using the Loaders API. |
todo?databinding | Uses the Data Binding Library. |
todo?mvp?clean | Uses concepts from Clean Architecture. |
todo?mvp?dagger | Uses Dagger2 to add support for dependency injection. |
todo?mvp?contentproviders | Based on the todo-mvp-loaders sample, this version fetches data using the Loaders API, and also makes use of content providers. |
todo?mvp?rxjava | Uses RxJava to implement concurrency, and abstract the data layer. |
todo?mvvm?databinding | Based on the todo-databinding sample, this version incorporates the Model?View?ViewModel pattern. |
Samples in progress
Sample | Description |
---|---|
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. |
可以看到上面的一些庫(kù)在Android中都比較常用月幌,這一次我們先從最基礎(chǔ)的todo-mvp來(lái)看。
可以在上面的地址中獲取悬蔽,也可以直接在命令行中采用
git clone https://github.com/googlesamples/android-architecture.git
的方法獲取扯躺。
需要切換到todo-mvp分支中
git checkout todo-mvp
在一般閱讀代碼的時(shí)候,我會(huì)采取新建一個(gè)read-code的分支蝎困,便于自己看到自己的改動(dòng)录语。
git checkout -b read-code
準(zhǔn)備工作已經(jīng)OK,我們來(lái)看看該項(xiàng)目的模塊結(jié)構(gòu)禾乘。
模塊結(jié)構(gòu)
整體來(lái)看Google大法是通過(guò)實(shí)際的業(yè)務(wù)模塊來(lái)劃分包的澎埠,當(dāng)然你要是通過(guò)UI、Util始藕、Data來(lái)劃分也一點(diǎn)沒(méi)有問(wèn)題蒲稳,個(gè)人習(xí)慣而已。如下:
- BasePresenter & BaseView是所有的P和V的基礎(chǔ)接口伍派,BasePresenter中有一個(gè)start()方法江耀,V可以通過(guò)改方法調(diào)用P中的業(yè)務(wù)邏輯。BaseView中有一個(gè)setPresenter()方法诉植,通過(guò)該方法祥国,在P的構(gòu)造函數(shù)中將V關(guān)聯(lián)起來(lái)。
- data中的數(shù)據(jù)源分為了遠(yuǎn)程TasksRemoteDataSource和本地TasksLocalDataSource
- addedittask顧名思義晾腔,這個(gè)模塊有兩種狀態(tài)(添加和修改)在這個(gè)包里分了四塊
- AddEditTaskContract即合約類(lèi)舌稀,在其中有兩個(gè)內(nèi)部接口,分別定義了V所要控制的UI邏輯和P所控制的業(yè)務(wù)邏輯灼擂。
- AddEditTaskPresenter則為實(shí)現(xiàn)了Contract內(nèi)部接口的P扩借,在其中有業(yè)務(wù)的邏輯。
- AddEditTaskFragment則為實(shí)現(xiàn)了Contract內(nèi)部接口的V缤至,
在其中有UI的部分操作潮罪。 - 肯定有人就會(huì)有疑問(wèn)了康谆,為何上面MVP都已經(jīng)有了,那AddEditTaskActivity的作用又是什么呢嫉到?我所理解的Activity應(yīng)該屬于一個(gè)容器沃暗,在這個(gè)容器中做了一部分簡(jiǎn)單的初始化UI操作,如控制ToolBar的樣式何恶、加載Fragment等孽锥。
代碼分析
AddEditTaskActivity分析
先來(lái)看看AddEditTaskActivity,顧名思義有「添加任務(wù)」和「編輯任務(wù)」的功能细层。這個(gè)Activity不是我們所認(rèn)為的標(biāo)準(zhǔn)意義上的View類(lèi)惜辑,上面提到了,它所做的工作疫赎。
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);
}
這個(gè)Activity處理了部分UI初始化工作盛撑,同時(shí),創(chuàng)建了Presenter和載入了Fragment捧搞,即通過(guò)構(gòu)造函數(shù)將V和P做了聯(lián)系抵卫。
這里需要注意的是,在構(gòu)造函數(shù)的第二個(gè)參數(shù)tasksPepository是通過(guò)Injection注入的胎撇。很多新手看到這個(gè)介粘,就會(huì)有疑問(wèn),怎么找不到這個(gè)類(lèi)晚树。其實(shí)是這樣的姻采,在文件目錄中,我們看到了和app同級(jí)的目錄爵憎,存在mock和prod偎谁。其次在app.gradle里面設(shè)置了productFlavors來(lái)控制發(fā)行的版本。因此我們?nèi)ock文件夾下就能找到Injection類(lèi)了纲堵。
public class Injection {
public static TasksRepository provideTasksRepository(@NonNull Context context) {
checkNotNull(context);
return TasksRepository.getInstance(FakeTasksRemoteDataSource.getInstance(),
TasksLocalDataSource.getInstance(context));
}
}
這里通過(guò)注入巡雨,寫(xiě)入了一個(gè)假的遠(yuǎn)程Task數(shù)據(jù)源,用來(lái)做測(cè)試席函。
AddEditTaskContract分析
這是一個(gè)合約類(lèi)铐望,內(nèi)部有兩個(gè)內(nèi)部接口,分別繼承了V和P的基類(lèi)茂附,同時(shí)拓展了部分方法正蛙。
public interface AddEditTaskContract {
//View中實(shí)現(xiàn)了UI的調(diào)用邏輯
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
...
}
interface Presenter extends BasePresenter {
//Presenter中實(shí)現(xiàn)了業(yè)務(wù)邏輯
void saveTask(String title, String description);
...
}
}
AddEditTaskFragment分析
這個(gè)Fragment實(shí)現(xiàn)了合約類(lèi)中的View,所以通過(guò)覆寫(xiě)B(tài)aseView中的setPresenter()营曼,將Presenter和View關(guān)聯(lián)起來(lái)乒验。
public void setPresenter(@NonNull AddEditTaskContract.Presenter presenter) {
mPresenter = checkNotNull(presenter);
}
其次覆寫(xiě)了合約類(lèi)中的UI邏輯,在此我就不詳說(shuō)了蒂阱,比較簡(jiǎn)單锻全,大家可以自己看看狂塘。
AddEditTaskPresenter分析
在這個(gè)Presenter中,通過(guò)構(gòu)造函數(shù)將Presenter和View關(guān)聯(lián)在一起鳄厌。構(gòu)造函數(shù)的調(diào)用發(fā)生在上面的Activity中荞胡。
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView, boolean shouldLoadDataFromRepo) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mIsDataMissing = shouldLoadDataFromRepo;
//將Presenter和View關(guān)聯(lián)在一起
mAddTaskView.setPresenter(this);
}
這里將數(shù)據(jù)源tasksRepository傳入到Presenter的構(gòu)造函數(shù)中,可以使得Presenter在處理業(yè)務(wù)邏輯的時(shí)候了嚎,將數(shù)據(jù)教由給Model層處理泪漂。
這樣的好處是顯而易見(jiàn)的,徹底的將M-V-P分離開(kāi)來(lái)歪泳,實(shí)現(xiàn)了解耦萝勤,也讓整個(gè)程序的框架變得清楚明了起來(lái)。
TasksRepository分析
TasksRepository繼承自TaskDataSource呐伞,TaskDataSource這個(gè)類(lèi)中定義了兩個(gè)回調(diào)接口敌卓,以及部分實(shí)現(xiàn)方法。
interface LoadTasksCallback {
void onTasksLoaded(List<Task> tasks);
void onDataNotAvailable();
}
interface GetTaskCallback {
void onTaskLoaded(Task task);
void onDataNotAvailable();
}
而TaskRepository中首先定義了兩個(gè)數(shù)據(jù)源荸哟,一個(gè)是本地?cái)?shù)據(jù)源mTasksLocalDataSource,另一個(gè)是遠(yuǎn)程數(shù)據(jù)源mTasksRemoteDataSoure瞬捕。
下來(lái)我們重點(diǎn)看看獲取任務(wù)的方法getTasks(LoadTasksCallbac callback)
public void getTasks(@NonNull final LoadTasksCallback callback) {
checkNotNull(callback);
// Respond immediately with cache if available and not dirty
if (mCachedTasks != null && !mCacheIsDirty) {
//TODO: What is Map's function values()
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);
}
});
}
}
我們來(lái)看看上面的代碼:
- 從Cache里面讀并且Cache中數(shù)據(jù)沒(méi)有被污染鞍历,則直接返回
- 若Cache數(shù)據(jù)已經(jīng)被污染,則獲取遠(yuǎn)程的數(shù)據(jù)源肪虎。否則劣砍,直接從本地?cái)?shù)據(jù)庫(kù)查詢(xún),若查詢(xún)不到扇救,則獲取遠(yuǎn)程數(shù)據(jù)源
以上刑枝,可以看出來(lái),TasksRepository很好的將兩個(gè)數(shù)據(jù)源的調(diào)度策略對(duì)外隱藏了起來(lái)迅腔,使得調(diào)用者不用關(guān)心如何選擇數(shù)據(jù)源調(diào)度装畅。
總結(jié)
再往下面就是一些業(yè)務(wù)實(shí)現(xiàn)邏輯上面的事情了,大家可以自己去看看沧烈。
總的來(lái)說(shuō)掠兄,Google給了我們一個(gè)很好的范例,通過(guò)一個(gè)簡(jiǎn)單的APP锌雀,把MVP結(jié)構(gòu)擺在了我們面前蚂夕。當(dāng)然你可以不認(rèn)可它,而要將Activity去掉腋逆,那也是可以的婿牍。我到覺(jué)得現(xiàn)有的這種結(jié)構(gòu),更加方便的能看出來(lái)MVP各個(gè)角色之間的關(guān)系惩歉。
正如Google在項(xiàng)目介紹中說(shuō)為何使用Fragment的那樣:
Notice also in the following illustration that this version of the app uses fragments, and this is for two reasons:
? The use of both activities and fragments allows for a better separation of concerns which compliments this implementation of MVP. In this version of the app, the Activity is the overall controller which creates and connects views and presenters.
? The use of fragments supports tablet layouts or UI screens with multiple views.
使用activities和framgents使得MVP更好的分離開(kāi)等脂,Activity更多的是相當(dāng)于一個(gè)全局的控制器俏蛮,將Views和Presenters進(jìn)行聯(lián)系起來(lái)。
最后慎菲,讓我們結(jié)合這張圖片來(lái)回顧一下MVP在這個(gè)APP中的應(yīng)用:
![](https://raw.githubusercontent.com/wiki/googlesamples/android-architecture/images/mvp.png)
- Activity作為一個(gè)控制類(lèi)嫁蛇,將View和Presenter連接在一起
- Presenter直接調(diào)用Model層的Repository獲取數(shù)據(jù)
- Repository中則對(duì)外封裝了數(shù)據(jù)源的獲取邏輯,通過(guò)回調(diào)返回給上層
我的小站歡迎過(guò)來(lái)逛逛露该。