一、前言
最近在看architecture,它提供许多例子,首先是一个非常基础的例子,
todo-mvp,一个没有使用其他框架的一个基础的Model-View-Presenter(MVP)架构,起到一个参考和其他例子的对比作用。
这个例子,用到以下类库:
common android support libraries (包名在com.android.support.*下,提供向后兼容以及其他的一些功能)
android testing support libraries (UI测试的框架,Espresso和AndroidJUnitRunner)
mockito (一个通过模拟对象来实现对单元测试的框架)
guava (一套谷歌的java核心类库,通常用于android app)
该例子包括其他例子,都只包含四个页面:
Tasks — 用于管理任务列表
TaskDetail — 查看任务详情或删除任务
AddEditTask — 新建或编辑任务
Statistics — 显示任务相关的统计信息
每一个页面都是由以下的类和接口来实现
contract 定义View和Presenter接口的联系。(自我理解分析:在项目的包名目录下,首先会定义了一个Base的Presenter和View接口,BasePresenter接口,定义了一个start方法,这个方法,在每个页面视图层的onResume生命周期中,都会调用,起到一个页面初始化的作用,所以提取出来在BasePresenter,以便在每个页面的实现中去实现相应的功能。而BaseView中定义了一个setPresenter方法,传入的参数是一个泛型,这个方法比较重要,在Presenter的构造方法中调用,使得View视图层持有Presenter对象,而Presenter的构造方法需传入Fragment参数,使得Presenter同样也包含了View的引用。而每个页面的contract,定义了/该页面需要对应方法的/继承了Base的/View和Presenter
接口
)
Activity 创建fragments和presenters。(项目用Fragment作为View的实现,有two reasons:1.通过activities和fragments分离更适合MVP架构的实现,当前这个例子中,Activity是一个全局的控制器,用于创建和联系起View和Presenter。2.Fragment更有利于平板布局或多视图页面)
Fragment 实现view接口
presenter 实现相应contract中的presenter接口
presenter通常调用Model模型层实现数据存储与业务逻辑,然后调用View来更新界面UI。视图层View几乎没有业务逻辑的处理,它只是将presenter转化成相应的UI,和监听用户的操作传递给presenter。
architecture项目下的每个不同例子,都是通过不同的方法来实现相同点功能,以此来展现和对比多种架构设计。例如,todo-mvp采用以下方法来解决常见的问题:
当前示例使用product flavors在编译时替换不同模块,为手动和自动测试提供虚假数据。
使用回调函数处理异步任务。
二、代码分析
上面扯了非常多有的没的,只是大致的了解下说明,可以直接去看官网的说明,下面还是具体看看代码。看到第一个页面TaskActivity,先了解下页面布局。
一个DrawLayout,左边是侧滑菜单栏,右边是一个Toolbar加一个主体frameLayout,和一个浮动按钮。
我一般首先会看TaskActivity的代码:
onCreate
Set up the toolbar
设置了toolbar,弄了个图标,然后setupDrawerContent(navigationView)设置左边导航栏点菜单事件,点第一个啥也不发生,第二个跳转到StatisticsActivity,Flags还是FLAG_ACTIVITY_CLEAR_TASK,其他点Activity全部清空。
Create the fragment
通过findFragmentById获取Fragment,从容器找片段,然后是一个非空的判断,如果系统内存不足、或者切换横竖屏、或者app长时间在后台运行,Activtiy都有可能会被系统回收,然后Fragment并不会随着Activity点回收而被回收,从而导致,Fragment丢失对应的Activity。判断如果为NULL则创建新的Fragment,如果不为NULL则不需要重新创建。创建Fragment加入时使用了ActivityUtils工具类,并且传入了id,一方面告知DragmentManager,此fragment的位置,另一方面也是此fragment的唯一标识,上面就是通过id对fragment进行查找。
Create the presenter
创建一个presenter,传入的参数是TasksRepository,和Fragment,前面说过,这样presenter就持有了model模型层和视图层了,并且再TasksPresenter构造方法中调用了setPresenter方法,视图层也持有presenter。
至于这个TasksRespository中的数据存储,采用了三级缓存来管理,稍后分析。
Load previously saved state,if available
如果savedInstanceState不为NULL,则恢复设置成之前的Task类型,理所当然这个当前Task类型是在onSaveInstaceState生命周期中存进去的。
TasksRespository
前面说到presenter持有model模型层,model负责业务逻辑和数据存储,由TasksRepository来实现。
TasksRespository采用单例模式来创建,传入点参数是远程数据类和本地数据类,TasksRepository持有本地和远程两个数据类,这三个都实现了DataSource接口,里面定义了涉及到的相关数据存储的方法,简单的看一下TasksRepository里面的实现方法:
destroyInstance
INSTANCE=null;销毁,当前对象指向null
getTasks
获取Tasks,参数LoadTaskCallback,在presenter中使用时创建,返回结果回调处理。
//Respond immediately with cache if available and not dirty
如果缓存不为null并且缓存标识false,直接将缓存数据回调。
mCacheIsDirty缓存的一个无效标记,可以控制true来进行强制刷新从远程获取数据,调用
getTasksFromRemoteDataSource方法,将LoadTaskCallback回调参数传入,调用
TasksRemoteDataSource的getTasks的实现方法,创建一个Handler对象执行一个延时操作,模拟网络请求
讲数据添加到集合返回,然后刷新缓存和本地数据库数据,最后回调。
当缓存无效的标识为false,先调用TasksLocalDataSource的getTasks方法,从本地数据库去查询数据,成功
时刷新缓存并且返回数据,失败时从远程网络请求数据。
saveTask
分别调用TasksRemoteDataSource和TasksLocalDataSource的saveTask方法将数据保存到本地数据库和
服务器上,并且更新本地缓存数据。
completeTask(Task task)
完成任务也是分别调用TaskRemoteDataSource和TasksLocalDataSource的completeTask来更新数据和远
程的数据,最后更新缓存数据。
activateTask(和上面差不离其)
clearCompletedTasks
也差不多,分别从本地数据库远程和本地缓存移除已完成任务。
getTask
思路也就是先从缓存取,缓存木有从本地数据库取,数据库木有就从远端网络请求。
refreshTasks
缓存无效标识设置为true
..............................................剩下差不多都大同小异,没啥好说的了。
Presenter
model层通过TasksRepository来实现数据存储和业务逻辑,那么再来看看关键的Presenter。
start
basePresenter里面每个页面必须实现的方法,在视图层的onResume生命周期中调用,用于加载数据。
loadTasks(false);forceUpdate强制更新标识,由2个参数决定,另一个是第一次加载必须强制
从网络请求更新。
继续调用loadTasks重载方法,梳理下这个方法整个处理流程。
首先判断第二个参数是否UI显示等待,然后强制更新标识如果为true,调用TasksRepository
设置缓存标识无效。
调用getTasks方法获取数据,获取成功时根据筛选类型添加到list,
判断View是否有效fragment是否添加在
activity,UI取消加载等待,最后显示Tasks。
addNewTask
当View没有任务列表显示时,会显示无任务的UI,中间有个添加新任务按钮,点击事件,调用presenter的
addNewTask方法,然后在presenter中来调用fragment中的showAddTask而不是点击直接调用,这个思想
符合了视图层只负责显示,而不负责逻辑的处理和判断。
openTaskDetails 同上
completeTask
将任务标记成完成状态,调用TasksRepository的completeTask方法,然后给个提示消息,最后更新UI。
.........................................其他没啥好说的了。
View
TaskFragment
1.单例创建方法(空构造方法应该是写错了吧private)
2.onCreate初始化一个adapter,传入了一个List和事件监听,用于响应对应的事件
3.onResume,调用Presenter加载数据
4.setPresenter在Presenter初始化时调用,用以view持有presenter
5.onActivityResult,跳转返回,调用Presenter在Presenter中进行逻辑处理
其他感觉也差不离其了,无非就是事件交给Presenter处理........................
自动测试
例子还有一块自动化测试,没有接触过,记录一下,以防后用。
Espresso自动化测试
首先一般测试,配置
android{
defaultConfig{
testInstrumentationRunner 'android.support.test.runner.AndroidJUnitRunner'
}
}
dependencies{
androidTestCompile "com.android.support:support-annotations:24.1.1"(注解库)
androidTestCompile "com.android.support.test:runner:0.5"(一个非捆绑的测试运行库)
androidTestCompile "com.android.support.test:rules:0.5"(一套使用在AndroidJunitRunner的规则库)
androidTestCompile "com.android.support.test.espresso:espresso-core:2.2.2"(UI测试的核心库)
}
数据加载类的延时操作需要使用到Espresso的IdlingReaource
需要添加依赖compile "com.android.support.test.espresso:espresso-idling-resource:2.2.2"
SimpleCountingIdlingResource
实现IdlingResource接口,一个参数为名字的构造器,必须重写三个方法
getName
isIdleNow定义了一个AtomicInteger类,判断是否等于0
registerIdleTransitionCallback注册等于0时回调
increment 获取当前的值并自增
decrement 先自减后获取值,等于0时回调
EspressoIdlingResource
封装一下SimpleCountingIdlingResource
在耗时操作中loadTasks中开始时EspressoIdlingResource.increment();
结束时EspressoIdlingResource.decrement();
然后编写测试类,brefor register After unregister
三、总结
详细看了tasks这一个页面,另外详情和新增编辑这些页面也都以相同方式构建,比较好理解了。大概总结一下:
1.本例使用Fragment作为View的实现
2.定义了BaseView(setPresenter来使Presenter持有View)和BasePresenter(start初始化加载数据)抽取了需共同
实现的方法,并在对应的页面模块创建Contract类,定义对应模块需要实现相应功能的方法接口。
3.Presenter通过初始化方法传入TasksRepository和Fragment持有View和Model
最后放一张官网的图以供理解: