Android Architecture Components(上篇)

达芬奇密码2018-07-23 10:04

前言:

作为一名移动互联网App研发人员,在实际项目的研发过程中,保质保量高效率,方便快捷,同时方便开发者之间的互相协作地完成开发任务,尤为重要。为了达成这个目的,我们一般会进行一些可复用,可扩展性,方便其他开发者调用的模块化和组件化的框架的搭建。当然其中不乏一些优秀的官方或第三方的框架,比如图片库:Fresco, UImageLoader Glide;网络库,OkHttp,Retrofit,Volley;消息分发 : EventBus等等。这些优秀的库的出现,极大的方便了无数的App 开发者,并在数以万计的App中使用。 而今天要介绍的是Google I / O 大会上发布的架构化组件,即Android Architecture Components。

什么是Android Architecture Components

A collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.

Android架构化组件是通过提供管理开发者UI 组件的生命周期和数据持久化方案的一系列库,来帮助开发者设计出,稳健性,可测试性和可维护的App应用。

在此之前,Google官方并没有推荐指定任何的应用程序开发架构,这意味着,我们可以自由的选择设计模式,Mvp Mvc MvvM等。但是同时,这也意味着我们需要编写大量的基础框架代码来实现这些设计模型。不仅如此,Google官方对组件(View ,Fragment, Activity等)的生命周期管理,并没有提供一套完整的可复用的解决方案,这对开发者来说会产生极大的困扰。比如我们在组件中进行异步操作,如查询数据库,网络请求,异步解析数据,设置定时器时,一旦组件销毁,异步操作完成回调UI线程时,就极易出现如空指针,内存泄漏等各种问题。而今天要介绍的Architecture Components就可以很轻松的解决这些问题。

Architecture Components主要包括两部分:

2)本地数据持久化: Room

Avoid boilerplate code and easily convert SQLite table data to Java objects using Room . Room provides compile time checks of SQLite statements and can return RxJava, Flowable and LiveData observables. Room可以帮助开发者避免写大量样板代码,并且可以地将数据库表数据,转换成Java 实体数据。Room提供SQLite语句的编译时检查,并且可以返回 Rxjava Flowable 和LiveData类型的可被观察数据。

1、ViewModel:

ViewModel是用来以一种能感知生命周期的方式来存储和管理与UI有关的数据。它允许数据在配置发生变化时,能够存活下来而不丢失。比如最常见的,Activity 横竖屏切换时,我们先想想正常流程下,横竖屏切换时,Activity的生命周期是怎样的。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Chronometer chronometer = findViewById(R.id.chronometer);
    chronometer.start();
}

布局文件如下:

   <RelativeLayout
      xmlns:android="http://schemas.android.com/apk/res/android"
      xmlns:tools="http://schemas.android.com/tools"
      android:id="@+id/activity_main"
      android:layout_width="match_parent"
      android:layout_height="match_parent"
      android:paddingBottom="@dimen/activity_vertical_margin"
      android:paddingLeft="@dimen/activity_horizontal_margin"
      android:paddingRight="@dimen/activity_horizontal_margin"
      android:paddingTop="@dimen/activity_vertical_margin"
      tools:context="com.example.android.lifecycles.step1.ChronoActivity1">

<TextView
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:text="Hello World!"
    android:layout_centerVertical="true"
    android:layout_centerHorizontal="true"
    android:id="@+id/hello_textview"/>

<Chronometer
    android:layout_width="wrap_content"
    android:layout_height="wrap_content"
    android:layout_above="@+id/hello_textview"
    android:layout_centerHorizontal="true"
    android:id="@+id/chronometer"/>
 </RelativeLayout>

我们先来看下图:(现在用的录屏转gif软件真的很难用,很难把握时机点,动图有点怪,见谅~)

