理解Android Architecture Components系列(一)

Android Architecture Components是谷歌在Google I/O 2017发布一套帮助开发者解决Android架构设计的方案。里面包含了两大块内容:

  1. 生命周期相关的Lifecycle-aware Components
  2. 数据库解决方案Room

在接下的文章里,将会分析Android Architecture Components的各个组件的功能和用法,以及这些组件背后的设计思想的简单分析。这系列文章的分享目标是对Android系统熟悉或者已经有些开发经验的人,如果有看到暂时不熟悉的类如: ViewModelLiveData等时可以先按文章的逻辑读下去,关于这些类的详细信息,留在以后的文章中讨论。首先建立起对Android Architecture Components的整体认识,然后再对细节各个击破。这一系列文章主要还是对Google官方文档Android Architecture Components的翻译和加入一点不成熟的主观理解,英文好的童鞋可以直接跳到官方文档,讲得更加清楚详细。

总体概览

Google官方对Android Architecture Components的定义是:

A new collection of libraries that help you design robust, testable, and maintainable apps.

即这是一个帮助构建稳定,易于测试和易于维护的App架构的库。

实际开发中经常遇到的一个问题是:App 的架构应该怎样设计。从最早的MVC,到现在MVP、MVVM,架构设计花样百出,可苦了功力赏浅的开发者。Google在I/O大会里也提到,开发者经常向Google提出一个问题:关于构建一个稳定的Android App架构设计有没有一个推荐的方案或者广大开发者喜闻乐见的Demo?

Google:emmmm....没有,不存在的。

这世上本没有问题,问的次数多了,也就成了问题。

终于Google坐不住了,祭出了神器Android Architecture Component。内心OS:不要说我谷爹不照顾你们,看好了,我要出招了。

开发过程中经常遇到的问题

以开发一个图片分享的应用为例。从选择照片到裁剪到最后分享照片,流程要经过好几个步骤。这些步骤会很容易被来电或者其他App的事件打断(如收到微信),并且随着系统的运行,App的Activity等系统组件可能被操作系统回收。这就意味着不能在系统组件(例如Activity)中保存App 的数据和状态并且这些系统组件间也不能出现相互依赖的状况。
说到这里你可能会想:不对啊,不是有onSaveInstance()这些方法来保存数据嘛,怎么就不行了呢?你不让我在这里保存数据,那我在哪里存?这里先按下不表,原因在以后的文章里有解释。让我们先来看看关于架构设计的一些基本原则。

设计App架构的基本原则

  • Soc原则Separation of concerns 分离关注点原则,简而言之就是要模块化,低耦合。从Android的角度来看就是不要将任何和操作UI以及和调用系统组件的无关的代码放到Activity或者Fragment中,因为系统可能因为内存紧张等原因随时杀掉Activity或者Fragment。应该将这部分代码看成是App和系统交互的中间层,像胶水一样,链接App和Android系统。这么说有点累赘了,简单的例子就是MVP。
  • Model驱动UI,最好是用持久化的数据的Model(即保存到本地的数据)。为什么是持久化的数据呢?主要有一下两点:
  1. 由于数据是持久化存储的,所以当系统因为各种原因回收资源时,并不会造成数据的丢失。
  2. 在网络条件不稳定或者网络无法连接的情况下,App仍然可以工作。
    那么Model是什么呢?如果熟悉MVP的话,很容易理解Model的概念,即MVP中的M。它是一个独立于Activity/Framgment用于保存数据的类。因为它是独立于系统组件的,因此,不受系统生命周期的的影响。

由此就会产生两个问题:

  • 如何做到Soc
  • 如何便捷、高效的持久化数据

推荐的系统架构

好了,讲了半天的理论真是枯燥的要死,让我们举个栗子吧。

以编写一个个人信息页面为例,由UserProfileFragment.java和对应的布局文件user_profile_layout.xml组成。
那么为了让这个页面能够展示个人信息,我们需要两个数据:

  • The User ID,用户的ID,用于标识唯一的用户。给Fragment传递参数的方法最好方法是使用fragment arguments,因为如果系统在一些情况下直接杀死了App对应的进程,那么通过这种方法传递的参数就会被保存,并在App下次恢复启动的时候被读取到。
  • The User object,用户个人信息对应的POJO类。即包含用户如用户名,年龄等信息的类。

和之前做法不同的是我们还要创建一个基于ViewModel的类UserProfileViewModel用于保存用户信息,而不是直接在Fragment中保存,道理已经在前面阐述过。什么是ViewModel?这里简单的做一个概念上的介绍:

A ViewModel provides the data for a specific UI component, such as a fragment or activity, and handles the communication with the business part of data handling, such as calling other components to load the data or forwarding user modifications. The ViewModel does not know about the View and is not affected by configuration changes such as recreating an activity due to rotation.

即:ViewModel是为特定的UI(例如Activity/Fragment)提供数据的类,同时它也承担和数据相关的业务逻辑处理功能:比如根据uid请求网络获取完整的用户信息,或者将用户修改的头像上传到服务器。因为ViewModel是独立于View(例如Activity/Fragment)这一层的,所以并不会被View层的事件影响,比如Activity被回收或者屏幕旋转等并不会造成ViewModel的改变。

好像啰里八嗦的讲了一堆还是不太明白,让我们来理一理现在都有涉及到哪些东西。现在已经有三个文件了:

  • user_profile.xml: 个人信息的布局文件
  • UserProfileFragment.java:承载布局文件,显示用户信息的Fragment
  • UserProfileViewModel.java:为UserProfileFragment提供数据,并且根据用户在Fragment上的操作提供相应业务逻辑的类

emmm......还是不太明白呀,show me the code!!!(简单起见,user_profile.xml布局文件大家自行脑补吧)

先从最不熟悉的UserProfileViewModel开始(这里面省略了如何获取和处理用户信息的业务逻辑,详细的获取用户信息的逻辑稍后的文章会有介绍),代码如下:

public class UserProfileViewModel extends ViewModel {
    private String userId;
    private User user;

    //初始化传递uid进来
    public void init(String userId) {
        this.userId = userId;
    }
    //提供完整的用户信息
    public User getUser() {
        return user;
    }
}

UserProfileFragment:

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);
        //通过Arguments获取uid
        String userId = getArguments().getString(UID_KEY);
        //创建ViewModel,不必太在意ViewModel的创建形式,这个之后会做详细的分析。现在只需要明白是在哪里生成的就行。
        viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
        viewModel.init(userId);
    }

    @Override
    public View onCreateView(LayoutInflater inflater,
                @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
        return inflater.inflate(R.layout.user_profile, container, false);
    }
}

注意:这里面UserProfileFragment继承的是LifecycleFragment而不是Fragment,这是由于目前 Architecture Components 处于Alpha阶段。等LifeCycle API稳定之后,Android Support Library里的Fragment将会实现LifecycleOwner接口。(至于什么是LifecycleOwner,这里暂不做介绍,大概的意思是和感知组件生命周期相关。)截止到写这篇文章为止,support V4中的Fragment已经实现了LifecycleOwner,LifecycleFragment处于Deprecated状态,所以可以直接继承V4 包中的Fragment即可。

好了,现在已经熟悉了三块代码了,那么我们怎么将他们联系在一起呢?换句话说,如果我们在UserProfileViewModel获取到了用户信息,那么我们怎么通知Fragment去显示相应的字段呢?(写个接口?不行的!这么高级的事情不能这么干的)这时候LiveData就上场了。那么什么是LiveData呢?这里仍然是做一个简单的概念上的介绍:

LiveData is an observable data holder. It lets the components in your app observe LiveData objects for changes without creating explicit and rigid dependency paths between them. LiveData also respects the lifecycle state of your app components (activities, fragments, services) and does the right thing to prevent object leaking so that your app does not consume more memory.

即:LiveData是一个包含可以被观察的数据载体。这么说又有点不好理解了,其实他就是基于观察者模式去做的。当LiveData的数据发生变化时,所有对这个LiveData变化感兴趣的类都会收到变化的更新。并且他们之间并没有类似于接口回调这样的明确的依赖关系。LiveData还能够感知组件(例如activities, fragments, services)的生命周期,防止内存泄漏。其实这和RxJava非常相似,但是LiveData能够在组件生命周期结束后自动阻断数据流的传播,防止产生空指针等意外。这个RxJava是不同的。

那么说的这么好,这个LiveData怎么用呢?其实非常简单,就是把UserProfileViewModelgetUser()方法的返回类型从User改成LiveData<User>就好,这样Fragment就能在User发生改变的时候及时被通知,LiveData更厉害的功能是,当对应的Fragment生命周期结束时,自动释放持有的引用。具体代码如下:

public class UserProfileViewModel extends ViewModel {
    ...
    //private User user;
    private LiveData<User> user;
    public LiveData<User> getUser() {
        return user;
    }
}

对应的Fragment中代码:

@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    viewModel.getUser().observe(this, new Observer<User>() {
            @Override
            public void onChanged(@Nullable User user) {
                //update UI
            }
        });
}

这样一旦UserProfileViewModel中的user发生改变,那么Fragment中就在onChanged()会收到相应的改变,并根据改变更新相应的UI。

如果对比之前类似RxJava这样的基于观察者模式的库时,就会发现这里并没有在Fragment的onStop()方法中对UserProfileViewModel解注册观察者。因为在LiveData中这是没有必要的,因为LiveData可以感知Fragment的生命周期状态,在收到onStart()开始后调用通知回调,并且在onStop()后不再调用回调通知。当然在收到Fragment的onDestroy()方法后还会自动的移除观察者。

从上面的代码可以看出我们并没有对 Configuration Changes(例如用户旋转屏幕)做任何处理。 ViewModel会在收到Configuration Changes时自动保存数据。当新的Fragment(Configuration Changes 后新生成的Fragment)进入生命周期时,它会收到之前的ViewModel实例,并且会收到ViewModel中保存的数据。从这里不难看出,ViewModel的生命周期要比Views长,所以不能在ViewModel中持有Views的引用,否则会造成内存泄漏等问题。ViewModel详细的生命周期稍后的文章会做详细的分析。

对上面这段文字总结下就是,把数据放在ViewModel中就不再需要担心生命周期的问题,而且对如果这个数据是以LiveData的形式被传递出去,那么任何改动都会被实时的传递给UI层。

这里有个疑问那就是如果Activity在onStop()后LiveData中数据发生改变,但是此时Activity对应的ViewModel是不会回调数据改变的。那么如果这时Activty重新回到界面onResume()后,还会收到最新的数据吗?答案是:会的。

举个简单的应用就是修改头像,我们会跳到修改头像的Activity去修改头像,在修改成功后返回个人中心页面,这时候数据会自动刷新减少了很多繁杂的逻辑代码。

好了,大概看到这里已经对Android Architecture Components中LifeCycle部分有了初步的认识,大概知道了LifecyclesUserProfileFragment)、LiveDataLiveData<User>)、ViewModelUserProfileViewModel)在什么情况下使用,以及一些使用的优点,这样其实已经久够了。对于这样一个框架,首先应该建立起一个整体的认识,然后再去填充细节的用法和技巧。

相关文章:
理解Android Architecture Components系列(一)
理解Android Architecture Components系列(二)
理解Android Architecture Components系列之Lifecycle(三)
理解Android Architecture Components系列之LiveData(四)
理解Android Architecture Components系列之ViewModel(五)
理解Android Architecture Components系列之Room(六)
理解Android Architecture Components系列之Paging Library(七)
理解Android Architecture Components系列之WorkManager(八)

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,736评论 4 362
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,167评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,442评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,902评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,302评论 3 287
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,573评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,847评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,562评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,260评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,531评论 2 245
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,021评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,367评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 33,016评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,068评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,827评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,610评论 2 274
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,514评论 2 269

推荐阅读更多精彩内容