Android官方架构组件指南
来源:原创 时间:2017-10-18 浏览:0 次此攻略适用于那些从前或现在进行Android运用的根底开发,并期望了解和学习编写Android程序的最佳实践和架构。经过学习来构建强壮的出产等级的运用。
留意:此攻略默许你对Android开发有比较深的了解,熟知Android Framework。如果你还仅仅个Android开发新手,那么主张先学习下Android的根底知识。
Android程序员面对的问题
传统的桌面运用程序开发在大多数状况下,发动器快捷办法都有一个进口点,并作为一个单一的进程运转,但Android运用程序的结构更为杂乱。典型的Android运用程序由多个运用程序组件构成,包含Activity,Fragment,Service,ContentProvider和Broadcast Receiver。
大多数这些运用程序组件在Android操作体系运用的AndroidManifest中声明,以决议怎样将运用程序集成到设备上来为用户供给完好的体会。尽管如前所述,桌面运用程序传统上是作为一个单一的进程运转的,但正确编写的Android运用程序则需求更灵敏,由于用户经过设备上的不同运用程序织造办法,不断切换流程和使命。
举个比方,当用户在交际App上计划共享一张相片,那么Android体系就会为此发动相机来完结此次恳求。此刻用户脱离了交际App,可是这个用户体会是无缝衔接的。相机可能又会触发并发动文件办理器来挑选相片。终究回到交际App并共享相片。此外,在此进程中的任何时候,用户可能会被打电话中止,并在完结电话后再回来共享相片。
在Android中,这种运用间跳转行为很常见,因而你的运用有必要正确处理这些流程。请记住,移动设备是资源有限的,所以在任何时候,操作体系可能需求杀死一些运用来为新的运用腾出空间。
你的运用程序的一切组件都能够被独自发动或无序发动,而且在任何时候由用户或体系毁掉。由于运用程序组件是时刻短的,它们的生命周期(创立和毁掉时)不受你的操控,因而你不该该将任何运用程序数据或状况存储在运用程序组件中,而且运用程序组件不该彼此依靠。
常见的架构原理
如果你无法运用运用程序组件来存储运用程序数据和状况,应怎样构建运用程序?
在你的App开发中你应该将重心放在分层上,如果将一切的代码都写在Activity或许Fragment中,那问题就大了。任何不是处理UI或跟操作体系交互的操作不该该放在这两个类中。尽量坚持它们代码的精简,这样你能够防止许多与生命周期相关的问题。记住你并不能掌控Activity和Fragment,他们仅仅在你的App和Android体系间起了桥梁的效果。任何时候,Android体系可能会依据用户操作或其他要素(如低内存)来收回它们。最好尽量削减对他们的依靠,以供给坚实的用户体会。
还有一点比较重要的就是耐久模型驱动UI。运用耐久模型首要是由于当你的UI被收回或许在没有网络的状况下还能正常给用户展现数据。模型是用来处理运用数据的组件,它们独立于运用中的视图和四大组件。因而模型的生命周期必定和UI是别离的。坚持UI代码的整齐,会让你能更简略的办理和调整UI。让你的运用依据模型开发能够很好的办理你运用的数据并是你的运用更具测验性和持续性。
运用架构引荐
回到这篇文章的主题,来说说Android官方架构组件(一下简称架构)。一下会介绍怎样在你的运用中实践这一架构形式。
留意:不可能存在某一种架构办法能够完美合适任何场景。话虽如此,这种架构应该是大多数用例的杰出起点。如果你现已有了很好的Android运用程序架构办法,请持续坚持。
假定我们需求一个实际用户材料的UI,该用户的材料文件将运用REST API从效劳端获取。
构建用户界面
我们的这个用户界面由一个UserProfileFragment.java文件和它的布局文件user_profile_layout.xml。
为了驱动UI,数据模型需求持有下面两个数据:
User ID:用户的标识符。最好运用Fragment的参数将此信息传递到Fragment中。如果Android操作体系收回了Fragment,则会保存此信息,以便下次从头发动运用时,该ID可用。
User Object:传统的Java目标,代表用户的数据。
为此,我们新建一个承继自ViewModel的名为UserProfileViewModel的模型来持有这个数据。
ViewModel供给特定UI组件的数据,例如Activity和Fragment,并处理与数据处理事务部分的通讯,例如调用其他组件来加载数据或转发用户修正。ViewModel不了解View,而且不受UI的重建(如重由于旋转而导致的Activity的重建)的影响。
现在我们有一下三个文件:
user_profile.xml: 视图的布局文件。
UserProfileViewModel.java: 持有UI数据的模型。
UserProfileFragment.java: 用于显现数据模型中的数据并和用户进行交互。
一下是详细代码(为了简化,布局文件省掉)。
留意:上面的UserProfileFragment承继自LifeCycleFragment而不是Fragment。当Lifecycle的Api安稳后,Fragment会默许完结LifeCycleOwner。
现在,我们有三个文件,我们怎样衔接它们?究竟,当ViewModel的用户字段被设置时,我们需求一种通知UI的办法。这儿就要说到LiveData了。
LiveData是一个可调查的数据持有者。它答应运用程序中的组件调查LiveData目标持有的数据,而不会在它们之间创立显式和刚性的依靠途径。LiveData还尊重你的运用程序组件(Activity,Fragment,Service)的生命周期状况,并做正确的作业以防止内存走漏,然后你的运用程序不会耗费更多的内存。
如果你现已运用了想Rxjava活着Agrea这类第三方库,那么你能够运用它们替代LiveData,不过你需求处理好它们与组件生命周期之间的联系。
现在我们运用LiveData来替代UserProfileViewModel中的User字段。所以Fragment能够经过调查它来更新数据。LiveData值得称道的当地就在于它是生命周期感知的,当生命周期完毕是,其上的调查者会被即便整理。
然后将UserProfileFragment修正如下,调查数据并更新UI:
一旦用户数据更新,onChanged回调将被调用然后UI会被改写。
如果你了解一些运用调查者形式第三方库,你会觉得古怪,为什么没有在Fragment的onStop()办法中将调查者移除。关于LiveData来说这是没有必要的,由于它是生命周期感知的,这意味着如果UI处于不活动状况,它就不会调用调查者的回调来更新数据。而且在onDestroy后会主动移除。
我们也不需求处理任何视图重建(如屏幕旋转)。ViewModel会主动康复重建前的数据。当新的视图被创立出来后,它会接收到与之前相同的ViewModel实例,而且调查者的回调会被马上调用,更新最新的数据。这也是ViewModel为什么不能直接引证视图目标,由于它的生命周期善于视图目标。
获取数据
现在我们将视图和模型衔接起来,可是模型该怎样获取数据呢?在这个比方中,我们假定运用REST API从后台获取。我们将运用Retrofit来向后台恳求数据。
我们的retrofit类Webservice如下:
如果仅仅简略的完结,ViewModel能够直接操作Webservice来获取用户数据。尽管这样能够正常作业,但你的运用无法确保它的后续迭代。由于这样做将太多的职责让ViewModel来承当,这样就违反类之前讲到的分层准则。又由于ViewModel的生命周期是绑定在Activity和Fragment上的,所以当UI被毁掉后如果丢掉一切数据将是很差的用户体会。所以我们的ViewModel将和一个新的模块进行交互,这个模块叫Repository。
Repository模块担任处理数据。它为运用程序的其余部分供给了一个洁净的API。他知道在数据更新时从哪里获取数据和调用哪些API调用。你能够将它们视为不同数据源(耐久性模型,Web效劳,缓存等)之间的中介者。
UserRepository类如下:
尽管repository模块看上去没有必要,但他起着重要的效果。它为App的其他部分笼统出了数据源。现在我们的ViewModel并不知道数据是经过WebService来获取的,这意味着我们能够随意替换掉获取数据的完结。
办理组件间的依靠联系
上面这种写法能够看出来UserRepository需求初始化Webservice实例,这尽管说起来简略,但要完结的话还需求知道Webservice的详细结构办法该怎样写。这将加大代码的杂乱度,别的UserRepository可能并不是仅有运用Webservice的目标,所以这种在内部构建Webservice实例显然是不引荐的,下面有两种形式来处理这个问题:
依靠注入:依靠注入答应类界说它们的依靠联系而不结构它们。在运转时,另一个类担任供给这些依靠联系。我们主张在Android运用程序中运用Google的Dagger 2库完结依靠注入。Dagger 2经过遍历依靠联系树主动构建目标,并在依靠联系上供给编译时确保。
效劳定位器:效劳定位器供给了一个注册表,其间类能够获取它们的依靠联系而不是结构它们。与依靠注入(DI)比较,完结起来相对简略,因而如果您不了解DI,请改用Service Locator。
这些形式答应你扩展代码,由于它们供给清晰的形式来办理依靠联系,而不会重复代码或增加杂乱性。两者都答应交流完结进行测验;这是运用它们的首要优点之一。在这个比方中,我们将运用Dagger 2来办理依靠联系。
衔接ViewModel和Repository
现在,我们的UserProfileViewModel能够改写成这样:
缓存数据
上面的Repository尽管网络恳求做了封装,可是它依靠后台数据源,所以存在缺乏。
上面的UserRepository完结的问题是,在获取数据之后,它不会保存在任何当地。如果用户脱离UserProfileFragment偏从头进来,则运用程序将从头获取数据。这是欠好的,有两个原因:它糟蹋了名贵的网络带宽和迫运用户等候新的查询完结。为了处理这个问题,我们将向我们的UserRepository增加一个新的数据源,它将把User目标缓存在内存中。如下:
耐久化数据
在当时的完结中,如果用户旋转屏幕或脱离并回来到运用程序,现有UI将当即可见,由于Repository会从内存中检索数据。可是,如果用户脱离运用程序,并在Android操作体系杀死进程后几小时后又会怎样样?
在现在的完结中,我们将需求从网络中再次获取数据。这不仅是一个糟糕的用户体会,也是糟蹋,由于它将运用移动数据来从头获取相同的数据。你以经过缓存Web恳求来简略地处理这个问题,但它会发作新的问题。如果恳求一个朋友列表而不是单个用户,会发作什么状况?那么你的运用程序可能会显现不共同的数据,这是最令人困惑的用户体会。例如,相同的用户的数据可能会不同,由于朋友列表恳求和用户恳求能够在不同的时刻履行。你的运用需求兼并他们,以防止显现不共同的数据。
正确的处理办法是运用耐久模型。这时候Room就派上用场了。
Room是一个目标映射库,它供给本地数据耐久性和最少的样板代码。在编译时,它依据形式验证每个查询,然后过错的SQL查询会导致编译时过错,而不是运转时失利。Room笼统了运用原始SQL表和查询的一些根本完结细节。它还答应调查数据库数据(包含调集和衔接查询)的更改,经过LiveData目标揭露这些更改。
要运用Room我们首要需求运用@Entity来界说实体:
接着创立数据库类:
值得留意的是MyDatabase是一个笼统了,Room会在编译期间供给它的一个完结类。
接下来需求界说DAO:
接着在MyDatabase中增加获取上面这个DAO的办法:
这儿的load办法回来的是LiveData
现在我们能够修正UserRepository了:
这儿尽管我们将UserRepository的直接数据来历从Webservice改为本地数据库,但我们却不需求修正UserProfileViewModel或许UserProfileFragment。这就是笼统层带来的优点。这也给测验带来了便利,由于你能够供给一个虚伪的UserRepository来测验你的UserProfileViewModel。
现在,如果用户从头回到这个界面,他们会马上看到数据,由于我们现已将数据做了耐久化的保存。当然如果有用例需求,我们也可不展现太老旧的耐久化数据。
在一些用例中,比方下拉改写,如果正处于网络恳求中,那UI需求通知用户正处于网络恳求中。一个好的实践办法就是将UI与数据别离,由于UI可能由于各种原因被更新。从UI的视点来说,恳求中的数据和本地数据相似,仅仅它还没有被耐久化到数据库中。
以下有两种处理办法:
将getUser的回来值中加入网络状况。
在Repository中供给一个能够回来改写状况的办法。如果你仅仅想在用户经过下拉改写来通知用户现在的网络状况的话,那这个办法是比较合适的。
数据仅有来历
在以上实例中,数据仅有来历是数据库,这样做的优点是用户能够依据安稳的数据库数据来更新页面,而不需求处理许多的网络恳求状况。数据库有数据则运用,没有数据则等候其更新。
测验
我们之前说到分层能够个运用供给杰出的测验才能,接下来就看看我们怎样测验不同的模块。
用户界面与交互:这是仅有一个需求运用到Android UI Instrumentation test的测验模块。测验UI的最好办法就是运用Espresso结构。你能够创立Fragment然后供给一个虚伪的ViewModel。由于Fragment只跟ViewModel交互,所以虚拟一个ViewModel就足够了。
ViewModel:ViewModel能够用JUnit test进行测验。由于其不触及界面与交互。而且你只需求虚拟UserRepository即可。
UserRepository:测验UserRepository相同运用JUnit test。你能够虚拟出Webservice和DAO。你能够经过运用正确的网络恳求来恳求数据,让后将数据经过DAO写入数据库。如果数据库中有相关数据则无需进行网络恳求。
UserDao:关于DAO的测验,引荐运用instrumentation进行测验。由于此处无需UI,而且能够运用in-memory数据库来确保测验的封闭性,不会影响到磁盘上的数据库。
Webservice:坚持测验的封闭性是适当重要的,因而即便是你的Webservice测验也应防止对后端进行网络呼叫。有许多第三方库供给这方面的支撑。例如,MockWebServer是一个很棒的库,能够协助你为你的测验创立一个假的本地效劳器。
架构图
辅导准则
编程是一个构思范畴,构建Android运用程序也不破例。有多种办法来处理问题,无论是在多个Activity或Fragment之间传递数据,仍是检索长途数据并将其在本地坚持离线形式,或许是任何其他常见的场景。
尽管以下主张不是强制性的,但经历通知我们,遵从这些主张将使你的代码库从长远来看愈加强壮,可测验和可保护。
在AndroidManifest中界说的Activity,Service,Broadcast Receiver等,它们不是数据源。相反,他们仅仅用于协谐和展现数据。由于每个运用程序组件的寿数适当短,运转状况取决于用户与其设备的交互以及运转时的全体当时运转状况,所以不要将这些组件作为数据源。
你需求在运用程序的各个模块之间创立清晰界定的职责规模。例如,不要在不同的类或包之间传递用于加载网络数据的代码。相同,不要将数据缓存和数据绑定这两个职责彻底不同的放在同一个类中。
每个模块之间要竟可能少的彼此露出。不要抱有侥幸心理去揭露一个关于模块的内部完结细节的接口。你可能会在短期内获得到快捷,可是跟着代码库的开展,你将多付屡次技术性债款。
当你界说模块之间的交互时,请考虑怎样使每个模块阻隔。例如,具有用于从网络中提取数据的界说杰出的API将使得更简略测验在本地数据库中耐久存在该数据的模块。相反,如果将这两个模块的逻辑组合在一同,或许将整个代码库中的网络代码放在一同,那么测验就更难(如果不是不可能)。
你的运用程序的中心是什么让它独立出来。不要花时刻重复轮子或一次又一次地编写相同的样板代码。相反,将精力会集在使你的运用程序绝无仅有的一起,让Android架构组件和其他引荐的库来处理重复的样板代码。
坚持尽可能多的相关联的新鲜数据,以便你的运用程序在设备处于脱机形式时可用。尽管你能够享用稳定和高速衔接,但你的用户可能不会。
你的Repository应指定一个数据源作为实在的单一来历。每逢你的运用程序需求拜访这些数据时,它应该一直源于实在的单一来历。
扩展: 揭露网络状况
在上面的小结我们成心省掉了网络过错和加载状况来确保比方的简洁性。在这一小结我们演示一种运用Resource类来封装数据及其状况。以此来揭露网络状况。
下面是简略的Resource完结:
认为从网络上抓取视频的一起在UI上显现数据库的旧数据是很常见的用例,所以我们要创立一个能够在多个当地重复运用的协助类NetworkBoundResource。以下是NetworkBoundResource的决策树:
NetworkBoundResource从调查数据库开端,当第一次从数据库加载完实体后,NetworkBoundResource会查看这个效果是否满意用来展现的需求,如不满意则需求从网上从头获取。当然以上两种状况可能一起发作,你期望先将数据显现在UI上的一起去网络上恳求新数据。
如果网络恳求效果,则将效果保存到数据库,然后从头从数据库加载数据,如果网络恳求失利,则直接传递过错信息。
留意:在上面的进程中能够看到当将新数据保存到数据库后,我们从头从数据库加载数据。尽管大部分状况我们不用如此,由于数据库会为我们传递此次更新。但另一方面,依靠数据库内部的更新机制并不是我们想要的如果更新的数据与旧数据共同,则数据谷不会做出更新提示。我们也不期望直接从网络恳求中获取数据直接用于UI,由于这样违反了单一数据源的准则。
下面是NetworkBoundResource类的公共api:
留意到上面界说了两种泛型,ResultType和RequestType,由于从网络恳求回来的数据类型可能会和数据库回来的不共同。
别的留意到上面代码中的ApiResponse这个类,他是将Retroft2.Call转换成LiveData的一个简略封装。
下面是NetworkBoundResource余下部分的完结:
接着我们就能够在UserRepository中运用NetworkBoundResource了。