天天看点

App开发架构指南(谷歌官方文档译文)

这篇文章面向的是已经掌握app开发基本知识,想知道如何开发健壮app的读者。

注:本指南假设读者对 android framework 已经很熟悉。如果你还是app开发的新手,请查看 getting started 系列教程,该教程涵盖了本指南的预备知识。

app开发者面临的常见问题

跟传统的桌面应用开发不同,android app的架构要复杂得多。一个典型的android app是由多个app组件构成的,包括activity,fragment,service,content provider以及broadcast receiver。而传统的桌面应用往往在一个庞大的单一的进程中就完成了。

大多数的app组件都声明在app manifest中,android os用它来决定如何将你的app与设备整合形成统一的用户体验。虽然就如刚说的,桌面app只运行一个进程,但是一个优秀的android app却需要更加灵活,因为用户操作在不同app之间,不断的切换流程和任务。

比如,当你要在自己最喜欢的社交网络app中分享一张照片的时候,你可以想象一下会发生什么。app触发一个camera intent,然后android os启动一个camera app来处理这一动作。此时用户已经离开了社交网络的app,但是用户的操作体验却是无缝对接的。而 camera app反过来也可能触发另一个intent,比如启动一个文件选择器,这可能会再次打开另一个app。最后用户回到社交网络app并分享照片。在这期间的任意时刻用户都可被电话打断,打完电话之后继续回来分享照片。

在android中,这种app并行操作的行为是很常见的,因此你的app必须正确处理这些流程。还要记住移动设备的资源是有限的,因此任何时候操作系统都有可能杀死某些app,为新运行的app腾出空间。

总的来说就是,你的app组件可能是单独启动并且是无序的,而且在任何时候都有可能被系统或者用户销毁。因为app组件生命的短暂性以及生命周期的不可控制性,任何数据都不应该把存放在app组件中,同时app组件之间也不应该相互依赖。

通用的架构准则

如果app组件不能存放数据和状态,那么app还是可架构的吗?

最重要的一个原则就是尽量在app中做到separation of concerns(关注点分离)。常见的错误就是把所有代码都写在activity或者fragment中。任何跟ui和系统交互无关的事情都不应该放在这些类当中。尽可能让它们保持简单轻量可以避免很多生命周期方面的问题。别忘了能并不拥有这些类,它们只是连接app和操作系统的桥梁。根据用户的操作和其它因素,比如低内存,android os可能在任何时候销毁它们。为了提供可靠的用户体验,最好把对它们的依赖最小化。

第二个很重要的准则是用。之所以要持久化是基于两个原因:如果os销毁app释放资源,用户数据不会丢失;当网络很差或者断网的时候app可以继续工作。model是负责app数据处理的组件。它们不依赖于view或者app 组件(activity,fragment等),因此它们不会受那些组件的生命周期的影响。保持ui代码的简单,于业务逻辑分离可以让它更易管理。

app架构推荐

在这一小节中,我们将通过一个用例演示如何使用architecture component构建一个app。

注:没有一种适合所有场景的app编写方式。也就是说,这里推荐的架构适合作为大多数用户案例的开端。但是如果你已经有了一种好的架构,没有必要再去修改。

假设我们在创建一个显示用户简介的ui。用户信息取自我们自己的私有的后端rest api。

创建用户界面

ui由userprofilefragment.java以及相应的布局文件user_profile_layout.xml组成。

要驱动ui,我们的data model需要持有两个数据元素。

user id: 用户的身份识别。最好使用fragment argument来传递这个数据。如果os杀死了你的进程,这个数据可以被保存下来,所以app再次启动的时候id仍是可用的。

user object: 一个持有用户信息数据的pojo对象。

我们将创建一个继承viewmodel类的userprofileviewmodel来保存这一信息。

一个viewmodel为特定的ui组件提供数据,比如fragment 或者 activity,并负责和数据处理的业务逻辑部分通信,比如调用其它组件加载数据或者转发用户的修改。viewmodel并不知道view的存在,也不会被configuration change影响。

现在我们有了三个文件。

user_profile.xml: 定义页面的ui

userprofileviewmodel.java: 为ui准备数据的类

userprofilefragment.java: 显示viewmodel中的数据与响应用户交互的控制器

下面我们开始实现(为简单起见,省略了布局文件):

public class userprofileviewmodel extends viewmodel { 

    private string userid; 

    private user user; 

    public void init(string userid) { 

        this.userid = userid; 

    } 

