您的位置:首页 > 其它

MVP入门

2016-05-31 16:37 381 查看

基础

        官方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()。这就是观察者模式,同时也保证了数据的加载永远走在子线程中。

        

        

        
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: