Android中的MVP

(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层描述和具体代码

现在是创建ModelViewPresenter的时候了。由于 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.

  • 1
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值