android-architecture学习——todo‑mvp

来源:互联网 发布:js正则匹配指定字符串 编辑:程序博客网 时间:2024/05/18 13:10

 一、前言

        最近在看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
       最后放一张官网的图以供理解:
       
           
0 0