MVP

作者: 咸鱼佬 | 来源:发表于2017-05-21 13:21 被阅读80次

概述

在Android中

  1. view 指代的是activity和xml
  2. model指代的是一切的业务逻辑层的东西,下载,文件读写的一些耗时与业务相关的操作
  3. presenter 就是连接view和model的桥梁

相对于mvc,mvp就是切断了model 和 view之间的直接联系


相对与mvc 在Android中activity的任务量非常大,所以继而优化出mvp

任务

实现一个小的mvp 的 demo ,功能是从网上下载一段json数据,然后再一步步优化一些小细节

流程

大概就是这么个样子

步骤

分析

看第一张图片可知道,presenter作为view 和 model的桥梁,必须是拥有view层和model层的实例(先不管是怎么样的是实例),而view能向presenter发起数据请求,那么也拥有presenter的实例。而model层接收完信息是怎么通知presenter的尼?那么也就是回调函数(其实也算是拥有一个presenter的实例了)

定义包和接口

model的接口

public interface IModel {

    /**
     *
     * @param loadDataListener presenter 实现这个接口,用于加载数据完成后
     *                         presenter能够知道
     * @return 因为可能需要耗时操作,不能卡死main线程,所以返回值为void
     */
    void loadData(LoadDataListener loadDataListener);

    interface LoadDataListener {

    /**
     *当数据加载完成之后调用此函数,将加载的数据发给presenter
     */
    void complete(String data);

}


}

presenter接口:

public interface IPresenter {

    void getData();
}

那么对于view,在这里就是activity,是否也需要抽象一些功能出来变成一个接口,比如加载前,加载中甚至加载完成的时候显示加载的数据?
view 的 接口

public interface IView {

    /**
     * 加载数据的时候一些用户提示
     */
    void showDowning();

    /**
     * 加载完成的时候显示数据
     */

    void showData(String data);
}

实现接口

view

public class MainActivity extends AppCompatActivity implements IView {

    private TextView mTextView;
    private Button mButton;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();

    }

    private void init() {
        mTextView = (TextView) this.findViewById(R.id.content_view);
        mButton = (Button) this.findViewById(R.id.download_data_btn);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //拥有一个presenter的实例,将一个Iview的实例传递过去
                new PresenterImp(MainActivity.this).getData();
            }
        });
    }

    /**
     * 提示用户加载开始了
     */
    @Override
    public void showDowning() {
        Toast.makeText(this, "下载中....", Toast.LENGTH_SHORT).show();
    }

    /**
     * 展示加载的数据
     *
     * @param data
     */
    @Override
    public void showData(String data) {
        mTextView.setText(data);
    }
}

model:

public class ModelImp implements IModel {

    private static final String TAG = "ModelImp";

    /**
     *
     * @param loadDataListener presenter 实现这个接口,用于加载数据完成后
     *                         presenter能够知道
     */
    @Override
    public void loadData(final LoadDataListener loadDataListener) {

        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    URL url = new URL("http://tj.nineton.cn/Heart/index/future24h/" +
                            "?city=CHSH000000&language=zh-chs&" +
                            "key=36bdd59658111bc23ff2bf9aaf6e345c");

                    URLConnection urlConnection = url.openConnection();
                    InputStream inputStream = urlConnection.getInputStream();

                    byte[] bytes = new byte[1024];
                    inputStream.read(bytes);

                    String s = new String(bytes);

                    Log.d(TAG, "run: " + s);
                    loadDataListener.complete(s);
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}

这里下载的数据是没有解释的,懒
presenter:

public class PresenterImp implements IPresenter {

    //拥有一个model 的实例,我们可以直接new 一个
    IModel mIModel = new ModelImp();

    //那么拥有一个view的实例怎么写?直接把activity传过来?
    //显然是不合适的,我们拿到这个view的实例只是为了提示下载时候的一些信息,是不是,下载的时候告诉用户开始下载了
    //所以我们就只需要一个Iview的接口的实例就可以了,并且view(activity)已经实现了这个接口的

    IView mIView;

    public PresenterImp(IView IView) {
        mIView = IView;
    }

    @Override
    public void getData() {

        if (null != mIView) {
            //提示用户下载开始了
            mIView.showDowning();
        }

        //开始下载
        mIModel.loadData(new IModel.LoadDataListener() {

            /**
             * model层下载完成回调此函数,将数据传过来,这里注意的是这个方法是在model层被调用了,
             * 而我们是在子线程中下载数据,这个方法也是在子线程中被回调,所以更新ui需要注意
             * @param data
             */
            @Override
            public void complete(final String data) {

                ((Activity) mIView).runOnUiThread(new Runnable() {
                    @Override
                    public void run() {
                        mIView.showData(data);
                    }
                });
            }
        });
    }
}

运行结果:

至此我们算是完成了mvp式的demo,那么这样子就是最完美的吗?

问题分析

1

我们在上面的分析中说到在presenter中是持有View ,Model的实例的,而Model层通过回调函数通知presenter层下载数据的情况,而这个回调函数所在的接口就是由presenter其内部实现的,也就是说这个接口实例中持有着presenter的实例


那么相当于在model层中拥有着view层的实例(间接),也就是在拥有activity的实例,并且在这里还在子线程中持有,那么就会导致一个常见的内存泄漏问题

解决
把在presenter层中的Iview实例换成若引用

public class PresenterImp implements IPresenter {