这是一个计时Demo,在进行横竖屏切换时,我们发现,计数被重置了。这是因为Activity在 横竖屏切换时,Activity被销毁,并重新onCreate了。chronometer 生成了一个新的对象,所以计数重置了。但是众说周知,横竖屏切换时,数据被重置,在任何app应用中都是无法接受的。这时会有人说,可以通过android:configChanges="orientation|keyboardHidden" 等等,各种设置来实现Activity不被onCreate。这里且不说这种方式在高低版本兼容中可能出现的问题,横竖屏切换时的生命周期变化本身就可以专门写一篇文章来说明了。总之一句话,通过android:configChanges 来设置,以实现数据不被重置,太!麻!烦!那么还有其他方式来轻松的解决这个问题吗,是的,有,就是ViewModel。

先看图:

从图中我们可以看到,在横竖屏切换后,时间的数据并没有被重置。

ChronometerViewModel :

public class ChronometerViewModel extends ViewModel {
@Nullable
private Long mStartTime;

@Nullable
public Long getStartTime() {
    return mStartTime;
}

public void setStartTime(final long startTime) {
    this.mStartTime = startTime;
}
}

Activity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.d("ansen", "onCreate------>2");
    // The ViewModelStore provides a new ViewModel or one previously created.
    ChronometerViewModel chronometerViewModel
            = ViewModelProviders.of(this).get(ChronometerViewModel.class);

    // Get the chronometer reference
    Chronometer chronometer = findViewById(R.id.chronometer);

    if (chronometerViewModel.getStartTime() == null) {
        // If the start date is not defined, it's a new ViewModel so set it.
        long startTime = SystemClock.elapsedRealtime();
        chronometerViewModel.setStartTime(startTime);
        chronometer.setBase(startTime);
    } else {
        // Otherwise the ViewModel has been retained, set the chronometer's base to the original
        // starting time.
        chronometer.setBase(chronometerViewModel.getStartTime());
    }

    chronometer.start();
}

ChronometerViewModel 在横竖屏切换后,是会继续存活的,ChronometerViewModel 里的mStartTime的数据自然也是存活的,这样就可以达到我们在前文预期的效果。 那么ViewModel是如何做到的呢?

ChronometerViewModel chronometerViewModel
            = ViewModelProviders.of(this).get(ChronometerViewModel.class);

这行代码中的this,为AppCompatActivity, 实现了LifecycleOwner接口。Framework保证,只要LifecycleOwner是存活着的,ViewModel就处于存活状态。在ViewModel的LifecycleOwner因为配置变化而销毁时(比如横竖屏切换),ViewModel本身不会销毁,新的LifecycleOwner 实例会和存活的ViewModel建立连接。说明图表如下:

注意:

1)Activity在配置变化如横竖屏切换时,Activity会被销毁,但是ViewModel不会因为配置变化而销毁。但是如果我们调用Activity 的finish()方法,activity 生命周期完全结束,处于finished状态时,ViewModel也会随之销毁。

2)如果ViewModel持有View或Context的引用,可能会造成内存泄漏,要避免这种情况。ViewModel的onCleared()方法,对于其持有的更长生命周期的对象来取消订阅,或者清除引用是有效的,但是不是用来取消View 或Context的引用,要避免这种行为。

public class LiveDataTimerViewModel extends ViewModel {

private static final int ONE_SECOND = 1000;

private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();

private long mInitialTime;

public LiveDataTimerViewModel() {
    mInitialTime = SystemClock.elapsedRealtime();
    Timer timer = new Timer();

    // Update the elapsed time every second.
    timer.scheduleAtFixedRate(new TimerTask() {
        @Override
        public void run() {
            final long newValue = (SystemClock.elapsedRealtime() - mInitialTime) / 1000;
            // setValue() cannot be called from a background thread so post to main thread.
            mElapsedTime.postValue(newValue);
        }
    }, ONE_SECOND, ONE_SECOND);

}

public LiveData<Long> getElapsedTime() {
    return mElapsedTime;
}
}

Activity:

public class ChronoActivity3 extends AppCompatActivity {

private LiveDataTimerViewModel mLiveDataTimerViewModel;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    setContentView(R.layout.chrono_activity_3);

    mLiveDataTimerViewModel = ViewModelProviders.of(this).get(LiveDataTimerViewModel.class);