    public user getuser() { 

        return user; 

public class userprofilefragment extends lifecyclefragment { 

    private static final string uid_key = "uid"; 

    private userprofileviewmodel viewmodel; 

    @override 

    public void onactivitycreated(@nullable bundle savedinstancestate) { 

        super.onactivitycreated(savedinstancestate); 

        string userid = getarguments().getstring(uid_key); 

        viewmodel = viewmodelproviders.of(this).get(userprofileviewmodel.class); 

        viewmodel.init(userid); 

    public view oncreateview(layoutinflater inflater, 

                @nullable viewgroup container, @nullable bundle savedinstancestate) { 

        return inflater.inflate(r.layout.user_profile, container, false); 

注:上面的例子中继承的是lifecyclefragment而不是fragment类。等architecture component中的lifecycles api稳定之后,android support library中的fragment类也将实现lifecycleowner。

现在我们有了这些代码模块,如何连接它们呢?毕竟当viewmodel的user成员设置之后,我们还需要把它显示到界面上。这就要用到livedata了。

livedata是一个可观察的数据持有者。 无需明确在它与app组件之间创建依赖就可以观察livedata对象的变化。livedata还考虑了app组件(activities, fragments, services)的生命周期状态,做了防止对象泄漏的事情。

注:如果你已经在使用rxjava或者agera这样的库,你可以继续使用它们,而不使用livedata。但是使用它们的时候要确保正确的处理生命周期的问题,与之相关的lifecycleowner stopped的时候数据流要停止,lifecycleowner destroyed的时候数据流也要销毁。你也可以使用android.arch.lifecycle:reactivestreams让livedata和其它的响应式数据流库一起使用(比如, rxjava2)。

现在我们把userprofileviewmodel中的user成员替换成livedata,这样当数据发生变化的时候fragment就会接到通知。livedata的妙处在于它是有生命周期意识的,当它不再被需要的时候会自动清理引用。

    ... 

    private livedata<user> user; 

    public livedata<user> getuser() { 

现在我们修改userprofilefragment,让它观察数据并更新ui。

@override 

public void onactivitycreated(@nullable bundle savedinstancestate) { 

    super.onactivitycreated(savedinstancestate); 

    viewmodel.getuser().observe(this, user -> { 

      // update ui 

    }); 

每当user数据更新的时候 onchanged 回调将被触发,然后刷新ui。

如果你熟悉其它library的observable callback的用法,你会意识到我们不需要重写fragment的onstop()方法停止对数据的观察。因为livedata是有生命周期意识的,也就是说除非fragment处于活动状态,否则callback不会触发。livedata还可以在fragmentondestroy()的时候自动移除observer。

对我们也没有做任何特殊的操作来处理 configuration changes(比如旋转屏幕)。viewmodel可以在configuration change的时候自动保存下来,一旦新的fragment进入生命周期,它将收到相同的viewmodel实例,并且携带当前数据的callback将立即被调用。这就是为什么viewmodel不应该直接引用任何view,它们游离在view的生命周期之外。参见viewmodel的生命周期。

获取数据

现在我们把viewmodel和fragment联系了起来,但是viewmodel该如何获取数据呢?在我们的例子中,假设后端提供一个rest api,我们使用retrofit从后端提取数据。你也可以使用任何其它的library来达到相同的目的。

下面是和后端交互的retrofit webservice:

public interface webservice { 

    /** 

     * @get declares an http get request 

     * @path("user") annotation on the userid parameter marks it as a 

     * replacement for the {user} placeholder in the @get path 

     */ 

    @get("/users/{user}") 

    call<user> getuser(@path("user") string userid); 

viewmodel的一个简单的实现方式是直接调用webservice获取数据,然后把它赋值给user对象。虽然这样可行,但是随着app的增大会变得难以维护。viewmodel的职责过多也违背了前面提到的关注点分离(separation of concerns)原则。另外,viewmodel的有效时间是和activity和fragment的生命周期绑定的,因此当它的生命周期结束便丢失所有数据是一种不好的用户体验。相反,我们的viewmodel将把这个工作代理给repository模块。

repository模块负责处理数据方面的操作。它们为app提供一个简洁的api。它们知道从哪里得到数据以及数据更新的时候调用什么api。你可以把它们看成是不同数据源(persistent model, web service, cache, 等等)之间的媒介。

下面的userrepository类使用了webservice来获取用户数据。

public class userrepository { 

    private webservice webservice; 

    // ... 

    public livedata<user> getuser(int userid) { 

        // this is not an optimal implementation, we'll fix it below 

        final mutablelivedata<user> data = new mutablelivedata<>(); 

        webservice.getuser(userid).enqueue(new callback<user>() { 

            @override 

            public void onresponse(call<user> call, response<user> response) { 

                // error case is left out for brevity 

                data.setvalue(response.body()); 

            } 

        }); 

        return data; 

虽然repository模块看起来没什么必要,但它其实演扮演着重要的角色;它把数据源从app中抽象出来。现在我们的viewmodel并不知道数据是由webservice提供的,意味着有必要的话可以替换成其它的实现方式。

注:为简单起见我们省略了网络错误出现的情况。实现了暴露网络错误和加载状态的版本见下面的addendum: exposing network status。

管理不同组件间的依赖:

前面的userrepository类需要webservice的实例才能完成它的工作。可以直接创建它就是了,但是为此我们还需要知道webservice所依赖的东西才能构建它。这显著的增减了代码的复杂度和偶合度(比如,每个需要webservice实例的类都需要知道如何用它的依赖去构建它)。另外,userrepository很可能不是唯一需要webservice的类。如果每个类都创建一个新的webservice,就变得很重了。

有两种模式可以解决这个问题:

依赖注入: 依赖注入允许类在无需构造依赖的情况下定义自己的依赖对象。在运行时由另一个类来负责提供这些依赖。在android app中我们推荐使用谷歌的dagger 2来实现依赖注入。dagger 2 通过遍历依赖树自动构建对象,并提供编译时的依赖。

service locator:service locator 提供一个registry,类可以从这里得到它们的依赖而不是构建它们。相对依赖注入来说要简单些,所以如果你对依赖注入不熟悉,可以使用 service locator 。

这些模式允许你扩展自己的代码,因为它们提供了清晰的模式来管理依赖,而不是不断的重复代码。两者均支持替换成mock依赖来测试,这也是使用它们主要优势之一。

在这个例子中,我们将使用 dagger 2 来管理依赖。

连接viewmodel和repository

现在我们修改userprofileviewmodel以使用repository。

    private userrepository userrepo; 

    @inject // userrepository parameter is provided by dagger 2 

    public userprofileviewmodel(userrepository userrepo) { 

        this.userrepo = userrepo; 

        if (this.user != null) { 

            // viewmodel is created per fragment so 

            // we know the userid won't change 

            return; 

        } 

        user = userrepo.getuser(userid); 

        return this.user; 

本文作者:佚名

来源:51cto

继续阅读