    //拥有一个model 的实例,我们可以直接new 一个
    IModel mIModel = new ModelImp();

    //那么拥有一个view的实例怎么写?直接把activity传过来?
    //显然是不合适的,我们拿到这个view的实例只是为了提示下载时候的一些信息,是不是,下载的时候告诉用户开始下载了
    //所以我们就只需要一个Iview的接口的实例就可以了,并且view(activity)已经实现了这个接口的

    WeakReference<IView> mIViewWeakReference;

    public PresenterImp(IView iView) {
        mIViewWeakReference = new WeakReference<>(iView);
    }

    @Override
    public void getData() {

        IView iView = mIViewWeakReference.get();
        if (null != iView) {
            //提示用户下载开始了
            iView.showDowning();
        }

        //开始下载
        mIModel.loadData(new IModel.LoadDataListener() {

            /**
             * model层下载完成回调此函数,将数据传过来,这里注意的是这个方法是在model层被调用了,
             * 而我们是在子线程中下载数据,这个方法也是在子线程中被回调,所以更新ui需要注意
             * @param data
             */
            @Override
            public void complete(final String data) {

                IView iView = mIViewWeakReference.get();

                if (null != iView) {

                    ((Activity) iView).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {

                            IView iView = mIViewWeakReference.get();
                            if (null != iView) {
                                iView.showData(data);
                            }
                        }
                    });
                }
            }
        });
    }
}

但是这样就真的没事了嘛?那么还有什么问题?

2

比如说,加载的时间是10s,而用户在5s的时候退出当前的activity,虽然在presenter层中是弱引用,但是对于系统而言,即使是这样子,系统也不一定在model层调用complete函数的之前把这个activity回收掉,也就是说,我依然会执行iView.showData(),单数对于activity来说,它已经是destroy了,这样子也是会出现一些问题
那么现在只需在activity的onDestroy方法中处理下在presenter中的弱引用就可以了

public class MainActivity extends AppCompatActivity implements IView {

    private TextView mTextView;
    private Button mButton;
    private IPresenter mIPresenter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();

    }

    private void init() {
        mTextView = (TextView) this.findViewById(R.id.content_view);
        mButton = (Button) this.findViewById(R.id.download_data_btn);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                //拥有一个presenter的实例,将一个Iview的实例传递过去
                //
                mIPresenter = new PresenterImp().attach(MainActivity.this);
                mIPresenter.getData();
            }
        });
    }

    @Override
    public void showDowning() {
        Toast.makeText(this, "下载中....", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showData(String data) {
        mTextView.setText(data);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        ((PresenterImp) mIPresenter).detach();
    }
}

在presenter中也稍微加了一些方法

public PresenterImp attach(IView iView) {
    mIViewWeakReference = new WeakReference<>(iView);
    return this;
}

public void detach(){
    if (null != mIViewWeakReference) {
        mIViewWeakReference.clear();
    }
}

那么现在就是完美的吗?

3

每一次写个activity的时候都要写这么多重复的代码,而且对于不一样的model和presenter每次也是包含着重复的代码,是否可以抽象出一个类来做一些重复的工作?
新建了一个新的project,复制了文件过来,先看总的目录


抽象出一个BaseActivity和一个BasePresenter
BaseActivity:
public abstract class BaseActivity extends AppCompatActivity implements IView {

    protected IPresenter mIPresenter;

    protected abstract IPresenter createPresenter();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        mIPresenter = createPresenter();
        mIPresenter.attach(this);
    }

    @Override
    protected void onDestroy() {
        super.onDestroy();
        mIPresenter.detach();
    }
}

在这个抽象类中,定义了IPresenter成员变量,而至于创建一个怎么样的IPresenter,就交给了继承此抽象类的activity,由它决定
并且在在这个抽象类中的onCreate方法和onDestroy方法中调用了IPresenter 的attach 和detach方法

再看看现在的MainActivity

public class MainActivity extends BaseActivity {

    private TextView mTextView;
    private Button mButton;


    @Override
    protected IPresenter createPresenter() {
//        return new WeatherPresenter();
        return new JokePresenter();
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();

    }

    private void init() {
        mTextView = (TextView) this.findViewById(R.id.content_view);
        mButton = (Button) this.findViewById(R.id.download_data_btn);

        mButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                mIPresenter.getData();
            }
        });
    }

    @Override
    public void showDowning() {
        Toast.makeText(this, "下载中....", Toast.LENGTH_SHORT).show();
    }

    @Override
    public void showData(String data) {
        mTextView.setText(data);
    }

}

BasePresenter:

public abstract class BasePresenter<T> implements IPresenter {


