(1)MVP模式简介
MVP是模型(Model)、视图(View)、主持人(Presenter)的缩写,分别代表项目中3个不同的模块。
模型(Model):负责处理数据的加载或者存储,比如从网络或本地数据库获取数据等;
视图(View):负责界面数据的展示;比如UI的展示,UI的界面的更新,用户输入的获取
主持人(Presenter):相当于协调者,是模型与视图之间的桥梁,将模型与视图分离开来。
Presenter 获取model层的数据之后构建view层;也可以收到view层UI上的反馈命令后分发处理逻辑,交给model层做业务操作。主要的业务逻辑放在这里!
(2)MVP模式的应用
我们将构建一个简单的笔记应用程序来说明MVP。该应用程序允许用户记录笔记,将其保存在本地数据库中,并删除笔记。为了简单起见,应用程序将只有一个Activity。
(1)事件分析
首先我们需要新建一条笔记。那么使用MVP的流程就是:
- 用户键入一个笔记并点击添加按钮。
- Presenter获得用户输入的字符串创建一个Note对象,并通知Model将Note对象插入到数据库中。
- Model将该Note对象插入到数据库中,并通知Presenter笔记列表已更改。
Presenter将通过调用View的方法清除字符串,并要求视图刷新其列表以显示新创建的笔记。
现在让我们考虑实现这个效果所需的操作,并使用MVP分开它们。为了使各种对象松散耦合,通过使用接口来进行层之间的通信。我们至少需要四个接口:
RequiredViewOps
:必要的View操作,比如Ui加载,交由Presenter调用的方法ProvidedPresenterOps
:交由View调用的方法,构建view层的方法RequiredPresenterOps
:交由Model调用ProvidedModelOps
:交由Presenter调用
Presenter
实现了 接口PresenterOps
View
拥有PresenterOps的引用, 以访问 Presenter
Presenter
拥有ModelOps的引用以访问 Model
Presenter
实现了 RequiredPresenterOps
Model
拥有RequiredPresenterOps的引用 以访问Presenter
View
实现了 RequiredViewOps
Presenter
拥有RequiredViewOps的引用 以访问View
(3)在Android上实现Model View Presenter
我们主要关注MVP模式的实现。将跳过其他功能,如设置SQLite数据库,构建DAO或处理用户交互。
我们只 使用一个Activity布局,其中包括:
EditText
新的笔记Button
添加一个注释RecyclerView
列出所有笔记两个
TextView
元素和一个Button
因此,布局文件如下所示activity_main.xml:
<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
xmlns:app="http://schemas.android.com/apk/res-auto"
tools:context=".ui.MainActivity">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:popupTheme="@style/AppTheme.PopupOverlay" />
</android.support.design.widget.AppBarLayout>
<include layout="@layout/content_main" />
<ProgressBar android:id="@+id/progressbar"
style="@style/progressbar_endless"
/>
<android.support.design.widget.FloatingActionButton
android:id="@+id/fab"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="bottom|end"
android:layout_margin="@dimen/fab_margin"
android:src="@drawable/ic_action_add" />
</android.support.design.widget.CoordinatorLayout>
content_main.xml:
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:paddingBottom="@dimen/activity_vertical_margin"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
android:paddingTop="@dimen/activity_vertical_margin"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context=".ui.MainActivity"
tools:showIn="@layout/activity_main">
<EditText
android:id="@+id/edit_note"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<android.support.v7.widget.RecyclerView
android:id="@+id/list_notes"
android:layout_width="wrap_content"
android:layout_height="match_parent"
/>
</LinearLayout>
接口MainMVP
我们开始创建接口。为了保持一切安全,我们创建一个MainMVP类,其中包含负责管理层之间的通信的所有接口。
public interface MainMVP {
/**
* View层的接口,负责界面数据的展示和刷新
*
*/
interface RequiredViewOps{
void showProgress();
void hideProgress();
void showToast(String msg);
void showAlert(AlertDialog dialog);
void notifyItemRemoved(int position);
void notifyDataSetChanged();
void notifyItemInserted(int layoutPosition);
void notifyItemRangeChanged(int positionStart, int itemCount);
void clearEditText();
//这两个方法用于获得当前Activity对象
Context getAppContext();
Context getActivityContext();
}
/**
* Presenter层的接口,负责业务逻辑,例子中就是添加和删除的逻辑
* 提供方法给View与Presenter进行通信
* 处理用户交互,向Model等发送数据请求
*/
interface PresenterOps{
void onDestroy();
void clickNewNote(EditText editText);
void clickDeleteNote(Note note, int adapterPos, int layoutPos);
void loadData();
int getNotesCounts();
NotesViewHolder createViewHolder(ViewGroup viewGroup,int viewType);
void bindViewHolder(NotesViewHolder holder,int position);
}
/**
* Presenter层的接口
* Model处理完逻辑之后,结果通过RequiredPresenterOps回调通知Presenter
* 提供方法给Model与Presenter通信
*/
interface RequiredPresenterOps{
Context getAppContext();
Context getActivityContext();
}
/**
* Model层的接口
* 处理所有数据相关的业务逻辑,在这个例子中,负责添加数据和删除数据,获得数据列表和单个数据
*/
interface ModelOps{
int insereNota(Note nota);
void onDestroy();
int loadData();
int getNotesCount();
Note getNote(int position);
boolean deleteNote(Note note, int adapterPos);
}
}
View层描述和具体代码
现在是创建Model,View和Presenter的时候了。由于 MainActivity 将作为View,它应该实现 RequiredViewOps 接口。
MVP下Activity和Fragment以及View的子类体现在了这一 层,Activity一般也就做加载UI视图、设置监听再交由Presenter处理的一些工作,所以也就需要持有相应Presenter的引用。本层所需要做的操作就是在每一次有相应交互的时候,调用presenter的相关方法就行。(比如说,button点击)
我们可以先分析View层的接口大概需要的方法:
- (1)添加数据到数据库的过程中需要加载信息,比如提供一个加载框
- (2)添加成功后需要调用adapter的notifyItemInserted(),notifyItemRangeChanged()插入到列表中
- (3)添加成功后清除输入框
- (4)加载数据成功后刷新列表数据,取消加载框
- (5)若加载数据失败,如无网络连接,则需要给用户提示信息
- (6)从数据库中删除数据
- (7)从列表中删除数据
public class MainActivity extends AppCompatActivity implements View.OnClickListener,MainMVP.RequiredViewOps{
private EditText editText;
private ProgressBar progressBar;
private RecyclerView noteRecycler;
private Toolbar toolbar;
private FloatingActionButton fab;
private NoteAdapter mAdapter;
private MainMVP.PresenterOps mPresenter;
private List<Note> mData;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
setupMVP();
setupView();
initEvent();
}
@Override
protected void onResume() {
super.onResume();
mPresenter.loadData();
}
@Override
protected void onDestroy() {
super.onDestroy();
mPresenter.onDestroy();
}
private void initEvent() {
fab.setOnClickListener(this);
}
private void setupView() {
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);
fab = (FloatingActionButton) findViewById(R.id.fab);
editText = (EditText) findViewById(R.id.edit_note);
progressBar = (ProgressBar) findViewById(R.id.progressbar);
noteRecycler = (RecyclerView) findViewById(R.id.list_notes);
LinearLayoutManager linearLayoutManager = new LinearLayoutManager( this );
linearLayoutManager.setOrientation(LinearLayoutManager.VERTICAL);
mAdapter = new NoteAdapter();
noteRecycler.setLayoutManager(linearLayoutManager);
noteRecycler.setItemAnimator(new DefaultItemAnimator());
noteRecycler.setAdapter(mAdapter);
}
//实例化presenter
private void setupMVP() {
mPresenter = new MainPresenter(this);
}
@Override
public void showProgress() {
progressBar.setVisibility(View.VISIBLE);
}
@Override
public void hideProgress() {
progressBar.setVisibility(View.GONE);
}
@Override
public void showToast(String msg) {
Toast.makeText(this,msg,Toast.LENGTH_SHORT).show();
}
@Override
public void showAlert(AlertDialog dialog) {
dialog.show();
}
@Override
public void notifyItemRemoved(int position) {
mAdapter.notifyItemRemoved(position);
}
@Override
public void notifyDataSetChanged() {
mAdapter.notifyDataSetChanged();
}
@Override
public void notifyItemInserted(int layoutPosition) {
mAdapter.notifyItemInserted(layoutPosition);
}
@Override
public void notifyItemRangeChanged(int positionStart, int itemCount) {
mAdapter.notifyItemRangeChanged(positionStart,itemCount);
}
@Override
public void clearEditText() {
editText.setText("");
}
//获得当前Activity
@Override
public Context getAppContext() {
return getActivityContext();
}
//获得当前Activity
@Override
public Context getActivityContext() {
return this;
}
@Override
public void onClick(View view) {
switch (view.getId()){
case R.id.fab:
mPresenter.clickNewNote(editText);
break;
}
}
public class NoteAdapter extends RecyclerView.Adapter<NotesViewHolder>{
private List<Note> mNotes;
public NoteAdapter() {
}
/**
* 这里把Adapter中的方法也放到了presenter中
* Adapter的作用其实和Presenter十分类似
* @param parent
* @param viewType
* @return
*/
@Override
public NotesViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
// LayoutInflater inflater = LayoutInflater.from(parent.getContext());
// View view = inflater.inflate(R.layout.holder_note,parent,false);
// return new NotesViewHolder(view);
return mPresenter.createViewHolder(parent,viewType);
}
@Override
public void onBindViewHolder(NotesViewHolder holder, int position) {
// Note note = mNotes.get(position);
// //holder.mTitleTextView.setText(info.getmTtitle());
// holder.bindView(note);
mPresenter.bindViewHolder(holder,position);
}
@Override
public int getItemCount() {
return mPresenter.getNotesCounts();
}
}
}
Presenter层描述和具体代码
Presenter是中间人,需要实现两个接口:
PresenterOps
以允许来自View的请求
RequiredPresenterOps
从Model接收结果
Presenter扮演着view和model的中间层的角色。获取model层的数据之后构建view层;也可以收到view层UI上的反馈命令后分发处理逻辑,交给model层做业务操作。它也可以决定View层的各种操作。
因此它包括了:
- (1)点击添加数据的逻辑;
- (2)加载数据的逻辑;
- (3)还承担了Adapter的职责;
注意:我们需要使用View层的弱引用, WeakReference<MVP_Main.RequiredViewOps>
因为MainActivity 可以随时销毁,我们希望避免内存泄漏。
public class MainPresenter implements MainMVP.PresenterOps,MainMVP.RequiredPresenterOps {
private WeakReference<MainMVP.RequiredViewOps> mNotesView;
// Model reference
private MainMVP.ModelOps mNotesModel;
/**
* Presenter Constructor
* @param view MainActivity
*/
public MainPresenter(MainMVP.RequiredViewOps notesView) {
mNotesView = new WeakReference<>(notesView);
this.mNotesModel = new MainModel(this);
}
/**
* 通过此方法获得View对象
* Return the View reference.
* Throw an exception if the View is unavailable.
*/
private MainMVP.RequiredViewOps getView() throws NullPointerException{
if ( mNotesView != null )
return mNotesView.get();
else
throw new NullPointerException("View is unavailable");
}
@Override
public void onDestroy() {
mNotesModel.onDestroy();
//添加数据的业务逻辑
@Override
public void clickNewNote(EditText editText) {
getView().showProgress();
final String noteText = editText.getText().toString();
if(!noteText.isEmpty()){
new AsyncTask<Void,Void,Integer>(){
@Override
protected Integer doInBackground(Void... voids) {
//调用Model的插入数据方法
return mNotesModel.insereNota(makeNote(noteText));
}
@Override
protected void onPostExecute(Integer integer) {
try{
if(integer>-1){
getView().clearEditText();
getView().hideProgress();
getView().notifyItemInserted(integer);
getView().notifyItemRangeChanged(integer,
mNotesModel.getNotesCount());
}else{
getView().hideProgress();
getView().showToast("Error Create Note "+noteText);
}
}catch (NullPointerException e){
e.printStackTrace();
}
}
}.execute();
}else{
try{
getView().showToast("Cannot Create blank Note!");
}catch (NullPointerException e){
e.printStackTrace();
}
}
}
//创建Note对象
private Note makeNote(String noteText) {
Note note = new Note();
note.setText(noteText);
note.setDate(getDate());
return note;
}
private String getDate() {
return new SimpleDateFormat("HH:mm:ss - MM/dd/yyyy", Locale.getDefault()).format(new Date());
}
@Override
public void clickDeleteNote(Note note, int adapterPos, int layoutPos) {
openDeleteAlert(note,adapterPos,layoutPos);
}
private void openDeleteAlert(final Note note, final int adapterPos, final int layoutPos) {
AlertDialog.Builder alertBuilder = new AlertDialog.Builder(getActivityContext());
alertBuilder.setPositiveButton("DELETE", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
deleteNote(note,adapterPos,layoutPos);
}
});
alertBuilder.setNegativeButton("CANCEL", new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialogInterface, int i) {
dialogInterface.dismiss();
}
});
alertBuilder.setTitle("DELETE NOTE");
alertBuilder.setMessage("Delete "+note.getText()+"?");
AlertDialog alertDialog = alertBuilder.create();
try{
getView().showAlert(alertDialog);
}catch (Exception e){
e.printStackTrace();
}
}
/**
* 删除Note对象的具体逻辑
* @param note
* @param adapterPos
* @param layoutPos
*/
private void deleteNote(final Note note, final int adapterPos, final int layoutPos) {
getView().showProgress();
new AsyncTask<Void,Void,Boolean>(){
@Override
protected Boolean doInBackground(Void... voids) {
//调用Model的删除数据方法
return mNotesModel.deleteNote(note,adapterPos);
}
@Override
protected void onPostExecute(Boolean aBoolean) {
try{
if (aBoolean){
getView().hideProgress();
getView().notifyDataSetChanged();
getView().notifyItemRemoved(layoutPos);
}else{
getView().showToast("Error deleting note["+note.getId()+"]");
}
}catch (Exception e){
e.printStackTrace();
}
}
}.execute();
}
//加载数据的业务逻辑
@Override
public void loadData() {
try{
getView().showProgress();
new AsyncTask<Void,Void,Integer>(){
@Override
protected Integer doInBackground(Void... voids) {
//调用Model的加载数据方法
return mNotesModel.loadData();
}
@Override
protected void onPostExecute(Integer aBoolean) {
try{
getView().hideProgress();
if(aBoolean<=0){
getView().hideProgress();
}else{
getView().notifyDataSetChanged();
}
}catch (NullPointerException e){
e.printStackTrace();
}
super.onPostExecute(aBoolean);
}
}.execute();
}catch (NullPointerException e){
e.printStackTrace();
}
}
/**
* 从Model层获得笔记列表大小
* @return Notes list size
*/
@Override
public int getNotesCounts() {
return mNotesModel.getNotesCount();
}
@Override
public NotesViewHolder createViewHolder(ViewGroup viewGroup, int viewType) {
LayoutInflater inflater = LayoutInflater.from(viewGroup.getContext());
View view = inflater.inflate(R.layout.holder_note,viewGroup,false);
return new NotesViewHolder(view);
}
@Override
public void bindViewHolder(final NotesViewHolder holder, int position) {
//从Model层获得当前Note对象
final Note note = mNotesModel.getNote(position);
holder.text.setText( note.getText() );
holder.date.setText( note.getDate() );
holder.btnDelete.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
clickDeleteNote(note, holder.getAdapterPosition(), holder.getLayoutPosition());
}
});
}
@Override
public Context getAppContext() {
return getView().getAppContext();
}
@Override
public Context getActivityContext() {
return getView().getActivityContext();
}
}
Model层描述和具体代码
模型层负责处理数据相关的业务逻辑。它包含添加到数据库的ArrayList
列表 ,用于执行数据库操作的DAO引用以及对Presenter的引用。
public class MainModel implements MainMVP.ModelOps {
private MainMVP.RequiredPresenterOps mPresenter;
private DAO mDAO;
public ArrayList<Note> mNotes;
public MainModel(MainMVP.RequiredPresenterOps mPresenter) {
this.mPresenter = mPresenter;
mDAO = new DAO(mPresenter.getAppContext());
}
@Override
public int insereNota(Note note) {
Note insertNote = mDAO.insertNote(note);
if(insertNote !=null){
if(loadData()>0){
return getNotePosition(insertNote);
}
}
return -1;
}
private int getNotePosition(Note insertNote) {
for(int i = 0;i<mNotes.size();i++){
if(insertNote.getId()==mNotes.get(i).getId()){
return i;
}
}
return -1;
}
@Override
public void onDestroy() {
mPresenter = null;
mDAO = null;
mNotes = null;
}
@Override
public int loadData() {
mNotes = mDAO.getAllNotes();
return mNotes.size()>0?mNotes.size():0;
}
@Override
public int getNotesCount() {
if ( mNotes != null )
return mNotes.size();
return 0;
}
@Override
public Note getNote(int position) {
return mNotes.get(position);
}
@Override
public boolean deleteNote(Note note, int adapterPos) {
long delId = mDAO.deleteNote(note);
if(delId>0){
mNotes.remove(adapterPos);
return true;
}
return false;
}
}
(4)MVP经典参考资料
这些是我认为对MVP讲解比较清晰易懂的文章,结合食用,风味更佳:
http://android.jobbole.com/83311/
http://www.cnblogs.com/liuling/archive/2015/12/23/mvp-pattern-android.html
https://rocko.xyz/2015/02/06/Android中的MVP/
https://segmentfault.com/a/1190000003927200
源码可以访问Code of Github.