abstrakt:還記得一年前,在上一家公司的時(shí)候,領(lǐng)導(dǎo)準(zhǔn)備接一個(gè)案子,客戶那邊給了一份開(kāi)發(fā)規(guī)范的文檔,上面明確的寫(xiě)著要采用MVP模式進(jìn)行開(kāi)發(fā)。一開(kāi)始看到這個(gè)模式時(shí)候,一臉懵逼,什么是MVP?不懂,問(wèn)一下同事,也沒(méi)有人能說(shuō)清楚,無(wú)奈那就百度吧。好簡(jiǎn)單粗暴的說(shuō)明啊,還是一臉懵逼。 后來(lái),不知道為什么案子也沒(méi)有接,就這樣不了了之了。最近發(fā)生了很多事,從上一家公司離職,與朋友準(zhǔn)備搞公司,搞了差不多2個(gè)月,到現(xiàn)在
還記得一年前,在上一家公司的時(shí)候,領(lǐng)導(dǎo)準(zhǔn)備接一個(gè)案子,客戶那邊給了一份開(kāi)發(fā)規(guī)范的文檔,上面明確的寫(xiě)著要采用MVP模式進(jìn)行開(kāi)發(fā)。一開(kāi)始看到這個(gè)模式時(shí)候,一臉懵逼,什么是MVP?不懂,問(wèn)一下同事,也沒(méi)有人能說(shuō)清楚,無(wú)奈那就百度吧。
好簡(jiǎn)單粗暴的說(shuō)明啊,還是一臉懵逼。 后來(lái),不知道為什么案子也沒(méi)有接,就這樣不了了之了。
最近發(fā)生了很多事,從上一家公司離職,與朋友準(zhǔn)備搞公司,搞了差不多2個(gè)月,到現(xiàn)在的從團(tuán)隊(duì)退出。然后準(zhǔn)備找工作。。。。
在這期間搞項(xiàng)目的時(shí)候,就抽空研究了一下MVP模式,試著用它進(jìn)行開(kāi)發(fā)。因?yàn)橹皇且粋€(gè)項(xiàng)目,涉及的還不深,所以叫試水。記錄一下。
網(wǎng)上關(guān)于MVP的介紹、講解、示例以及開(kāi)源的項(xiàng)目很多,我這里就不廢話了,如果現(xiàn)在還有人不了解什么是MVP,那就百度去吧。我這里參考Google的源碼todo-mvp來(lái)說(shuō)。 先看一下目錄結(jié)構(gòu):
不要問(wèn)我為什么我截圖的字體顏色是藍(lán)色的,我不會(huì)告訴你我是用的octotree瀏覽器插件。 這里有兩個(gè)Base文件:BaseView、BasePresenter,好像和VP有關(guān),先看一下源碼:
package com.example.android.architecture.blueprints.todoapp; public interface BasePresenter { void start(); } package com.example.android.architecture.blueprints.todoapp; public interface BaseView<T> { void setPresenter(T presenter); }
What ?這是什么鬼??jī)蓚€(gè)接口?干嗎用的?不知道,不明覺(jué)厲。不管了,反正一個(gè)對(duì)應(yīng)的V,一個(gè)對(duì)應(yīng)的是P就是了。好吧,你們估計(jì)再說(shuō)我這不廢話了。這里看不出什么東西,那就從程序的入口看吧,從AndroidManifest.xml中找到程序的入口是tasks/TasksActivity。
<activity android:name="com.example.android.architecture.blueprints.todoapp.tasks.TasksActivity" android:theme="@style/AppTheme.OverlapSystemBar"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity>
打開(kāi)tasks包,看到如下幾個(gè)文件
我的習(xí)慣是先不看里面的內(nèi)容,先看文件名字,大致了解每個(gè)文件是干嘛用的,這樣有助于對(duì)整體進(jìn)行把控,所以這里就體現(xiàn)了命名規(guī)范的重要性,關(guān)于命名規(guī)范,可以百度,也可以參考我的另外一篇文章:Android 開(kāi)發(fā)規(guī)范(個(gè)人版)。哎呀,又扯遠(yuǎn)了,繼續(xù)回來(lái)看代碼。
第一個(gè)文件,應(yīng)該是個(gè)自定義的布局,好像沒(méi)什么太大的關(guān)系。 第二個(gè)主程序的入口,沒(méi)啥說(shuō)的。 第三個(gè)Contract (契約),誰(shuí)和誰(shuí)的,不懂,先不管。 第四個(gè)Filter Type(過(guò)濾器類(lèi)型),應(yīng)該是一些類(lèi)型的定義,好像關(guān)系也不大,先不管。 第五個(gè)Fragment,不說(shuō)了 第六個(gè)Persenter,這個(gè)有關(guān)系,而且還很大,那就先看一下它吧。
public class TasksPresenter implements TasksContract.Presenter { .... private final TasksContract.View mTasksView; public TasksPresenter(@NonNull TasksRepository tasksRepository, @NonNull TasksContract.View tasksView) { ... mTasksView = checkNotNull(tasksView, "tasksView cannot be null!"); mTasksView.setPresenter(this); } @Override public void start() { ... } }
省略了一下不必要的代碼,這里可以看到幾個(gè)關(guān)鍵點(diǎn), 1、TasksPresenter本身實(shí)現(xiàn)了TasksContract.Presenter; 2、構(gòu)造函數(shù)里面需要傳入一個(gè)TasksContract.View; 3、拿到這個(gè)tasksView后賦值給了mTasksView,并把自己通過(guò)mTasksView.setPresenter(this)方法傳遞出去。
到這里算是有點(diǎn)眉目了。知道了P和V是如何綁定在一起的了。 P綁定V:通過(guò)實(shí)例化是傳入V; V綁定P: 通過(guò)v.setPresenter(P);
但如何使用V呢?繼續(xù)往下,這里用到了TasksContract這個(gè)契約,跟蹤一下代碼看一下。
package com.example.android.architecture.blueprints.todoapp.tasks;
package com.example.android.architecture.blueprints.todoapp.tasks; import android.support.annotation.NonNull; import com.example.android.architecture.blueprints.todoapp.BaseView; import com.example.android.architecture.blueprints.todoapp.data.Task; import com.example.android.architecture.blueprints.todoapp.BasePresenter; import java.util.List; /** * 這指定 view 和 presenter 之間的 contract。 * This specifies the contract between the view and the presenter. */ public interface TasksContract { interface View extends BaseView<Presenter> { void setLoadingIndicator(boolean active); void showTasks(List<Task> tasks); void showAddTask(); void showTaskDetailsUi(String taskId); void showTaskMarkedComplete(); void showTaskMarkedActive(); void showCompletedTasksCleared(); void showLoadingTasksError(); void showNoTasks(); void showActiveFilterLabel(); void showCompletedFilterLabel(); void showAllFilterLabel(); void showNoActiveTasks(); void showNoCompletedTasks(); void showSuccessfullySavedMessage(); boolean isActive(); void showFilteringPopUpMenu(); } interface Presenter extends BasePresenter { void result(int requestCode, int resultCode); void loadTasks(boolean forceUpdate); void addNewTask(); void openTaskDetails(@NonNull Task requestedTask); void completeTask(@NonNull Task completedTask); void activateTask(@NonNull Task activeTask); void clearCompletedTasks(); void setFiltering(TasksFilterType requestType); TasksFilterType getFiltering(); } }
看到這里就有點(diǎn)意思了,契約類(lèi)里面主要做了兩件事,
定義了一個(gè)繼承自BaseView的接口(View), 并聲明需要實(shí)現(xiàn)的方法。
定義一個(gè)繼承自BasePresenter的接口并繼承(Presenter),并聲明需要實(shí)現(xiàn)的方法。
其實(shí)也可以說(shuō)是一件事,就是聲明一些接口。
哦,這下知道Contract是干嗎用的了,就是把V、P的接口寫(xiě)到同一個(gè)文件里面啊,好像也并么有什么高大上的東西???那我把這個(gè)文件分成兩個(gè)文件寫(xiě),應(yīng)該也可以吧?我認(rèn)為是可以的。但是又基于Contract的含義即:契約,就是把View和Presenter綁定到一起:
interface Presenter extends BasePresenter {} interface View extends BaseView<Presenter> {}
這樣還是按照官方的來(lái),用一個(gè)文件來(lái)寫(xiě)好了。
Presenter找到了,Contract也知道是干嗎用的了。那么View呢,從文件名已經(jīng)找不到了,那就看繼續(xù)看代碼吧。從TasksActivity看起,首先我們知道TasksPresenter構(gòu)造函數(shù)里面有一個(gè)TasksContract.View的參數(shù),那么就找這個(gè)參數(shù)傳的什么。
public class TasksActivity extends AppCompatActivity { .... private TasksPresenter mTasksPresenter; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.tasks_act); .... TasksFragment tasksFragment = (TasksFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame); if (tasksFragment == null) { // Create the fragment tasksFragment = TasksFragment.newInstance(); } ..... // Create the presenter //這個(gè)傳入的是 tasksFragment mTasksPresenter = new TasksPresenter( Injection.provideTasksRepository(getApplicationContext()), tasksFragment); } ....... }
由上面的代碼可知,TasksPresenter 傳入了一個(gè)TasksFragment的對(duì)象,那這樣的TasksFragment就應(yīng)該是所謂的View了,跟蹤進(jìn)入TasksFragment。
public class TasksFragment extends Fragment implements TasksContract.View {private TasksContract.Presenter mPresenter; ......public TasksFragment() {// Requires empty public constructor}public static TasksFragment newInstance() {return new TasksFragment(); } ......@Overridepublic void onResume() {super.onResume(); mPresenter.start(); }@Overridepublic void setPresenter(@NonNull TasksContract.Presenter presenter) { mPresenter = checkNotNull(presenter); } ....@Overridepublic View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { ..... } ....... }
果然,TasksFragment 實(shí)現(xiàn)了TasksContract.View,就是所謂的View。他的核心點(diǎn)在于: 1、實(shí)現(xiàn)了TasksContract.View; 2、重寫(xiě)setPresenter方法,接收傳遞過(guò)來(lái)的presenter。
這樣之后,就可以通過(guò)presenter.xxxxx()的方式來(lái)調(diào)用presenter里面定義的一些方法,而presenter里面定義的方法主要執(zhí)行耗時(shí)操作或者一些數(shù)據(jù)處理等等,等到presenter里面的函數(shù)執(zhí)行完畢之后,在通過(guò)mTasksView.xxx()的方式回調(diào)給TasksFragment,TasksFragment再進(jìn)行頁(yè)面的改變。
官方給的Demo就看到這里吧,因?yàn)殛P(guān)于MVP核心的東西差不多就看完了,或許還有更多的東西我沒(méi)有發(fā)掘。
根據(jù)官方Demo,我這里總結(jié)了一下實(shí)現(xiàn)MVP模式的步驟:
1、定義BaseView、BasePresenter。可以參考官方示例。 2、定義契約類(lèi),在里面定義兩個(gè)接口,舉個(gè)登錄的例子:
public interface LoginContract { interface Presenter extends BasePresenter { /** * 登錄 */ void login(); } interface View extends BaseView<Presenter> { /** * 返回登錄成功 */ void loginSuccess(); void loginFailed(String errorMessage); } }
3、定義一個(gè)實(shí)現(xiàn)契約類(lèi)中Presenter接口的類(lèi),用于實(shí)現(xiàn)邏輯代碼,并把處理結(jié)果返回。例如:
public class LoginPresenter implements LoginContract.Presenter { private LoginContract.View view; public LoginPresenter(LoginContract.View view) { this.view = view; view.setPresenter(this); } /** * 登錄 */ @Override public void login() { String useName = view.getUserName(); String pwd = view.getPwd(); Map<String, String> params = new HashMap<String, String>(); params.put("phone", useName); params.put("password", pwd); AuthRequestUtil.doLogin(params, User.class, new ResponseCallBack<User>() { @Override public void onSuccess(User data) { super.onSuccess(data); saveLoginInfo(data); //返回登錄成功 view.loginSuccess(); } @Override public void onFailure(ServiceException e) { super.onFailure(e); //返回登錄失敗 view.loginFailed(e.getMessage()); } }); } }
4、在Activity 或者Fragment中實(shí)現(xiàn)契約類(lèi)中的View接口。
要實(shí)現(xiàn)簡(jiǎn)單的MVP,差不多就這4步。接觸的時(shí)間也不長(zhǎng),中間有可能會(huì)出現(xiàn)一些紕漏或者錯(cuò)誤,如果有這方面的牛人在看到這篇文章的時(shí)候,希望能給出寶貴意見(jiàn)。這里先說(shuō)聲謝謝。
關(guān)于MVP,還有很多東西,我看到還有關(guān)Presenter生命周期的相關(guān)文章,還沒(méi)有仔細(xì)研究。這里先記一下。等有時(shí)間在仔細(xì)研究一下。