    protected WeakReference<IView> mIViewWeakReference;
    protected IModel<T> mIModel;

    public BasePresenter() {
        mIModel = createModel();
    }

    protected abstract IModel createModel();

    @Override
    public void attach(IView iView) {
        mIViewWeakReference = new WeakReference<IView>(iView);
    }

    @Override
    public void detach() {
        if (null != mIViewWeakReference) {
            mIViewWeakReference.clear();
        }
    }
}

在这个抽象类中依旧拥有IView的弱引用和Imodel的实例,而对于IModel具体是哪个也交给了抽象方法createModel,由子类自己具体实现。

至于泛型T,则是需要根据下载数据的java bean ,像目录结构图中的WeatherPresenter和JokePresenter,对应着不一样的Presenter,那么由model层返回的也就是对应的bean

attach和detach就是用于关联IView和解绑IView

现在的WeatherPresenter:

public class WeatherPresenter extends BasePresenter<WeatherData> {

    @Override
    protected IModel createModel() {
        return new WeatherModel();
    }

    @Override
    public void getData() {

        IView iView = mIViewWeakReference.get();
        if (null != iView) {
            //提示用户下载开始了
            iView.showDowning();
        }

        //开始下载
        mIModel.loadData(new LoadDataListener<WeatherData>() {

            @Override
            public void complete(final WeatherData data) {
                IView iView = mIViewWeakReference.get();

                if (null != iView) {

                    ((Activity) iView).runOnUiThread(new Runnable() {
                        @Override
                        public void run() {

                            IView iView = mIViewWeakReference.get();
                            if (null != iView) {

                                iView.showData(data.getStatus());
                            }
                        }
                    });
                }
            }


        });

    }
}

Imodel则是加上了泛型

public interface IModel<T> {

    void loadData(LoadDataListener<T> loadDataListener);


    interface LoadDataListener<T> {

        void complete(T data);

    }


}

对应的WeatherModel

public class WeatherModel<T> implements IModel<T> {

    private static final String TAG = "WeatherModel";
    private final String URL = "http://tj.nineton.cn/Heart/index/future24h/?city=CHSH000000&language=zh-chs&key=36bdd59658111bc23ff2bf9aaf6e345c";
    
    @Override
    public void loadData(final IModel.LoadDataListener loadDataListener) {

        new Thread(new Runnable() {
            @Override
            public void run() {

                try {
                    URL url = new URL(URL);

                    URLConnection urlConnection = url.openConnection();
                    InputStream inputStream = urlConnection.getInputStream();

                    StringBuilder builder = new StringBuilder();

                    byte[] bytes = new byte[1024];
                    int volumn = -1;
                    while ((volumn = inputStream.read(bytes)) != -1) {

                        builder.append(new String(bytes, 0, volumn));
                    }

                    String s = builder.toString();

                    Log.d(TAG, "run: " + s);

                    Gson gson = new Gson();

                    WeatherData weatherData = gson.fromJson(s, WeatherData.class);

                    inputStream.close();

                    loadDataListener.complete(weatherData);
                } catch (MalformedURLException e) {
                    e.printStackTrace();
                } catch (IOException e) {
                    e.printStackTrace();
                }
            }
        }).start();
    }
}


MVP1地址:https://github.com/lijinxiong/MVP1
MVP2地址:https://github.com/lijinxiong/MVP2


相关文章

  • Android MVP

    Android MVP初探 Android MVP进阶 Android MVP高级 Android MVP扩展

  • MVP / RxJava / Retrofit / RxBus

    MVP Android MVP 详解(上) Android MVP 详解(下) Android中的MVP模式,带实...

  • 一套完整的Android通用框架

    转自吴小龙同学的博客 MVP模式 MVP简介 Android MVP Sample,MVP+Retrofit+Rx...

  • MVP简单尝试

    MVP模式解析 标签: Android 架构 MVP MVP模式的核心思想 MVP将Activity中的U...

  • MVP基础架构

    MVP 是什么 基础架构 登录例子 MVP的优缺点 一、MVP 是什么 MVP全名是 Model - View -...

  • android 安卓 mvp mvvm - mvp

    android 安卓 mvp mvvm - mvpandroid 安卓 mvp mvvm - mvvm MVP M...

  • MVP架构

    目录 1)MVP简介2)MVP实例 1)MVP简介 MVP模式将Activity中的业务逻辑全部剥离出来,Acti...

  • MVC和MVP

    Android mvp 架构的自述 如何更高效的使用MVP以及官方MVP架构解析 老的MVC架构 新的MVP架构 ...

  • MVP系列-Android平台-第1讲-初探MVP

    MVP系列-Android平台-第1讲-初探MVP 内容一:什么是MVP?什么是MVC? 第一点:什么是MVP? ...

  • MVP框架学习

    一、MVP介绍 二、为什么使用MVP模式 三、MVP与MVC的异同 四、使用MVP实现Android的登录的Dem...

网友评论

    本文标题:MVP

    本文链接:https://www.haomeiwen.com/subject/fqnmxxtx.html