ViewModel
举个例子:我们在界面上有一个计时器,记录我们在这个界面停留的时间,但是当我们旋转屏幕的时候,会导致Activity重新创建实例,onCreate()
方法会再次执行,导致计时器会重新从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>
public class ChronoActivity1 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
//在onCreate()方法中开始倒计时
Chronometer chronometer = findViewById(R.id.chronometer);
long startTime = SystemClock.elapsedRealtime();
//每次onCreate()方法都会重新设置base
chronometer.setBase(startTime);
chronometer.start();
}
}
当然我们可以通过其他手段解决这个问题,例如当屏幕旋转的时候不让Activity
重新创建实例。
或者我们可以在onSaveInstanceState()
方法中保存相应的数据,然后当Activity
重新创建实例
的时候,我们在onCreate()
方法中获取保存的数据,然后设置计时器的开始时间。然后我们再
看看ViewModel的表现。
自定义一个ChronometerViewModel
继承ViewModel
public class ChronometerViewModel extends ViewModel {
@Nullable
private Long mStartTime;
@Nullable
public Long getStartTime() {
return mStartTime;
}
public void setStartTime(final long startTime) {
this.mStartTime = startTime;
}
}
// 创建或者直接返回一个已经存在的ViewModel
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this)
.get(ChronometerViewModel.class);
if (chronometerViewModel.getStartTime() == null) {
//chronometerViewModel如果没设置过开始时间,那么说明这个新的ViewModel,
//所以给它设置开始时间
long startTime = SystemClock.elapsedRealtime();
chronometerViewModel.setStartTime(startTime);
chronometer.setBase(startTime);
} else {
//否则ViewModel已经在上个Activity的onCreate()方法中创建过了,屏幕旋转以后,
//ViewModel会被保存,我们直接获取ViewModel里持有的时间
chronometer.setBase(chronometerViewModel.getStartTime());
}
chronometer.start();
这样就可以解决屏幕旋转以后重新从0开始计时的问题了。
我们看一下关键代码
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this)
.get(ChronometerViewModel.class);
ViewModelProviders
的of()
方法,只看方法的注释,不看方法体
/**
* 创建一个ViewModelProvider ,只要传入的Activity存活,ViewModelProvider 就会被一直保留
* @param activity 一个activity, 在谁的生命周期内,ViewModel会被保留
* @return 一个ViewModelProvider 实例
*/
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
initializeFactoryIfNeeded(checkApplication(activity));
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
ViewModelProviders
的get()
方法,只看方法的注释,不看方法体
//返回一个已经存在的ViewModel或者创建一个新的ViewModel实例
@NonNull
public <T extends ViewModel> T get(@NonNull Class<T> modelClass) {
String canonicalName = modelClass.getCanonicalName();
if (canonicalName == null) {
throw new IllegalArgumentException("Local and anonymous classes
can not be ViewModels");
}
return get(DEFAULT_KEY + ":" + canonicalName, modelClass);
}
看一下ViewModel
被保留的周期
注意:Activity
走了onDestroy()
方法并不带表这个Activity
就结束了。可以通过Activity
的isFinishing()
方法来判断。我们发现,在旋转屏幕的时候isFinishing()
方法返回false。在按下返回键的时候isFinishing()
为true。
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: " + isFinishing());
}
ViewModel:ViewModel
是一个用来为Activity
或者Fragment
准备和管理数据的类。ViewModel
也可以用来处理Activity/Fragment
和应用其他部分的通信。
一个ViewModel
的创建总是和一个作用域(一个 Fragment/Activity
)有关,并且只要这个作用域存活,那么这个ViewModel
会被一直保留。例如,如果作用域是一个Activity
,那么ViewModel
会保留直到Activity
结束。
换句话说,这意味着如果ViewModel
的所有者,例如一个Activity
由于旋转而被销毁,但是ViewModel
并不会销毁,新创建的Activity
的实例仅仅是重新关联到已经存在的ViewModel
。
ViewModel
存在的目的是为了获取并保持对Activity/Fragment
重要的信息。Activity/Fragment
应该能够观察到 ViewModel
的变化。ViewModel
通常通过LiveData
或者Data Binding
来暴露信息。也可以通过其他任何可观察的对象,例如RxJava
中的ObserVable
。
ViewModel
的唯一的作用是管理UI的数据。ViewModel
不能访问UI或者持有Activity/Fragment
的引用。
在Fragment之间共享ViewModel
举个例子:在一个Activity
中有两个Fragment
,每个Fragment
里面都有一个SeekBar
。当其中一个SeekBar
进度改变的时候,也更新另外一个Fragment
里面的SeekBar
的进度。
Activity
什么也没做,就是布局文件里有两个Fragment
public class Activity_step5 extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_step5_solution);
}
}
<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的实现
public class Fragment_step5 extends Fragment {
private SeekBar mSeekBar;
private SeekBarViewModel mSeekBarViewModel;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View root = inflater.inflate(R.layout.fragment_step5, container, false);
mSeekBar = root.findViewById(R.id.seekBar);
mSeekBarViewModel = ViewModelProviders.of(getActivity())
.get(SeekBarViewModel.class);
subscribeSeekBar();
return root;
}
private void subscribeSeekBar() {
// Update the ViewModel when the SeekBar is changed.
mSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onProgressChanged(SeekBar seekBar, int progress,
boolean fromUser) {
//如果是用户改变了seekbar的进度就更新ViewModel
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(getActivity(), new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
}
}
运行程序,可以看到当手动改变其中一个SeekBar
的进度,另外一个也会跟着变
LiveData
上面说了,ViewModel
通常通过LiveData
或者Data Binding
来暴露信息。也可以通过其他任何可观察的对象,例如RxJava
中的ObserVable
。LiveData
与普通的Observable
不同,LiveData
是生命周期感知的,这意味着它尊重其他应用程序组件的生命周期,例如Activity
,Fragment
或Service
。 LiveData
生命周期感知能力确保 LiveData
仅仅去更新那些处于生命周期活动状态的观察者。(感觉这个比较厉害了)
接着上面的例子:
我们想在Activity
之外,每隔一秒钟,更新Activity
的UI。
新建一个LiveDataTimerViewModel
public class LiveDataTimerViewModel extends ViewModel {
private static final int ONE_SECOND = 1000;
//新建一个LiveData实例
private MutableLiveData<Long> mElapsedTime = new MutableLiveData<>();
private long mInitialTime;
public LiveDataTimerViewModel() {
mInitialTime = SystemClock.elapsedRealtime();
Timer timer = new Timer();
// 每隔一秒更新一次
timer.scheduleAtFixedRate(new TimerTask() {
@Override
public void run() {
final long newValue =
(SystemClock.elapsedRealtime() - mInitialTime) / 1000;
// setValue() 不能再后台线程调用,所以使用post到主线程
mElapsedTime.postValue(newValue);
}
}, ONE_SECOND, ONE_SECOND);
}
public LiveData<Long> getElapsedTime() {
return mElapsedTime;
}
}
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();
}
/**
* 新建一个Observer,然后订阅 mLiveDataTimerViewModel.getElapsedTime()
*/
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);
}
}
运行上面的代码你发现,只有在Activity
活动的时候(也就是生命周期状态是STARTED
和RESUMED
的时候),log日志才会输出,当你点击HOME键或者打开其他的APP的时候,log日志不会输出。当你回到Activity
的时候,日志才会接着输出。
看一下LiveData
的observe()
方法
/**
* 把给定的观察者添加到观察者列表中,事件是在主线程分发的。如果LiveData已经被设置了
* 数据,那么数据会被发送给这个新添加的观察者。
*
* 只有当生命周期持有者owner处在活动状 态的时候{@link Lifecycle.State#STARTED} or
* {@link Lifecycle.State#RESUMED} ,这个 observer 才会收到事件。
*
* 如果owner 到了销毁状态 {@link Lifecycle.State#DESTROYED},这个observer 会被自动移
* 除。
*
* 当这个owner处于不活动的状态的时候,如果数据改变了,这个observer不会收到任何更
* 新。当owner重新回到了active的状态,这个oberver会自动收到 最新的数据
*
* 只要指定的LifecycleOwner 没有被销毁,LiveData 就一直持有observer和owner的强应用
*当LifecycleOwner 被销毁了,LiveData 会移除obser和owner的引用
*
* 如果指定的owner已经处于销毁状态,方法直接返回。 {@linkLifecycle.State#DESTROYED}
*
* 如果指定的owner和oberver元组已经在观察这列表里了,方法直接返回
*
* 如果Iobserver 已经和另外一个owner在观察者列表里了,LiveData 抛出
*IllegalArgumentException
*
* @param owner 生命周期持有者,用来控制observer
* @param observer 观察者,用来接收事件
*/
@MainThread
public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
// ignore
return;
}
LifecycleBoundObserver wrapper = new LifecycleBoundObserver(owner, observer);
LifecycleBoundObserver existing = mObservers.putIfAbsent(observer, wrapper);
if (existing != null && existing.owner != wrapper.owner) {
throw new IllegalArgumentException("Cannot add the same observer"
+ " with different lifecycles");
}
if (existing != null) {
return;
}
owner.getLifecycle().addObserver(wrapper);
}
注意,AppCompatActivity
是SupportActivity
的子类,而SupportActivity
实现了LifecycleOwner
。所以AppCompatActivity
是一个生命周期持有者。
public class SupportActivity extends Activity implements LifecycleOwner {
...
}
订阅生命周期事件
我们已经知道SupportActivity
是一个生命周期持有者了。一个生命周期持有者会在不同的生命周期发出不同的生命周期事件。我们可以观察这些事件,并根据这些事件,进行基于生命周期的操作。
LifecycleOwner
获取Lifecycle
的方法
public interface LifecycleOwner {
/**
* 返回当前生命周期持有者的生命周期
*/
@NonNull
Lifecycle getLifecycle();
}
Lifecycle
类用来定义具有Android生命周期的对象。
Lifecycle
的ON_CREATE
, ON_START
, ON_RESUME
事件会在生命周期持有者的相应的生命周期方法后才发出。ON_PAUSE
, ON_STOP
, ON_DESTROY
事件会在生命周期持有者相应的生命周期方法之前发出。 例如, ON_START
会在onStart
方法之后发出,ON_STOP
会在onStop
方法之前发出。
Lifecycle
类的内部类Lifecycle.Event
是一个枚举类,定义了生命周期持有者发出的所有事件类型
枚举值 | 描述 |
---|---|
ON_ANY | 可以用来匹配任何事件 |
ON_CREATE | onCreate事件 |
ON_DESTROY | onDestroy事件 |
ON_PAUSE | onPause事件 |
ON_RESUME | onResume事件 |
ON_CREATE | onCreate事件 |
ON_START | onStart事件 |
ON_STOP | onStop事件 |
Lifecycle
类的内部类Lifecycle.State
也是一个枚举类,定义了生命周期持有者所有的生命周期状态。如下图所示。
举个例子:我们想在Activity
活动的时候,注册一个LocationListener
来获取位置信息,然后在onPause
的时候,移除监听器,那我们可以通过Activity
的生命周期事件来实现。
自定义的LocationListener
private class MyLocationListener implements LocationListener {
@Override
public void onLocationChanged(Location location) {
//位置改变的时候,改变界面上的经纬度
TextView textView = findViewById(R.id.location);
textView.setText(location.getLatitude() + ", " + location.getLongitude());
}
@Override
public void onStatusChanged(String provider, int status, Bundle extras) {
}
@Override
public void onProviderEnabled(String provider) {
Toast.makeText(LocationActivity.this,
"Provider enabled: " + provider, Toast.LENGTH_SHORT).show();
}
@Override
public void onProviderDisabled(String provider) {
}
}
要观察生命周期事件,首先要实现LifecycleObserver
接口。
BoundLocationListener
类实现了 LifecycleObserver
接口,当生命周期持有者LifecycleOwner
处于ON_RESUME的状态的时候,我们获取定位服务,并在位置变化的时候通知LocationListener
更新信息。当生命周期持有者LifecycleOwner
处于ON_PAUSE的状态的时候我们移除LocationListener
。
public class BoundLocationManager {
public static void bindLocationListenerIn(LifecycleOwner lifecycleOwner,
LocationListener listener, Context context) {
new BoundLocationListener(lifecycleOwner, listener, context);
}
@SuppressWarnings("MissingPermission")
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;
lifecycleOwner.getLifecycle().addObserver(this);
}
@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);
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
void removeLocationListener() {
if (mLocationManager == null) {
return;
}
mLocationManager.removeUpdates(mListener);
mLocationManager = null;
Log.d("BoundLocationMgr", "Listener removed");
}
}
}
然后将生命周期持有者和生命周期事件观察者绑定。这里忽略定位权限的处理。
public class LocationActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.location_activity);
bindLocationListener();
}
private void bindLocationListener() {
BoundLocationManager.bindLocationListenerIn(this, mGpsListener,
getApplicationContext());
}
}
运行程序,不断旋转手机的时候,输入如下
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
参考链接
ViewModel
LiveData
github android-lifecycles
https://codelabs.developers.google.com/codelabs/android-lifecycles/#0
网友评论