官方代碼,建議將代碼下載下來對照著代碼閱讀。
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
僅僅去更新那些處于生命周期活動狀態的觀察者。(感覺這個比較厲害了)
注意:正常情況應該在onCreate中訂閱觀察者。如果在onStart或者onResume中訂閱會有問題:
- 比如當前Activity onStop了,然后從onStop重新onStart的時候又會訂閱一次,導致重復訂閱。
接著上面的例子:
我們想在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()
方法
/**
* 將指定的觀察者添加到LifecycleOwner生命周期之內的觀察者列表中。事件是在主線程分發的。如果LiveData已經被設置了
* 數據,那么數據會被發送給這個新添加的觀察者。
*
* 只有當LifecycleOwner處在活動狀態的時候{@link Lifecycle.State#STARTED} or
* {@link Lifecycle.State#RESUMED} ,這個observer才會收到事件。
*
* 如果LifecycleOwner到了銷毀狀態 {@link Lifecycle.State#DESTROYED},這個observer會被自動移除。
*
* 當這個LifecycleOwner處于不活動的狀態的時候,如果數據改變了,這個observer不會收到任何更新。
* 當LifecycleOwner重新回到了active的狀態,這個oberver會自動收到最新的數據。
*
* 只要指定的LifecycleOwner沒有被銷毀,LiveData就一直持有observer和LifecycleOwner的強引用
* 當LifecycleOwner被銷毀了,LiveData會移除observer和LifecycleOwner的引用。
*
* 如果指定的LifecycleOwner已經處于銷毀狀態{@linkLifecycle.State#DESTROYED} ,方法直接返回。
*
* 如果指定的LifecycleOwner和oberver元組已經在觀察這列表里了,方法直接返回。
*
* 如果observer已經和另外一個關聯的LifecycleOwner在觀察者列表里了,
* LiveData拋出IllegalArgumentException。
*
* @param owner LifecycleOwner,用來控制observer
* @param observer 觀察者,用來接收事件
*/
@MainThread
public void observe(LifecycleOwner owner, @NonNull Observer<T> observer) {
if (owner.getLifecycle().getCurrentState() == DESTROYED) {
//LifecycleOwner處于銷毀狀態,直接返回。
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
類的內部類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;
//注釋1處,觀察LifecycleOwner的生命周期事件
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");
}
}
}
注釋1處,在BoundLocationListener的構造函數中,觀察LifecycleOwner的生命周期事件。然后使用注解在收到Lifecycle.Event.ON_RESUME
事件的時候添加位置監聽。在收到Lifecycle.Event.ON_RESUME
事件的時候移除位置監聽。
然后將生命周期持有者和生命周期事件觀察者綁定。這里忽略定位權限的處理。
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
參考鏈接