MVP入门
2016-05-31 16:37
381 查看
基础
官方demomvc中,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()。这就是观察者模式,同时也保证了数据的加载永远走在子线程中。
相关文章推荐
- SQLite数据类型
- oracle表空间
- Android学习笔记六十:无线 WIFI 的13个信道频率范围
- 文件存储
- 第十四周项目1.2—排序函数模板(选择法排序)
- MUPDF的代码完全解析
- JSP之WEB服务器:Apache与Tomcat的区别 ,几种常见的web/应用服务器
- 课后作业(最低价格买书)
- cmd命令行黑窗口命令
- JavaScript知识点总结(十一)之js中的Object类详解
- 孙孙啊i之项目实战(六) 第三方登陆
- C++作业6
- git 创建远程仓库并将本地文件上传到远程仓库
- chrome浏览器访问本地json格式
- Web Service 接口
- 浏览器工具
- Swap file ".Podfile.swp" already exists!
- iOS开发---业务逻辑
- 7. myeclipse10反编译插件安装
- vim与windows/linux之间的复制粘贴小结