简介
Android Architecture components 是一组 Android 库,它可以帮助我们以一种健壮的、可测试和可维护的方式构建 APP。
注意: 这个架构目前仍处于 alpha 开发状态。在正式版发布之前 API 可能会改变,你可能会遇到稳定性和性能问题。
下面将会介绍使用生命周期感知(lifecycle-aware)组件去构建APP:
-
ViewModel - 提供了创建和检索(类似于单例)绑定到特定生命周期对象的方法,
ViewModel通常是存储View数据的状态并与其他的组件进行通信,如数据存储组件和处理业务逻辑的组件。想了解更多,请查看 ViewModel 指南。
-
LifecycleOwner/LifecycleRegistryOwner -
LifecycleOwner和LifecycleRegistryOwner都是在LifecycleActivity和LifecycleFragment类中实现的接口。你可以将自己的组件也实现这些接口以观察生命周期所有者的状态变化。想了解更多,请查看 Lifecycles 指南。
-
LiveData - 允许您观察应用程序的多个组件的数据更改,而不会在它们之间创建明确的,刚性的依赖路径。
LiveData关心应用程序组件复杂的生命周期,包括Activity,Fragmnet,Service或者 APP 中定义的任何LifecycleOwner。LiveData会管理观察者的订阅状态,LifecycleOwner对象stopped时会暂停订阅,以及LifecycleOwner对象Finished时会取消订阅。想了解更多,请查看 LiveData 指南。
如果你已经安装了 Android Studio 2.3 或者更高版本并且熟悉 Activity 生命周期 ,那么你可以继续向下看了。
配置环境
-
下载源码,并导入 Android Studio
-
运行 【Step 1】,界面大概是下面这样的
step 1
- 旋转屏幕,计时器会从0开始重新计时。
这部分代码比较简单,首先是布局文件:
<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>
然后在 Activity 的 onCreate 函数中开始计时即可,如下:
public class ChronoActivity1 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
chronometer.start();
}
}
Chronometer 是 Android 提供的一个计时器组件,该组件继承自 TextView,它显示从某个起始时间开始过去了多少时间。
想要在屏幕旋转时计时器状态不被更改,您可以使用 ViewModel ,因为此类可以在配置更改(例如屏幕旋转)时不会被回收。
注: 在
mainfest文件中可以配置Activity在屏幕旋转时不重新创建Activity,从而保持计时器的状态,这种情况不在本篇谈论范围内。
注:如源码中的 google maven 不能访问,可将项目中 gradle 文件改为如下
repositories { jcenter() maven { url "https://dl.google.com/dl/android/maven2/" } }
添加 ViewModel
在此步骤中,您可以使用 ViewModel 在屏幕旋转之间保持状态,并解决您在上一步中观察到的行为(旋转屏幕时计时器会重置)。在上一步中,运行了一个显示计时器的 Activity。当配置更改(如屏幕旋转)会导致 Activity 被销毁,此定时器将重置。
您可以使用 ViewModel 在 Activity 或 Fragment 的整个生命周期中保留数据。如上一步所示,使用 Activity 来管理 APP 的数据是不明智的。 Activity 和 Fragment 是一种短命的对象,它们随着用户与应用程序交互而频繁创建和销毁。 ViewModel 还适用于管理与网络通信相关的任务,以及数据的操作和持久化。
使用ViewModel来保持计时器的状态
首先需要创建一个ViewModel,如下
public class ChronometerViewModel extends ViewModel {
@Nullable
private Long startDate;
@Nullable
public Long getStartDate() {
return startDate;
}
public void setStartDate(final long startDate) {
this.startDate = startDate;
}
}
打开 ChronoActivity2 并检查该 Activity 如何检索并使用 ViewModel 的,如下:
public class ChronoActivity2 extends LifecycleActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ViewModelProviders 会提供一个新的或者之前已经创建过的 ViewModel
ChronometerViewModel chronometerViewModel
= ViewModelProviders.of(this).get(ChronometerViewModel.class);
// 获取 chronometer
Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
if (chronometerViewModel.getStartDate() == null) {
// 如果开始时间为 null , 那么这个 ViewModel 是刚被创建的
long startTime = SystemClock.elapsedRealtime();
chronometerViewModel.setStartDate(startTime);
chronometer.setBase(startTime);
} else {
// 否则这个 ViewModel 是从 ViewModelProviders 中被检索出来的,所以需要设置刚开始的开始时间
chronometer.setBase(chronometerViewModel.getStartDate());
}
chronometer.start();
}
}
其中
ChronometerViewModel chronometerViewModel
= ViewModelProviders.of(this).get(ChronometerViewModel.class);
this 是 LifecycleOwner 的实例。 只要 LifecycleOwner 的范围是活跃的(alive),框架就可以使 ViewModel 处于活跃状态。如果其所有者因为配置更改(例如屏幕旋转)而被毁坏(destroyed),ViewModel 则不会被销毁。生命周期所有者会重新连接到现有的 ViewModel ,如下图所示:
Viewmodel Lifecycle
注意:
Activity或Fragment的范围从created到finished(或终止),您不能与被destroyed混淆。请记住,当一个设备旋转时,Activity被销毁,但是与它关联的任何ViewModel的实例并不会被销毁。
试试看
运行应用程序并确认执行以下任一操作时定时器不会重置:
- 旋转屏幕。
- 导航到另一个应用程序,然后返回。
定时器不会重置
但是,如果你或系统退出应用程序,则定时器将重置。
注意: 系统会在生命周期所有者(例如
Activity或Fragment)的整个生命周期中将ViewModel实例保存在内存中 。 系统不会将ViewModel的实例持久化到长期存储。
使用 LiveData 包装数据
这一步使用自定义的 Timer 实现上一步的中的 chronometer 。将上面的逻辑添加到 LiveDataTimerViewModel 类中,将 Activity 的主要功能放在管理用户和UI之间的交互上。
Activity 会在定时器通知时更新UI。为了避免内存泄露, ViewModel 不应该持有 Activity 的实例。比如,配置更改(例如屏幕旋转)会使 Activity 被系统回收,如果这时 ViewModel 仍持有Activity 的实例那么 Activity 就不会被系统回收从而导致内存泄露 。系统会保留 ViewModel 的实例至到 Activity 或生命周期持有者不再存在。
注意: 在
ViewModel中持有 Context 或者 View 的实例可能会导致内存泄露。避免引用 Context 或者 View 类的实例的字段。ViewModel中的 onCleared() 方法可用于取消订阅或清除对具有较长生命周期的其他对象的引用,但不用于清除 Context 或者 View 对象的引用。
可以设置 Activity 或 Fragment 来观察数据源,当数据更改时将会接收通知,而不是直接从 ViewModel 修改视图。这就是观察者模式。
注意: 要想将数据可被观察,可将数据包装在LiveData类中。
如果你使用过 Data Binding Library 或其他响应式库(如RxJava),你应该很熟悉观察者模式。LiveData 是一个特殊的可观测类,它可以感知生命周期,并且只在声明周期活跃时通知观察者。
LifecycleOwner
ChronoActivity3 是 LifecycleActivity 的子类,他提供了声明周期的状态。如下:
public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner {...}
LifecycleRegistryOwner 接口的作用是用来将 ViewModel 和 LiveData 绑定到 Activity 或 Fragment 生命周期中。如果Fragment 的话可以继承 LifecycleFragment 类。
Update ChronoActivity
- 在
ChronoActivity3中添加如下代码,在subscribe()方法中创建订阅者:
public class ChronoActivity3 extends LifecycleActivity {
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);
}
};
mLiveDataTimerViewModel.getElapsedTime().observe(this, elapsedTimeObserver);
}
}
- 接下来在
LiveDataTimerViewModel中设置计时时间,如下:
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() 不允许在子线程中调用,所以需要post主线程执行
// 其实也可以直接使用 postValue() 方法,该方法会在内部post到主线程执行
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//setValue时,观察者可监听到值的变化
mElapsedTime.setValue(newValue);
}
});
}
}, ONE_SECOND, ONE_SECOND);
}
public LiveData<Long> getElapsedTime() {
return mElapsedTime;
}
}
- 运行 APP 你会发现界面每秒都在更新,除非你导航到另外一个 APP。如果你的设备支持多窗口,或者旋转屏幕并不会影响APP显示。如下图:
注意:
LiveData只有在 Activity 或者LifecycleOwner活跃(active)时才发送更新。如果你导航到其他的APP,Activity 中的日志打印将会停止,至到你重新回到APP。当LiveData对象的各自的生命周期所有者处于STARTED或者RESUMED,我们才称LiveData是活跃的。
订阅生命周期事件
许多 Android 组件或库要求你
- 订阅或初始化组件或库
- 取消订阅或 stop 组件或库
没有做上面的步骤可能会导致内存泄露或者产生bugs。
生命周期所有者可以传递给生命周期感知组件生命周期的状态,以确保他们知道生命周期的当前状态。
你可以用下面的方法查询生命周期的状态:
lifecycleOwner.getLifecycle().getCurrentState()
上面的语句返回的是什么周期的状态,如 Lifecycle.State.RESUMED, 或 Lifecycle.State.DESTROYED 。
实现 LifecycleObserver 接口的生命周期感知对象还可以观察生命周期所有者的状态的变化:
lifecycleOwner.getLifecycle().addObserver(this);
可以通过注解的方式,实现在各个生命周期事件执行相应的方法:
@OnLifecycleEvent(Lifecycle.EVENT.ON_RESUME)
void addLocationListener() { ... }
创建生命周期感知组件
在这里我们会创建一个对 Activity 生命周期所有者做出响应的组件,使用 Fragment 作为生命周期所有者时,可以采用类似的原则和步骤。
使用 Android framework 的 LocationManager 去获取经纬度并且展示给用户,这允许你
-
订阅更改并使用
LiveData自动更新UI。 -
创建一个包装器,他可以根据 Activity 生命状态的来决定是注册还是注销对LocationManager的监听。
通常的做法是在 Activity 的 onStart() 或 onResume() 中注册监听器,在 onStop() 或 onPause() 方法中移除监听器,如下:
// 在 activity 中通常这样做
@Override
protected void onResume() {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
}
@Override
protected void onPause() {
mLocationManager.removeUpdates(mListener);
}
看下我们是如何改造上面代码的,首先我们需要创建一个 BoundLocationManager 类,它需要实现 LifecycleOwner 接口并且需要传入 LifecycleRegistryOwner 的实例。 BoundLocationManager 类的实例绑定到 Activity 的生命周期上了,如下:
static class BoundLocationListener implements LifecycleObserver {
private final Context mContext;
private LocationManager mLocationManager;
private final LocationListener mListener;
public BoundLocationListener(LifecycleOwner lifecycleOwner,
LocationListener listener, Context context) {
mContext = context;
mListener = listener;
//想要观察 Activity 的声明周期,必须将其添加到观察者中。添加下面的代码
//才能是 BoundLocationListener 实例监听到生命周期
lifecycleOwner.getLifecycle().addObserver(this);
}
//可以使用 @OnLifecycleEvent 注解来监听 Activity 生命周期的变化
// 可以使用下面的注解来添加 addLocationListener() 方法
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
void addLocationListener() {
mLocationManager =
(LocationManager) mContext.getSystemService(Context.LOCATION_SERVICE);
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
Log.d("BoundLocationMgr", "Listener added");
// Force an update with the last location, if available.
Location lastLocation = mLocationManager.getLastKnownLocation(
LocationManager.GPS_PROVIDER);
if (lastLocation != null) {
mListener.onLocationChanged(lastLocation);
}
}
// 可以使用下面的注解来移除 removeLocationListener() 方法
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void removeLocationListener() {
if (mLocationManager == null) {
return;
}
mLocationManager.removeUpdates(mListener);
mLocationManager = null;
Log.d("BoundLocationMgr", "Listener removed");
}
}
注意: 观察者可以观测到提供者的当前状态,所以不需要从构造函数调用
addLocationListener()方法。他是在观察者添加到生命周期所有者的时候被调用的。
运行APP,在旋转屏幕的时候会有下面的打印
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
使用 Android 模拟器改变设备的位置,UI 就会更新,如下图:
改变设备的位置
在 Fragmnet 之间共享 ViewModel
在 Fragmnet 之间共享 ViewModel 之前,我们需要做一些前期准备
首先需要创建一个 Activity ,该 Activity 中包含两个 Fragment,在其布局文件中添加两个 Fragment 即可,如下:
<LinearLayout 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:orientation="vertical"
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.step5_solution.Activity_step5">
<fragment
android:id="@+id/fragment1"
android:name="com.example.android.lifecycles.step5_solution.Fragment_step5"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
<fragment
android:id="@+id/fragment2"
android:name="com.example.android.lifecycles.step5_solution.Fragment_step5"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
其中 Fragment_step5 就是需要共享 ViewModel 的 Fragment,这个 Fragment 也很简单,只是包含了一个 SeekBar 控件,如下:
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="com.example.android.lifecycles.step5_solution.Fragment_step5">
<SeekBar
android:id="@+id/seekBar"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</FrameLayout>
然后就是创建一个包含 LiveData 的 ViewModel,如下:
public class SeekBarViewModel extends ViewModel {
public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
}
运行这一步中的代码,你会发现这是两个彼此独立的SeekBar实例:
使用 ViewModel 在 Fragment 之间通信,以便当一个 SeekBar 更改时,另一个 SeekBar 将被更新:
使用 ViewMode 进行通信的代码如下:
public class Fragment_step5 extends Fragment {
private SeekBar mSeekBar;
private SeekBarViewModel mSeekBarViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
// Inflate the layout for this fragment
View root = inflater.inflate(R.layout.fragment_step5, container, false);
mSeekBar = (SeekBar) root.findViewById(R.id.seekBar);
mSeekBarViewModel = ViewModelProviders.of(getActivity()).get(SeekBarViewModel.class);
subscribeSeekBar();
return root;
}
private void subscribeSeekBar() {
// 当 SeekBar 变化的时候更新 ViewModel
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
if (fromUser) {
Log.d("Step5", "Progress changed!");
mSeekBarViewModel.seekbarValue.setValue(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {
}
@Override
public void onStopTrackingTouch(SeekBar seekBar) {
}
});
// 当 ViewModel 变化的时候更新 SeekBar
mSeekBarViewModel.seekbarValue.observe((LifecycleOwner) getActivity(),
new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
}
}
注意: 你应该使用 Activity 作为生命周期的持有者,因为每个 Fragment 的生命周期都是独立的。
至此,你应该对 AAC 有一个初步的认识了。












网友评论