作为一名移动互联网App研发人员,在实际项目的研发过程中,保质保量高效率,方便快捷,同时方便开发者之间的互相协作地完成开发任务,尤为重要。为了达成这个目的,我们一般会进行一些可复用,可扩展性,方便其他开发者调用的模块化和组件化的框架的搭建。当然其中不乏一些优秀的官方或第三方的框架,比如图片库:Fresco, UImageLoader Glide;网络库,OkHttp,Retrofit,Volley;消息分发 : EventBus等等。这些优秀的库的出现,极大的方便了无数的App 开发者,并在数以万计的App中使用。 而今天要介绍的是Google I / O 大会上发布的架构化组件,即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主要包括两部分:
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类型的可被观察数据。
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(下篇)
本文来自网易实践者社区,经作者朱强龙授权发布。