关闭

MVP入门

600人阅读 评论(1) 收藏 举报
分类:

基础

        官方demo

        mvc中,c指的是activity等,但它同时又承担了一部分的v工作,显得混乱冗长。而mvp中,将v当作activity,新添加一层做为p,m层不变。

        对于官方的demo,整体包结构采用的是按模板划分包的。如下:


        其中util指的是一些工具类,data指的是数据层(m层),本demo中使用到bean也是定义在这里的。其余的就是一些各个任务模板。

        总结每一个任务模板可以看出:每一个模板中有activity,presenter,contract,fragment四个类以及一些额外的本模板需要的类。各类的功能如下。

整体架构如下:


分类详解

contract

        合同。定义了本模板中m与p层,p层与v层之间的接口。如下:

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 createTask(String title, String description);

        void updateTask(String title, String description);

        void populateTask();
    }
}
       当然,也可以将该类省略,将其中的View与Presenter单独生成两个接口类。只不过放在一起,有助于一次性了解各个模板之间的接口、交互。

presenter

        即p层,用于具体处理业务逻辑。也是m,v层交互的桥梁。并且,由于p的初始化并不是在v中,所以在p的构造函数中需要为v指定当前p是哪个。如下:

    public StatisticsPresenter(@NonNull TasksRepository tasksRepository,
                               @NonNull StatisticsContract.View statisticsView) {
        mTasksRepository = checkNotNull(tasksRepository, "tasksRepository cannot be null");
        mStatisticsView = checkNotNull(statisticsView, "StatisticsView cannot be null!");

        mStatisticsView.setPresenter(this);
    }

        调用了v层的setPresenter,为v层指定p。

fragment

        即v层。

activity

        本demo中,所有的v层都是由fragment实现的,而activity是为p指定m与v的地方。如:

        new StatisticsPresenter(
                Injection.provideTasksRepository(getApplicationContext()), statisticsFragment);

        从这里可以看出,p层关联的v,m都是在activity中指定的,而不是直接在fragment中直接new一个p。只不过所有的v都需要有一个setPresenter()方法,用于指定该v关联的p是哪个。这个方法的调用在p的构造函数中。

BaseView

        很简单,只有一个用来指定p的方法。这是因为所有的p都不是在v中进行的初始化,所以任何一个v都必须得具有设置P的方法。如下:

public interface BaseView<T> {

    void setPresenter(T presenter);

}

BasePresenter

        它是将所有p的共性操作都抽取出来的。如下:

public interface BasePresenter {

    void start();

}
        start()用于开始加载数据,可以发现在fragment中onResume中会调用该方法。

        其实,还应该有一个end()方法,用于将各个层的引用删除,防止出现内存泄露。

