官方代碼糠亩,建議將代碼下載下來對照著代碼閱讀纬朝。
ViewModel
舉個例子:我們在界面上有一個計時器,記錄我們在這個界面停留的時間,但是當(dāng)我們旋轉(zhuǎn)屏幕的時候鞍爱,會導(dǎo)致Activity重新創(chuàng)建實例贞滨,onCreate()
方法會再次執(zhí)行,導(dǎo)致計時器會重新從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()方法都會重新設(shè)置base
chronometer.setBase(startTime);
chronometer.start();
}
}
當(dāng)然我們可以通過其他手段解決這個問題揪荣,例如當(dāng)屏幕旋轉(zhuǎn)的時候不讓Activity
重新創(chuàng)建實例筷黔。
或者我們可以在onSaveInstanceState()
方法中保存相應(yīng)的數(shù)據(jù),然后當(dāng)Activity
重新創(chuàng)建實例
的時候仗颈,我們在onCreate()
方法中獲取保存的數(shù)據(jù)佛舱,然后設(shè)置計時器的開始時間椎例。然后我們再
看看ViewModel的表現(xiàn)。
自定義一個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;
}
}
// 創(chuàng)建或者直接返回一個已經(jīng)存在的ViewModel
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this)
.get(ChronometerViewModel.class);
if (chronometerViewModel.getStartTime() == null) {
//chronometerViewModel如果沒設(shè)置過開始時間请祖,那么說明這個新的ViewModel,
//所以給它設(shè)置開始時間
long startTime = SystemClock.elapsedRealtime();
chronometerViewModel.setStartTime(startTime);
chronometer.setBase(startTime);
} else {
//否則ViewModel已經(jīng)在上個Activity的onCreate()方法中創(chuàng)建過了订歪,屏幕旋轉(zhuǎn)以后,
//ViewModel會被保存,我們直接獲取ViewModel里持有的時間
chronometer.setBase(chronometerViewModel.getStartTime());
}
chronometer.start();
這樣就可以解決屏幕旋轉(zhuǎn)以后重新從0開始計時的問題了肆捕。
我們看一下關(guān)鍵代碼
ChronometerViewModel chronometerViewModel = ViewModelProviders.of(this)
.get(ChronometerViewModel.class);
ViewModelProviders
的of()
方法刷晋,只看方法的注釋,不看方法體
/**
* 創(chuàng)建一個ViewModelProvider ,只要傳入的Activity存活慎陵,ViewModelProvider 就會被一直保留
* @param activity 一個activity, 在誰的生命周期內(nèi)眼虱,ViewModel會被保留
* @return 一個ViewModelProvider 實例
*/
@MainThread
public static ViewModelProvider of(@NonNull FragmentActivity activity) {
initializeFactoryIfNeeded(checkApplication(activity));
return new ViewModelProvider(ViewModelStores.of(activity), sDefaultFactory);
}
ViewModelProviders
的get()
方法,只看方法的注釋席纽,不看方法體
//返回一個已經(jīng)存在的ViewModel或者創(chuàng)建一個新的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
就結(jié)束了捏悬。可以通過Activity
的isFinishing()
方法來判斷润梯。我們發(fā)現(xiàn)过牙,在旋轉(zhuǎn)屏幕的時候isFinishing()
方法返回false。在按下返回鍵的時候isFinishing()
為true纺铭。
@Override
protected void onDestroy() {
super.onDestroy();
Log.d(TAG, "onDestroy: " + isFinishing());
}
ViewModel:ViewModel
是一個用來為Activity
或者Fragment
準(zhǔn)備和管理數(shù)據(jù)的類寇钉。ViewModel
也可以用來處理Activity/Fragment
和應(yīng)用其他部分的通信。
一個ViewModel
的創(chuàng)建總是和一個作用域(一個 Fragment/Activity
)有關(guān)舶赔,并且只要這個作用域存活摧莽,那么這個ViewModel
會被一直保留。例如顿痪,如果作用域是一個Activity
镊辕,那么ViewModel
會保留直到Activity
結(jié)束。
換句話說蚁袭,這意味著如果ViewModel
的所有者征懈,例如一個Activity
由于旋轉(zhuǎn)而被銷毀,但是ViewModel
并不會銷毀揩悄,新創(chuàng)建的Activity
的實例僅僅是重新關(guān)聯(lián)到已經(jīng)存在的ViewModel
卖哎。
ViewModel
存在的目的是為了獲取并保持對Activity/Fragment
重要的信息。Activity/Fragment
應(yīng)該能夠觀察到 ViewModel
的變化删性。ViewModel
通常通過LiveData
或者Data Binding
來暴露信息亏娜。也可以通過其他任何可觀察的對象,例如RxJava
中的ObserVable
蹬挺。
ViewModel
的唯一的作用是管理UI的數(shù)據(jù)维贺。ViewModel
不能訪問UI或者持有Activity/Fragment
的引用。
在Fragment之間共享ViewModel
舉個例子:在一個Activity
中有兩個Fragment
巴帮,每個Fragment
里面都有一個SeekBar
溯泣。當(dāng)其中一個SeekBar
進(jìn)度改變的時候虐秋,也更新另外一個Fragment
里面的SeekBar
的進(jìn)度。
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的實現(xiàn)
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的進(jìn)度就更新ViewModel
if (fromUser) {
Log.d("Step5", "Progress changed!");
mSeekBarViewModel.seekbarValue.setValue(progress);
}
}
@Override
public void onStartTrackingTouch(SeekBar seekBar) { }
@Override
public void onStopTrackingTouch(SeekBar seekBar) { }
});
// 當(dāng)ViewModel改變了時候客给,更新seekBar的進(jìn)度
mSeekBarViewModel.seekbarValue.observe(getActivity(), new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
}
}
運行程序,可以看到當(dāng)手動改變其中一個SeekBar
的進(jìn)度肢簿,另外一個也會跟著變
LiveData
上面說了靶剑,ViewModel
通常通過LiveData
或者Data Binding
來暴露信息。也可以通過其他任何可觀察的對象池充,例如RxJava
中的ObserVable
抬虽。LiveData
與普通的Observable
不同,LiveData
是生命周期感知的纵菌,這意味著它尊重其他應(yīng)用程序組件的生命周期阐污,例如Activity
,Fragment
或Service
咱圆。 LiveData
生命周期感知能力確保 LiveData
僅僅去更新那些處于生命周期活動狀態(tài)的觀察者笛辟。(感覺這個比較厲害了)
注意:正常情況應(yīng)該在onCreate中訂閱觀察者。如果在onStart或者onResume中訂閱會有問題:
- 比如當(dāng)前Activity onStop了序苏,然后從onStop重新onStart的時候又會訂閱一次手幢,導(dǎo)致重復(fù)訂閱。
接著上面的例子:
我們想在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() 不能再后臺線程調(diào)用匈睁,所以使用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);
}
}
運行上面的代碼你發(fā)現(xiàn)监透,只有在Activity
活動的時候(也就是生命周期狀態(tài)是STARTED
和RESUMED
的時候),log日志才會輸出航唆,當(dāng)你點擊HOME鍵或者打開其他的APP的時候胀蛮,log日志不會輸出。當(dāng)你回到Activity
的時候糯钙,日志才會接著輸出粪狼。
看一下LiveData
的observe()
方法
/**
* 將指定的觀察者添加到LifecycleOwner生命周期之內(nèi)的觀察者列表中。事件是在主線程分發(fā)的任岸。如果LiveData已經(jīng)被設(shè)置了
* 數(shù)據(jù)再榄,那么數(shù)據(jù)會被發(fā)送給這個新添加的觀察者。
*
* 只有當(dāng)LifecycleOwner處在活動狀態(tài)的時候{@link Lifecycle.State#STARTED} or
* {@link Lifecycle.State#RESUMED} 享潜,這個observer才會收到事件困鸥。
*
* 如果LifecycleOwner到了銷毀狀態(tài) {@link Lifecycle.State#DESTROYED},這個observer會被自動移除米碰。
*
* 當(dāng)這個LifecycleOwner處于不活動的狀態(tài)的時候窝革,如果數(shù)據(jù)改變了,這個observer不會收到任何更新吕座。
* 當(dāng)LifecycleOwner重新回到了active的狀態(tài)虐译,這個oberver會自動收到最新的數(shù)據(jù)。
*
* 只要指定的LifecycleOwner沒有被銷毀吴趴,LiveData就一直持有observer和LifecycleOwner的強引用
* 當(dāng)LifecycleOwner被銷毀了漆诽,LiveData會移除observer和LifecycleOwner的引用。
*
* 如果指定的LifecycleOwner已經(jīng)處于銷毀狀態(tài){@linkLifecycle.State#DESTROYED} 锣枝,方法直接返回厢拭。
*
* 如果指定的LifecycleOwner和oberver元組已經(jīng)在觀察這列表里了,方法直接返回撇叁。
*
* 如果observer已經(jīng)和另外一個關(guān)聯(lián)的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處于銷毀狀態(tài),直接返回趋厉。
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
實現(xiàn)了LifecycleOwner
君账。所以AppCompatActivity
是一個生命周期持有者繁堡。
public class SupportActivity extends Activity implements LifecycleOwner {
...
}
訂閱生命周期事件
我們已經(jīng)知道SupportActivity
是一個生命周期持有者了。一個生命周期持有者會在不同的生命周期發(fā)出不同的生命周期事件乡数。我們可以觀察這些事件椭蹄,并根據(jù)這些事件,進(jìn)行基于生命周期的操作净赴。
LifecycleOwner
獲取Lifecycle
的方法
public interface LifecycleOwner {
/**
* 返回當(dāng)前生命周期持有者的生命周期
*/
@NonNull
Lifecycle getLifecycle();
}
Lifecycle
類用來定義具有Android生命周期的對象塑娇。
Lifecycle
類的內(nèi)部類Lifecycle.Event
是一個枚舉類,定義了生命周期持有者發(fā)出的所有事件類型
枚舉值 | 描述 |
---|---|
ON_ANY | 可以用來匹配任何事件 |
ON_CREATE | onCreate事件 |
ON_DESTROY | onDestroy事件 |
ON_PAUSE | onPause事件 |
ON_RESUME | onResume事件 |
ON_CREATE | onCreate事件 |
ON_START | onStart事件 |
ON_STOP | onStop事件 |
Lifecycle
類的內(nèi)部類Lifecycle.State
也是一個枚舉類劫侧,定義了生命周期持有者所有的生命周期狀態(tài)埋酬。如下圖所示。
舉個例子:
我們想在Activity
活動的時候烧栋,注冊一個LocationListener
來獲取位置信息写妥,然后在onPause
的時候,移除監(jiān)聽器审姓,那我們可以通過Activity
的生命周期事件來實現(xiàn)珍特。
自定義的LocationListener
private class MyLocationListener implements LocationListener {
@Override
public void onLocationChanged(Location location) {
//位置改變的時候,改變界面上的經(jīng)緯度
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) {
}
}
要觀察生命周期事件魔吐,首先要實現(xiàn)LifecycleObserver
接口扎筒。
BoundLocationListener
類實現(xiàn)了 LifecycleObserver
接口莱找,當(dāng)生命周期持有者LifecycleOwner
處于ON_RESUME的狀態(tài)的時候,我們獲取定位服務(wù)嗜桌,并在位置變化的時候通知LocationListener
更新信息奥溺。當(dāng)生命周期持有者LifecycleOwner
處于ON_PAUSE的狀態(tài)的時候我們移除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的構(gòu)造函數(shù)中,觀察LifecycleOwner的生命周期事件层亿。然后使用注解在收到Lifecycle.Event.ON_RESUME
事件的時候添加位置監(jiān)聽桦卒。在收到Lifecycle.Event.ON_RESUME
事件的時候移除位置監(jiān)聽。
然后將生命周期持有者和生命周期事件觀察者綁定匿又。這里忽略定位權(quán)限的處理方灾。
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());
}
}
運行程序,不斷旋轉(zhuǎn)手機的時候碌更,輸出如下
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
參考鏈接