    subscribe();
}

private void subscribe() {
    final Observer<Long> elapsedTimeObserver = new Observer<Long>() {
        @Override
        public void onChanged(@Nullable final Long aLong) {
            String newText = ChronoActivity3.this.getResources().getString(
                    R.string.seconds, aLong);
            ((TextView) findViewById(R.id.timer_textview)).setText(newText);
            Log.d("ChronoActivity3", "Updating timer");
        }
    };

    mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
}
}

我们可以看到LiveDataTimerViewModel 继承自ViewModel,ViewModel的作用上文已说过,这里不再赘述。 LiveDataTimerViewModel 拥有LiveData对象MutableLiveData,操作的数据类型是Long型的时间戳。 构造方法中,每一秒钟会更新一次数据,并将值postValue给LiveData对象mElapsedTime。注意: 因为定时器的线程是异步线程,而Activity是在UI线程中监听LiveData的时间变化的,所以此时需要用postValue给LiveData。而不是setValue(当然如果数据变化不是在异步线程而是在主线程中进行的,则需要用setValue)。

接下来看LiveData的observe方法。

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer observer)

1)将所给的Observer加入到所给的Owner的生命周期范畴的Observer的列表中。事件分发会在主线程中进行,如果LiveData 已经set了数据,那么这个数据变化会被发送给Observer。

2)Observer只会在Owner处于活跃状态时(STARTED或者RESUMED状态)时才会接收到变化的通知。

3)如果Owner状态变为了DESTROYED状态,那么这个Observer会被移除。

4)在Owner处于不活跃状态时,数据发生变化不会通知给Observer。但是当Owner重新变活跃时,它会接收到最后一次可用的数据更新通知。

5)只要Owner不处于DESTROYED状态 , LiveData会一直持有Observer的强引用。一旦Owner变为DESTROYED状态,Live会移除对Observer的引用。

6)如果Owner在observe方法调用时处于DESTROYED状态,LiveData会忽略此次调用。

7)如果给定的Oberver和Owner已经在观察列表中了,LiveData会忽略此次调用。但是如果Oserver在观察列表中拥有多个Owner, LiveData会报IllegalArgumentException。

现在看这个例子就比较简单了。观察者是elapsedTimeObserver ,它监听LiveData里的long类型的时间变化。elapsedTimeObserver 的生命周期拥有者是this即ChronoActivity3 。在ChronoActivity3 处于活跃状态时,LiveData会将时间的变化通知给elapsedTimeObserver,并在onChange回调中更新UI。

此时我相信大家应该注意到一个点。就是我们不需要在每次数据变化后,再去refresh一遍UI了,我们只需要将需要观察的数据放入LiveData中,然后将数据变化通知给组件。这在大量不同数据变化需要更新不同UI的场景下,大大解放了我们的双手,我们再也不用在每个数据变化时,都去调用refreshUI方法了!

LiveData的优势:

1)LIveData确保你的UI使用的是最新的数据,而且还不需要你在数据变化时自己调用更新。

2)不会内存泄漏。(我们上文讨论过这个问题。因为Observer只会在自己的生命周期拥有者,LiveCycleOwner active的状态下,才会接收到数据更新的通知,所以不会再有内存泄漏的问题。)

3)不会在Activity销毁后还接收到更新UI的指令导致崩溃。

4)开发者不需要再手动管理生命周期。

5)即使在配置发生变化时,比如activty的横竖屏切换,观察者还是能收到最新的有效数据。

6)这一点感觉很好用。LiveData可以以单例模式在应用中存在。这时它就可以被应用中,任何需要它的观察者监听,以实现多页面多处监听更新等。

再介绍两个类: MutableLiveData : 以public形式暴露了LiveData的setValue和postValue方法。 MediatorLiveData:可以观察其他的LiveData数据,并在其发生变化时通知给MediatorLiveData。

相关阅读:

Android Architecture Components(下篇)

本文来自网易实践者社区,经作者朱强龙授权发布。