Model

        对于一个应用来说,数据的来源往往有两个部分:本地以及服务器。而p层是往往不需要关注于数据到底来源于哪种渠道,它只需要有一个m层的引用,并可通过该引用拿到v层需要的数据即可,所以这里使用了代理模式。在代理类中对数据的来源进行了分类,并可以进行缓存。两个数据来源往往具有相同的操作,因此可抽象成一个接口。具体代码结构如下:

        data包统筹数据,source包为数据来源,Task为数据的bean。local表示本地数据,remote为远程数据(服务器)。
        对于本地数据的处理,采用了数据库——Contract定义的数据库字段名,Helper为数据库的helper,localdatasource为对数据库的操作。
        TasksDataSource为数据操作的总接口,localdatasource与remotedatasource都实现了它。TasksRepository也实现了它。
        对于P层来说,它应该只持有一个引用,通过该对象可以拿到数据,而不用判断这数据是来源于网络还是本地。因此,需要将local与remote进行统合,并将统合后的对象提供给p层,同时这个对象应该与local,remote实现相同的接口——因为三者在功能上是类似的,只不过统合对象对后两者进行了包装、分发。p层使用统合对象进行获取数据,具体的数据来源应该在m层中进行
        因此,使用TasksRepository对local与remote进行了统合,p层拿到的对象就是它的对象。其内部对数据的来源进行了判断。如下:
    public void getTasks(@NonNull final LoadTasksCallback callback) {
        checkNotNull(callback);

        // Respond immediately with cache if available and not dirty
        if (mCachedTasks != null && !mCacheIsDirty) {
            callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            return;
        }

        if (mCacheIsDirty) {
            // If the cache is dirty we need to fetch new data from the network.
            getTasksFromRemoteDataSource(callback);
        } else {
            // Query the local storage if available. If not, query the network.
            mTasksLocalDataSource.getTasks(new LoadTasksCallback() {
                @Override
                public void onTasksLoaded(List<Task> tasks) {
                    refreshCache(tasks);
                    callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
                }

                @Override
                public void onDataNotAvailable() {
                    getTasksFromRemoteDataSource(callback);
                }
            });
        }
    }
    private void getTasksFromRemoteDataSource(@NonNull final LoadTasksCallback callback) {
        mTasksRemoteDataSource.getTasks(new LoadTasksCallback() {
            @Override
            public void onTasksLoaded(List<Task> tasks) {
                refreshCache(tasks);//刷新内存缓存
                refreshLocalDataSource(tasks);//刷新到数据库中
                callback.onTasksLoaded(new ArrayList<>(mCachedTasks.values()));
            }

            @Override
            public void onDataNotAvailable() {
                callback.onDataNotAvailable();
            }
        });
    }
        从上述代码可以看出,在local、remote与p层之间添加了一个缓存区。

总结

        1,使用Activity充当p,m,v直接建立联系的桥梁。使用fragment充当v。
        2,对于model层,统一类管理数据的来源,不应将获取本地还是远程的判断放在p层中。并且,为提高访问速度,可能需要对数据进行内存缓存。
        3,如果Activity的逻辑比较复杂,应使用MVP,如果比较简单可直接写在activity中。

扩展

        上述示例中,数据的加载没有考虑在子线程中操作。可以考虑在m与p之前添加一个loader,由loader子线程中通过TasksRepository加载数据。demo来源。这个demo中主要采用的是mvp+loader+观察者模式。另参考Loader
        Loader:只负责提供子线程,供加载数据的方法运行在子线程中。
        TasksRepository:为本地数据和网络数据提供代理,以及对加载到的数据提供缓存。
        开始方法应该是p的start(),这里面直接调用了LoaderManager#initLoader(),也就是启动了Loader进行数据的加载。如下:
    protected void onStartLoading() {
        // Deliver any previously loaded data immediately if available.
        if (mRepository.cachedTasksAvailable()) {
            deliverResult(mRepository.getCachedTasks());//将缓存数据给返回
        }

        // Begin monitoring the underlying data source.
        mRepository.addContentObserver(this);

        if (takeContentChanged() || !mRepository.cachedTasksAvailable()) {
            // When a change has  been delivered or the repository cache isn't available, we force
            // a load.
            forceLoad();
        }
    }
        逻辑很简单,有缓存数据就返回缓存数据,没有就forceLoad。因为继承的是AsyncTaskLoader,所以forceLoad会调用起doInBackground()。这个方法实现也很简单,直接使用mRepository.getTasks()。这就将数据的加载过程放到了子线程中。
        数据加载结束后,对数据进行分类,并通知view进行展示。略。
        在v中有刷新的操作,该操作会调用p的loadTasks(),它又会调用mRepository.refreshTasks(),该方法只是通知它里面的观察者调用onTasksChanged()方法。在上面的onStartLoading()中可以发现,loader是repository的一个观察者,所以最终会调用到loader的onTasksChanged(),它里面只是一个判断并调用forceLoad()。这就是观察者模式,同时也保证了数据的加载永远走在子线程中。

        




        



        


1
0

查看评论
* 以上用户言论只代表其个人观点,不代表CSDN网站的观点或立场
    个人资料
    • 访问:95909次
    • 积分:2670
    • 等级:
    • 排名:第13616名
    • 原创:173篇
    • 转载:0篇
    • 译文:0篇
    • 评论:1条
    最新评论
  • MVP入门

    huihuang2: 博主,写的深入浅出 ,通俗易懂,实在是一篇不错的博文