android MVP示例代码分析(android,mvp,移动开发)

时间:2024-05-09 09:13:36 作者 : 石家庄SEO 分类 : 移动开发
  • TAG :

上项目结构图:

android MVP示例代码分析

从包名上很容易分辨出功能:addedittask是添加任务,data是数据管理,statistics是统计,taskdetail是任务详情,tasks是任务浏览之类的。事实上这个项目的关键也就是: Tasks 、 TaskDetail 、 AddEditTask 、 Statistics 。

这四个关键的地方都有相同之处:

  • 定义了view和presenter的契约

  • Activity负责fragment和presenter的创建

  • Fragment实现了view接口

  • presenter实现了presenter接口

也就是说,几个功能每一个都是MVP的模式,只不过Model层是公用的。而且这个项目里View层都是Fragment,果然google推荐用Fragment自己的项目里也给我们做个示范……其实关于到底是不是要用Fragment,还是有些争议的,那么到底要不要用呢?我觉得对于个体而言,不管你喜不喜欢,都要用一用,试一试,因为人要成长,必须踩坑。对于正式项目而言,则需要综合考量,使用Fragment的利是否大于弊。

扯远了,接下来看一下他代码仓库给的一张结构图:

android MVP示例代码分析

可以看出来左边是数据管理,典型的Model层。而右边呢,你可能认为Activity是Presenter,事实上并不是,Presenter在Activity内,Fragment是View无疑。到这,我觉得关于这个项目结构的简介已经足够了,接下来看代码。

我觉得看一个Android项目的正确姿势应该是先把玩一下app,看一下功能。贴几张app的图:

android MVP示例代码分析

android MVP示例代码分析

android MVP示例代码分析

android MVP示例代码分析

接着就该上入口的Activity看一下了,这个项目的入口Activity是TasksActivity,所在的包是tasks,看一下有哪些东西:

android MVP示例代码分析

***个是自定义View,第二个就是入口Activity了,第三个即上面所说的“契约”,里面包含了View接口和Presenter接口。TasksFilterType则是一个枚举,里面有三个过滤类型:所有,进行中的,完成的。TasksFragment就是MVP中的View了,TasksPresenter则是MVP中的Presenter了。看一下TasksActivity中的初始化代码:

protectedvoidonCreate(BundlesavedInstanceState){super.onCreate(savedInstanceState);setContentView(R.layout.tasks_act);Log.e(getClass().getSimpleName(),"onCreate");//Setupthetoolbar.Toolbartoolbar=(Toolbar)findViewById(R.id.toolbar);setSupportActionBar(toolbar);ActionBarab=getSupportActionBar();ab.setHomeAsUpIndicator(R.drawable.ic_menu);ab.setDisplayHomeAsUpEnabled(true);/***以下的DrawerLayout暂时不看了*///Setupthenavigationdrawer.mDrawerLayout=(DrawerLayout)findViewById(R.id.drawer_layout);mDrawerLayout.setStatusBarBackground(R.color.colorPrimaryDark);NavigationViewnavigationView=(NavigationView)findViewById(R.id.nav_view);if(navigationView!=null){setupDrawerContent(navigationView);}//获取fragment并将之添加到视图上//悬浮按钮在这个taksFragment里设置的点击事件TasksFragmenttasksFragment=(TasksFragment)getSupportFragmentManager().findFragmentById(R.id.contentFrame);//getSupportFragmentManager().findFragmentById()if(tasksFragment==null){//CreatethefragmenttasksFragment=TasksFragment.newInstance();//提供方法帮助activity加载ui//这个方法其实就是拿到一个事务,然后把这个fragmentadd到对应的id上了ActivityUtils.addFragmentToActivity(getSupportFragmentManager(),tasksFragment,R.id.contentFrame);}//CreatethepresentermTasksPresenter=newTasksPresenter(Injection.provideTasksRepository(getApplicationContext()),tasksFragment);//Loadpreviouslysavedstate,ifavailable.if(savedInstanceState!=null){TasksFilterTypecurrentFiltering=(TasksFilterType)savedInstanceState.getSerializable(CURRENT_FILTERING_KEY);mTasksPresenter.setFiltering(currentFiltering);}}

首先是初始化toolbar和侧滑,这里不必深入细节,可以跳过这俩。之后初始化fragment和presenter,初始化Fragment先是尝试通过id寻找可能已经存在的Fragment对象,如果没有,则重新创建一个Fragment对象。下一步则是创建一个presenter,***则是让应用在横竖屏状态切换的情况下恢复数据。

接下来看一下View和Presenter的“契约”:

publicinterfaceTasksContract{interfaceViewextendsBaseView<Presenter>{voidsetLoadingIndicator(booleanactive);voidshowTasks(List<Task>tasks);voidshowAddTask();voidshowTaskDetailsUi(StringtaskId);voidshowTaskMarkedComplete();voidshowTaskMarkedActive();voidshowCompletedTasksCleared();voidshowLoadingTasksError();voidshowNoTasks();voidshowActiveFilterLabel();voidshowCompletedFilterLabel();voidshowAllFilterLabel();voidshowNoActiveTasks();voidshowNoCompletedTasks();voidshowSuccessfullySavedMessage();booleanisActive();voidshowFilteringPopUpMenu();}interfacePresenterextendsBasePresenter{voidresult(intrequestCode,intresultCode);voidloadTasks(booleanforceUpdate);voidaddNewTask();voidopenTaskDetails(@NonNullTaskrequestedTask);voidcompleteTask(@NonNullTaskcompletedTask);voidactivateTask(@NonNullTaskactiveTask);voidclearCompletedTasks();voidsetFiltering(TasksFilterTyperequestType);TasksFilterTypegetFiltering();}}

这个接口里包含了View和Presenter,可以看到View和Presenter里的方法比较多,事实上这是应该的。因为在MVP架构里,View只负责根据Presenter的指示绘制UI,View将所有的用户交互交给Presenter处理。所以Presenter的很多方法可能就是对用户的输入的处理,而有输入必然有输出,View接口定义的各个方法便是给Presenter回调的。Presenter通过回调函数将对用户的输入的处理结果推到View中,View再根据这个结果对UI进行相应的更新。而在此项目中,Fragment就是View,在Fragment的各个点击事件中都调用了Presenter的对应方法,将业务逻辑交给Presenter处理。这看起来比传统的MVC强上很多,因为传统MVC中Activity既可以认为是Controller亦可以认为是View,职责难以分离,写到后面可能一个Activity就有上千行的代码,这会为后续的维护带来不少麻烦。而MVP则将业务逻辑抽取到了Presenter中,作为View的Fragment或者Activity职责更加单一,无疑为后续的开发维护带来了便利。

接下来详细的看Presenter的初始化,Presenter的创建是在TasksActivity中完成的,查看其构造函数:

publicTasksPresenter(@NonNullTasksRepositorytasksRepository,@NonNullTasksContract.ViewtasksView){mTasksRepository=checkNotNull(tasksRepository,"tasksRepositorycannotbenull");mTasksView=checkNotNull(tasksView,"tasksViewcannotbenull!");mTasksView.setPresenter(this);}

前两个检查传入的参数是否为空,接着将其赋值给TasksPresenter内的引用,调用view的setPresenter方法,将自身传入,这样view中就可以使用presenter对象了,比直接从activity中拿看起来要优雅了不少。Presenter具体的逻辑就不看了,都是一些比较简单的代码,回顾一下打开这个app所发生的事件的流程:创建TasksActivity -> 初始化Toolbar -> 初始化侧滑 -> 创建TasksFragment对象 -> 创建TaskPresenter对象 -> 给Fragment设置Presenter对象 -> 初始化Fragment布局,这样一套流程下来,整个流程就理清了,接下来只是等待用户的输入了。

接下来要看的是从本文开始到现在都一直忽略了的Model:TasksRepository。不过在分析TasksRepository之前,安利一下这个项目里的实体类,写的比较优雅,我们平时写实体类时***也能按照他的套路来写。我为什么说他写的比较优雅呢?因为各个属性或者是带返回值的方法都打上了@Nullable或者@NoNull注解来说明是否可以为空,事实上空指针这个错可以算是平时经常遇到的错了&hellip;&hellip;不过如果你有良好的设计和编码习惯,是可以避免的,带上这两个注解可以在编译期给你相关的提示。不仅如此,这个实体类还复写了equals()、hashCode()和toString()方法,而且实现的方式也符合规范,关于如何复写这三个方法,在《effective java》上有很好的总结,各位可以去读一下。

/**Copyright2016,TheAndroidOpenSourceProject**LicensedundertheApacheLicense,Version2.0(the"License");*youmaynotusethisfileexceptincompliancewiththeLicense.*YoumayobtainacopyoftheLicenseat**http://www.apache.org/licenses/LICENSE-2.0**Unlessrequiredbyapplicablelaworagreedtoinwriting,software*distributedundertheLicenseisdistributedonan"ASIS"BASIS,*WITHOUTWARRANTIESORCONDITIONSOFANYKIND,eitherexpressorimplied.*SeetheLicenseforthespecificlanguagegoverningpermissionsand*limitationsundertheLicense.*/packagecom.example.android.architecture.blueprints.todoapp.data;importandroid.support.annotation.NonNull;importandroid.support.annotation.Nullable;importcom.google.common.base.Objects;importcom.google.common.base.Strings;importjava.util.UUID;/***ImmutablemodelclassforaTask.*/publicfinalclassTask{@NonNullprivatefinalStringmId;@NullableprivatefinalStringmTitle;@NullableprivatefinalStringmDescription;privatefinalbooleanmCompleted;/***UsethisconstructortocreateanewactiveTask.**@paramtitletitleofthetask*@paramdescriptiondescriptionofthetask*/publicTask(@NullableStringtitle,@NullableStringdescription){this(title,description,UUID.randomUUID().toString(),false);}/***UsethisconstructortocreateanactiveTaskiftheTaskalreadyhasanid(copyofanother*Task).**@paramtitletitleofthetask*@paramdescriptiondescriptionofthetask*@paramididofthetask*/publicTask(@NullableStringtitle,@NullableStringdescription,@NonNullStringid){this(title,description,id,false);}/***UsethisconstructortocreateanewcompletedTask.**@paramtitletitleofthetask*@paramdescriptiondescriptionofthetask*@paramcompletedtrueifthetaskiscompleted,falseifit'sactive*/publicTask(@NullableStringtitle,@NullableStringdescription,booleancompleted){this(title,description,UUID.randomUUID().toString(),completed);}/***UsethisconstructortospecifyacompletedTaskiftheTaskalreadyhasanid(copyof*anotherTask).**@paramtitletitleofthetask*@paramdescriptiondescriptionofthetask*@paramididofthetask*@paramcompletedtrueifthetaskiscompleted,falseifit'sactive*/publicTask(@NullableStringtitle,@NullableStringdescription,@NonNullStringid,booleancompleted){mId=id;mTitle=title;mDescription=description;mCompleted=completed;}@NonNullpublicStringgetId(){returnmId;}@NullablepublicStringgetTitle(){returnmTitle;}@NullablepublicStringgetTitleForList(){if(!Strings.isNullOrEmpty(mTitle)){returnmTitle;}else{returnmDescription;}}@NullablepublicStringgetDescription(){returnmDescription;}publicbooleanisCompleted(){returnmCompleted;}publicbooleanisActive(){return!mCompleted;}publicbooleanisEmpty(){returnStrings.isNullOrEmpty(mTitle)&amp;&amp;Strings.isNullOrEmpty(mDescription);}@Overridepublicbooleanequals(Objecto){if(this==o)returntrue;if(o==null||getClass()!=o.getClass())returnfalse;Tasktask=(Task)o;returnObjects.equal(mId,task.mId)&amp;&amp;Objects.equal(mTitle,task.mTitle)&amp;&amp;Objects.equal(mDescription,task.mDescription);}@OverridepublicinthashCode(){returnObjects.hashCode(mId,mTitle,mDescription);}@OverridepublicStringtoString(){return"Taskwithtitle"+mTitle;}}

先看一下TasksRepository所在的包的结构:

android MVP示例代码分析

可以从包名上看出local是从本地读取数据,remote是远程读取,当然了,这里只是模拟远程读取。本地采用了数据库存取的方式。在TasksRepository(下文简称TR)内部有两个TasksDataSource的引用:

privatefinalTasksDataSourcemTasksRemoteDataSource;privatefinalTasksDataSourcemTasksLocalDataSource;

TasksDataSource是data包内的一个接口,使用接口引用,无非是想解耦,就算以后需求变更,不想采用数据库的方式存储数据,只要实现了这个接口,TR内部的代码也无需变更。TR用了单例,实现方式并不是线程安全的:

/***Returnsthesingleinstanceofthisclass,creatingitifnecessary.**@paramtasksRemoteDataSourcethebackenddatasource*@paramtasksLocalDataSourcethedevicestoragedatasource*@returnthe{@linkTasksRepository}instance*/publicstaticTasksRepositorygetInstance(TasksDataSourcetasksRemoteDataSource,TasksDataSourcetasksLocalDataSource){if(INSTANCE==null){INSTANCE=newTasksRepository(tasksRemoteDataSource,tasksLocalDataSource);}returnINSTANCE;}

说到底,他根本没有线程安全的必要,至少在这个app里,没有并发创建这个对象的场景,所以够用就行了。在TR内部使用了一个LinkedHashMap作为容器来保存Tasks,主要看一下两个方法,首先是存储:

publicvoidsaveTask(@NonNullTasktask){checkNotNull(task);mTasksRemoteDataSource.saveTask(task);mTasksLocalDataSource.saveTask(task);//DoinmemorycacheupdatetokeeptheappUIuptodateif(mCachedTasks==null){mCachedTasks=newLinkedHashMap<>();}mCachedTasks.put(task.getId(),task);}

会将传入的task存储到远程数据源和本地数据源(本地数据库)中,然后将这个task传到mCachedTasks(LinkedHashMap)中。代码比较简单,不做更多的分析,接下来看一下读取Task:

publicvoidgetTasks(@NonNullfinalLoadTasksCallbackcallback){checkNotNull(callback);//Respondimmediatelywithcacheifavailableandnotdirtyif(mCachedTasks!=null&amp;&amp;!mCacheIsDirty){callback.onTasksLoaded(newArrayList<>(mCachedTasks.values()));return;}if(mCacheIsDirty){//Ifthecacheisdirtyweneedtofetchnewdatafromthenetwork.getTasksFromRemoteDataSource(callback);}else{//Querythelocalstorageifavailable.Ifnot,querythenetwork.mTasksLocalDataSource.getTasks(newLoadTasksCallback(){@OverridepublicvoidonTasksLoaded(List<Task>tasks){refreshCache(tasks);callback.onTasksLoaded(newArrayList<>(mCachedTasks.values()));}@OverridepublicvoidonDataNotAvailable(){getTasksFromRemoteDataSource(callback);}});}}

这个taskId是需要获取Task的id,也是唯一标识,GetTaskCallback则是负责传递数据的接口回调。首先是从内存中读取数据,getTaskWithId方法就是,看一下代码:

privateTaskgetTaskWithId(@NonNullStringid){checkNotNull(id);if(mCachedTasks==null||mCachedTasks.isEmpty()){returnnull;}else{returnmCachedTasks.get(id);}}

就从保存task的LinkedHashMap中读取数据。如果这个过程读取不到数据那么接着从本地数据源中读取数据,如果本地数据源也没有拿到这个数据,那么最终就从远程数据源中读取数据。

 </div> <div class="zixun-tj-product adv-bottom"></div> </div> </div> <div class="prve-next-news">
本文:android MVP示例代码分析的详细内容,希望对您有所帮助,信息来源于网络。
上一篇:jQuery怎么实现咖啡订单管理功能下一篇:

8 人围观 / 0 条评论 ↓快速评论↓

(必须)

(必须,保密)

阿狸1 阿狸2 阿狸3 阿狸4 阿狸5 阿狸6 阿狸7 阿狸8 阿狸9 阿狸10 阿狸11 阿狸12 阿狸13 阿狸14 阿狸15 阿狸16 阿狸17 阿狸18