最近在籌備新項目的開發,打算使用google官方推薦的MVP配合Retrofit+RxAndroid打造一套新項目的框架。
先從MVP開始學習,然而網上關於MVP的博客以及學習資料實在是太多,所以打算刪繁就簡,先研究一番google官方的MVP實例。
谷歌的MVP框架源碼已經發布五個多月了,如今已經成爲了時下最火熱的Android框架,其視圖與模型完全分離的特性也受到了越來越多開發者的喜愛。
谷歌的MVP框架sample下載地址爲https://github.com/googlesamples/android-architecture/tree/todo-mvp/
本文是基於該實例進行對MVP進行源碼與概念的研究的。
MVP與MVP概念區分:
傳統MVC概念分爲:模型,視圖,控制三個模塊,如下圖所示:
其中,View是可以直接訪問Model的,所以也就造成了View還要承擔一定的業務邏輯,不能作爲單純的視圖層來使用,而且View和Controller也很難分開,很多時候我們經常會在View的響應時間裏寫很多Controller代碼,這樣直接導致代碼的複用性大大降低,到處都是UI實現相同但是邏輯略微不同的代碼,導致項目變得越來越臃腫不堪。
MVP的模式可由下圖所示:
從圖中可以清楚的看到,View僅僅只是“View”,不需要處理任何業務層的代碼,一切View和Model的操作都在Presenter中執行。
View是指顯示數據並且和用戶交互的層。在安卓中,它們可以是一個Activity,一個Fragment,一個android.view.View或者是一個Dialog。
Model 是數據源層。比如數據庫接口或者遠程服務器的api。
Presenter是從Model中獲取數據並提供給View的層,Presenter還負責處理後臺任務。
這裏需要說明的是Presenter與View是沒有直接關聯的,而是通過定義好的接口進行交互,從而使得在變更View的時候可以保持presenter的不邊,即重用View!這是MVC無法做到的。
這樣也使得我們在設計調試程序的時候變得更爲簡單,不必要先寫出詳細的View 佈局才能進行邏輯功能測試,因爲View在MVP中只是薄薄的一層顯示功能,我們可以首先設計和開發Presenter,在這個時候View只需要顯示一些基本的信息就可以了,在後面再根據需求更改View,這樣對Presenter層也不會有任何影響。
絕對不能與Model發生關係,是View作爲顯示層的關鍵原則。但是有的時候可能業務邏輯比較複雜,需要用到Model層的相關數據,這個時候可以在View和Presenter之間放置一個adapter,由這個adapter來訪問Model和View,避免兩者之間發生直接關聯,這個adapter也必須實現View的接口,保證了與Presenter之間接口的不變。這樣便可以實現View與Model層之間的完全解耦。
最後也是最重要的一點,在MVP模式裏,View應該只有簡單的Get和Set方法,不需要有任何複雜的業務邏輯,只需要toshow就可以了。
MVP官方實例源碼分析:
Github地址再發一遍:TODO MVP
導入到本地之後,我們先分析它的包結構:
/**
* This specifies the contract between the view and the presenter.
*/
public interface AddEditTaskContract {
interface View extends BaseView<Presenter> {
void showEmptyTaskError();
void showTasksList();
void setTitle(String title);
void setDescription(String description);
boolean isActive();
}
interface Presenter extends BasePresenter {
void saveTask(String title, String description);
void populateTask();
}
}
可以看到其中定義了View和Presenter層的接口,其中View接口只有基本的顯示與設置方法,改變UI的顯示文字,還有一個isActive方法判斷Fragment是否已經被添加到主Activity中。Presenter接口有兩個方法,一個saveTask負責儲存數據,一個populateTask從本地數據源獲取任務。
/**
* Displays an add or edit task screen.
*/
public class AddEditTaskActivity extends AppCompatActivity {
public static final int REQUEST_ADD_TASK = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.addtask_act);
// Set up the toolbar.
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
ActionBar actionBar = getSupportActionBar();
actionBar.setDisplayHomeAsUpEnabled(true);
actionBar.setDisplayShowHomeEnabled(true);
AddEditTaskFragment addEditTaskFragment =
(AddEditTaskFragment) getSupportFragmentManager().findFragmentById(R.id.contentFrame);
String taskId = null;
if (addEditTaskFragment == null) {
addEditTaskFragment = AddEditTaskFragment.newInstance();
if (getIntent().hasExtra(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID)) {
taskId = getIntent().getStringExtra(
AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID);
actionBar.setTitle(R.string.edit_task);
Bundle bundle = new Bundle();
bundle.putString(AddEditTaskFragment.ARGUMENT_EDIT_TASK_ID, taskId);
addEditTaskFragment.setArguments(bundle);
} else {
actionBar.setTitle(R.string.add_task);
}
ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),
addEditTaskFragment, R.id.contentFrame);
}
// Create the presenter
new AddEditTaskPresenter(
taskId,
Injection.provideTasksRepository(getApplicationContext()),
addEditTaskFragment);
}
@Override
public boolean onSupportNavigateUp() {
onBackPressed();
return true;
}
@VisibleForTesting
public IdlingResource getCountingIdlingResource() {
return EspressoIdlingResource.getIdlingResource();
}
}
根據傳來的參數判斷是添加還是編輯,並初始化presenter(39-42)與view(16-18)。/**
* Main UI for the add task screen. Users can enter a task title and description.
*/
public class AddEditTaskFragment extends Fragment implements AddEditTaskContract.View {
public static final String ARGUMENT_EDIT_TASK_ID = "EDIT_TASK_ID";
private AddEditTaskContract.Presenter mPresenter;
private TextView mTitle;
private TextView mDescription;
public static AddEditTaskFragment newInstance() {
return new AddEditTaskFragment();
}
public AddEditTaskFragment() {
// Required empty public constructor
}
@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);
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) {
mPresenter.saveTask(mTitle.getText().toString(), mDescription.getText().toString());
}
});
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.addtask_frag, container, false);
mTitle = (TextView) root.findViewById(R.id.add_task_title);
mDescription = (TextView) root.findViewById(R.id.add_task_description);
setHasOptionsMenu(true);
setRetainInstance(true);
return root;
}
@Override
public void showEmptyTaskError() {
Snackbar.make(mTitle, getString(R.string.empty_task_message), Snackbar.LENGTH_LONG).show();
}
@Override
public void showTasksList() {
getActivity().setResult(Activity.RESULT_OK);
getActivity().finish();
}
@Override
public void setTitle(String title) {
mTitle.setText(title);
}
@Override
public void setDescription(String description) {
mDescription.setText(description);
}
@Override
public boolean isActive() {
return isAdded();
}
}
其中包含一個presenter的回調接口實例,用於響應回調,並更新回調presenter進行操作時帶來的視圖改變,代碼很簡單。/**
* Listens to user actions from the UI ({@link AddEditTaskFragment}), retrieves the data and updates
* the UI as required.
*/
public class AddEditTaskPresenter implements AddEditTaskContract.Presenter,
TasksDataSource.GetTaskCallback {
@NonNull
private final TasksDataSource mTasksRepository;
@NonNull
private final AddEditTaskContract.View mAddTaskView;
@Nullable
private String mTaskId;
/**
* Creates a presenter for the add/edit view.
*
* @param taskId ID of the task to edit or null for a new task
* @param tasksRepository a repository of data for tasks
* @param addTaskView the add/edit view
*/
public AddEditTaskPresenter(@Nullable String taskId, @NonNull TasksDataSource tasksRepository,
@NonNull AddEditTaskContract.View addTaskView) {
mTaskId = taskId;
mTasksRepository = checkNotNull(tasksRepository);
mAddTaskView = checkNotNull(addTaskView);
mAddTaskView.setPresenter(this);
}
@Override
public void start() {
if (!isNewTask()) {
populateTask();
}
}
@Override
public void saveTask(String title, String description) {
if (isNewTask()) {
createTask(title, description);
} else {
updateTask(title, description);
}
}
@Override
public void populateTask() {
if (isNewTask()) {
throw new RuntimeException("populateTask() was called but task is new.");
}
mTasksRepository.getTask(mTaskId, this);
}
@Override
public void onTaskLoaded(Task task) {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.setTitle(task.getTitle());
mAddTaskView.setDescription(task.getDescription());
}
}
@Override
public void onDataNotAvailable() {
// The view may not be able to handle UI updates anymore
if (mAddTaskView.isActive()) {
mAddTaskView.showEmptyTaskError();
}
}
private boolean isNewTask() {
return mTaskId == null;
}
private void createTask(String title, String description) {
Task newTask = new Task(title, description);
if (newTask.isEmpty()) {
mAddTaskView.showEmptyTaskError();
} else {
mTasksRepository.saveTask(newTask);
mAddTaskView.showTasksList();
}
}
private void updateTask(String title, String description) {
if (isNewTask()) {
throw new RuntimeException("updateTask() was called but task is new.");
}
mTasksRepository.saveTask(new Task(title, description, mTaskId));
mAddTaskView.showTasksList(); // After an edit, go back to the list.
}
}
可以看到presenter體現了其控制器的核心作用,所有的數據交互與視圖改變都在這裏得到體現,在以前的MVC模式中這些代碼一般都嵌套在Activity的各個角落,然而使用MVP模式使其完全抽離了出來,這纔是MVP模式最寶貴的思想精華。