簡介
Android Architecture components 是一組 Android 庫布卡,它可以幫助我們以一種健壯的拙寡、可測(cè)試和可維護(hù)的方式構(gòu)建 APP救斑。
注意: 這個(gè)架構(gòu)目前仍處于 alpha 開發(fā)狀態(tài)稍走。在正式版發(fā)布之前 API 可能會(huì)改變原探,你可能會(huì)遇到穩(wěn)定性和性能問題。
下面將會(huì)介紹使用生命周期感知(lifecycle-aware)組件去構(gòu)建APP:
-
ViewModel - 提供了創(chuàng)建和檢索(類似于單例)綁定到特定生命周期對(duì)象的方法蚯撩,
ViewModel
通常是存儲(chǔ)View
數(shù)據(jù)的狀態(tài)并與其他的組件進(jìn)行通信础倍,如數(shù)據(jù)存儲(chǔ)組件和處理業(yè)務(wù)邏輯的組件。想了解更多胎挎,請(qǐng)查看 ViewModel 指南沟启。
-
LifecycleOwner/LifecycleRegistryOwner -
LifecycleOwner
和LifecycleRegistryOwner
都是在LifecycleActivity
和LifecycleFragment
類中實(shí)現(xiàn)的接口。你可以將自己的組件也實(shí)現(xiàn)這些接口以觀察生命周期所有者的狀態(tài)變化犹菇。想了解更多德迹,請(qǐng)查看 Lifecycles 指南。
-
LiveData - 允許您觀察應(yīng)用程序的多個(gè)組件的數(shù)據(jù)更改揭芍,而不會(huì)在它們之間創(chuàng)建明確的胳搞,剛性的依賴路徑。
LiveData
關(guān)心應(yīng)用程序組件復(fù)雜的生命周期称杨,包括Activity
,Fragmnet
,Service
或者 APP 中定義的任何LifecycleOwner
肌毅。LiveData
會(huì)管理觀察者的訂閱狀態(tài),LifecycleOwner
對(duì)象stopped
時(shí)會(huì)暫停訂閱姑原,以及LifecycleOwner
對(duì)象Finished
時(shí)會(huì)取消訂閱悬而。想了解更多,請(qǐng)查看 LiveData 指南锭汛。
如果你已經(jīng)安裝了 Android Studio 2.3 或者更高版本并且熟悉 Activity 生命周期 笨奠,那么你可以繼續(xù)向下看了。
配置環(huán)境
下載源碼唤殴,并導(dǎo)入 Android Studio
運(yùn)行 【Step 1】般婆,界面大概是下面這樣的
- 旋轉(zhuǎn)屏幕,計(jì)時(shí)器會(huì)從0開始重新計(jì)時(shí)朵逝。
這部分代碼比較簡單腺兴,首先是布局文件:
<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
函數(shù)中開始計(jì)時(shí)即可,如下:
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 提供的一個(gè)計(jì)時(shí)器組件廉侧,該組件繼承自 TextView
页响,它顯示從某個(gè)起始時(shí)間開始過去了多少時(shí)間。
想要在屏幕旋轉(zhuǎn)時(shí)計(jì)時(shí)器狀態(tài)不被更改段誊,您可以使用 ViewModel
闰蚕,因?yàn)榇祟惪梢栽谂渲酶模ɡ缙聊恍D(zhuǎn))時(shí)不會(huì)被回收。
注: 在
mainfest
文件中可以配置Activity
在屏幕旋轉(zhuǎn)時(shí)不重新創(chuàng)建Activity
连舍,從而保持計(jì)時(shí)器的狀態(tài)没陡,這種情況不在本篇談?wù)摲秶鷥?nèi)。
注:如源碼中的 google maven 不能訪問,可將項(xiàng)目中 gradle 文件改為如下
repositories { jcenter() maven { url "https://dl.google.com/dl/android/maven2/" } }
添加 ViewModel
在此步驟中盼玄,您可以使用 ViewModel
在屏幕旋轉(zhuǎn)之間保持狀態(tài)贴彼,并解決您在上一步中觀察到的行為(旋轉(zhuǎn)屏幕時(shí)計(jì)時(shí)器會(huì)重置)。在上一步中埃儿,運(yùn)行了一個(gè)顯示計(jì)時(shí)器的 Activity
器仗。當(dāng)配置更改(如屏幕旋轉(zhuǎn))會(huì)導(dǎo)致 Activity
被銷毀,此定時(shí)器將重置童番。
您可以使用 ViewModel
在 Activity
或 Fragment
的整個(gè)生命周期中保留數(shù)據(jù)精钮。如上一步所示,使用 Activity
來管理 APP 的數(shù)據(jù)是不明智的剃斧。 Activity
和 Fragment
是一種短命的對(duì)象轨香,它們隨著用戶與應(yīng)用程序交互而頻繁創(chuàng)建和銷毀。 ViewModel
還適用于管理與網(wǎng)絡(luò)通信相關(guān)的任務(wù)幼东,以及數(shù)據(jù)的操作和持久化臂容。
使用ViewModel來保持計(jì)時(shí)器的狀態(tài)
首先需要?jiǎng)?chuàng)建一個(gè)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 會(huì)提供一個(gè)新的或者之前已經(jīng)創(chuàng)建過的 ViewModel
ChronometerViewModel chronometerViewModel
= ViewModelProviders.of(this).get(ChronometerViewModel.class);
// 獲取 chronometer
Chronometer chronometer = (Chronometer) findViewById(R.id.chronometer);
if (chronometerViewModel.getStartDate() == null) {
// 如果開始時(shí)間為 null , 那么這個(gè) ViewModel 是剛被創(chuàng)建的
long startTime = SystemClock.elapsedRealtime();
chronometerViewModel.setStartDate(startTime);
chronometer.setBase(startTime);
} else {
// 否則這個(gè) ViewModel 是從 ViewModelProviders 中被檢索出來的策橘,所以需要設(shè)置剛開始的開始時(shí)間
chronometer.setBase(chronometerViewModel.getStartDate());
}
chronometer.start();
}
}
其中
ChronometerViewModel chronometerViewModel
= ViewModelProviders.of(this).get(ChronometerViewModel.class);
this
是 LifecycleOwner
的實(shí)例。 只要 LifecycleOwner
的范圍是活躍的(alive)娜亿,框架就可以使 ViewModel
處于活躍狀態(tài)。如果其所有者因?yàn)榕渲酶模ɡ缙聊恍D(zhuǎn))而被毀壞(destroyed)蚌堵,ViewModel
則不會(huì)被銷毀买决。生命周期所有者會(huì)重新連接到現(xiàn)有的 ViewModel
,如下圖所示:
注意:
Activity
或Fragment
的范圍從created
到finished
(或終止)吼畏,您不能與被destroyed
混淆督赤。請(qǐng)記住,當(dāng)一個(gè)設(shè)備旋轉(zhuǎn)時(shí)泻蚊,Activity
被銷毀躲舌,但是與它關(guān)聯(lián)的任何ViewModel
的實(shí)例并不會(huì)被銷毀。
試試看
運(yùn)行應(yīng)用程序并確認(rèn)執(zhí)行以下任一操作時(shí)定時(shí)器不會(huì)重置:
- 旋轉(zhuǎn)屏幕性雄。
- 導(dǎo)航到另一個(gè)應(yīng)用程序没卸,然后返回。
但是秒旋,如果你或系統(tǒng)退出應(yīng)用程序约计,則定時(shí)器將重置。
注意: 系統(tǒng)會(huì)在生命周期所有者(例如
Activity
或Fragment
)的整個(gè)生命周期中將ViewModel
實(shí)例保存在內(nèi)存中 迁筛。 系統(tǒng)不會(huì)將ViewModel
的實(shí)例持久化到長期存儲(chǔ)煤蚌。
使用 LiveData 包裝數(shù)據(jù)
這一步使用自定義的 Timer
實(shí)現(xiàn)上一步的中的 chronometer
。將上面的邏輯添加到 LiveDataTimerViewModel
類中,將 Activity
的主要功能放在管理用戶和UI之間的交互上尉桩。
Activity
會(huì)在定時(shí)器通知時(shí)更新UI筒占。為了避免內(nèi)存泄露, ViewModel
不應(yīng)該持有 Activity 的實(shí)例蜘犁。比如翰苫,配置更改(例如屏幕旋轉(zhuǎn))會(huì)使 Activity 被系統(tǒng)回收,如果這時(shí) ViewModel
仍持有Activity 的實(shí)例那么 Activity 就不會(huì)被系統(tǒng)回收從而導(dǎo)致內(nèi)存泄露 沽瘦。系統(tǒng)會(huì)保留 ViewModel
的實(shí)例至到 Activity
或生命周期持有者不再存在革骨。
注意: 在
ViewModel
中持有 Context 或者 View 的實(shí)例可能會(huì)導(dǎo)致內(nèi)存泄露。避免引用 Context 或者 View 類的實(shí)例的字段析恋。ViewModel
中的 onCleared() 方法可用于取消訂閱或清除對(duì)具有較長生命周期的其他對(duì)象的引用良哲,但不用于清除 Context 或者 View 對(duì)象的引用。
可以設(shè)置 Activity
或 Fragment
來觀察數(shù)據(jù)源助隧,當(dāng)數(shù)據(jù)更改時(shí)將會(huì)接收通知筑凫,而不是直接從 ViewModel
修改視圖。這就是觀察者模式并村。
注意: 要想將數(shù)據(jù)可被觀察巍实,可將數(shù)據(jù)包裝在LiveData類中。
如果你使用過 Data Binding Library 或其他響應(yīng)式庫(如RxJava)哩牍,你應(yīng)該很熟悉觀察者模式棚潦。LiveData
是一個(gè)特殊的可觀測(cè)類,它可以感知生命周期膝昆,并且只在聲明周期活躍時(shí)通知觀察者丸边。
LifecycleOwner
ChronoActivity3
是 LifecycleActivity
的子類,他提供了聲明周期的狀態(tài)荚孵。如下:
public class LifecycleActivity extends FragmentActivity implements LifecycleRegistryOwner {...}
LifecycleRegistryOwner
接口的作用是用來將 ViewModel
和 LiveData
綁定到 Activity
或 Fragment
生命周期中妹窖。如果Fragment 的話可以繼承 LifecycleFragment
類。
Update ChronoActivity
- 在
ChronoActivity3
中添加如下代碼收叶,在subscribe()
方法中創(chuàng)建訂閱者:
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() {
// 創(chuàng)建觀察者
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
中設(shè)置計(jì)時(shí)時(shí)間骄呼,如下:
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() 不允許在子線程中調(diào)用,所以需要post主線程執(zhí)行
// 其實(shí)也可以直接使用 postValue() 方法判没,該方法會(huì)在內(nèi)部post到主線程執(zhí)行
new Handler(Looper.getMainLooper()).post(new Runnable() {
@Override
public void run() {
//setValue時(shí)蜓萄,觀察者可監(jiān)聽到值的變化
mElapsedTime.setValue(newValue);
}
});
}
}, ONE_SECOND, ONE_SECOND);
}
public LiveData<Long> getElapsedTime() {
return mElapsedTime;
}
}
- 運(yùn)行 APP 你會(huì)發(fā)現(xiàn)界面每秒都在更新,除非你導(dǎo)航到另外一個(gè) APP澄峰。如果你的設(shè)備支持多窗口绕德,或者旋轉(zhuǎn)屏幕并不會(huì)影響APP顯示。如下圖:
注意:
LiveData
只有在 Activity 或者LifecycleOwner
活躍(active)時(shí)才發(fā)送更新摊阀。如果你導(dǎo)航到其他的APP耻蛇,Activity 中的日志打印將會(huì)停止踪蹬,至到你重新回到APP。當(dāng)LiveData
對(duì)象的各自的生命周期所有者處于STARTED
或者RESUMED
臣咖,我們才稱LiveData
是活躍的跃捣。
訂閱生命周期事件
許多 Android 組件或庫要求你
- 訂閱或初始化組件或庫
- 取消訂閱或 stop 組件或庫
沒有做上面的步驟可能會(huì)導(dǎo)致內(nèi)存泄露或者產(chǎn)生bugs。
生命周期所有者可以傳遞給生命周期感知組件生命周期的狀態(tài)夺蛇,以確保他們知道生命周期的當(dāng)前狀態(tài)疚漆。
你可以用下面的方法查詢生命周期的狀態(tài):
lifecycleOwner.getLifecycle().getCurrentState()
上面的語句返回的是什么周期的狀態(tài),如 Lifecycle.State.RESUMED
, 或 Lifecycle.State.DESTROYED
刁赦。
實(shí)現(xiàn) LifecycleObserver
接口的生命周期感知對(duì)象還可以觀察生命周期所有者的狀態(tài)的變化:
lifecycleOwner.getLifecycle().addObserver(this);
可以通過注解的方式娶聘,實(shí)現(xiàn)在各個(gè)生命周期事件執(zhí)行相應(yīng)的方法:
@OnLifecycleEvent(Lifecycle.EVENT.ON_RESUME)
void addLocationListener() { ... }
創(chuàng)建生命周期感知組件
在這里我們會(huì)創(chuàng)建一個(gè)對(duì) Activity 生命周期所有者做出響應(yīng)的組件,使用 Fragment 作為生命周期所有者時(shí)甚脉,可以采用類似的原則和步驟丸升。
使用 Android framework 的 LocationManager
去獲取經(jīng)緯度并且展示給用戶,這允許你
訂閱更改并使用
LiveData
自動(dòng)更新UI牺氨。創(chuàng)建一個(gè)包裝器狡耻,他可以根據(jù) Activity 生命狀態(tài)的來決定是注冊(cè)還是注銷對(duì)LocationManager的監(jiān)聽。
通常的做法是在 Activity 的 onStart()
或 onResume()
中注冊(cè)監(jiān)聽器猴凹,在 onStop()
或 onPause()
方法中移除監(jiān)聽器夷狰,如下:
// 在 activity 中通常這樣做
@Override
protected void onResume() {
mLocationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, mListener);
}
@Override
protected void onPause() {
mLocationManager.removeUpdates(mListener);
}
看下我們是如何改造上面代碼的,首先我們需要?jiǎng)?chuàng)建一個(gè) BoundLocationManager
類郊霎,它需要實(shí)現(xiàn) LifecycleOwner
接口并且需要傳入 LifecycleRegistryOwner
的實(shí)例沼头。 BoundLocationManager
類的實(shí)例綁定到 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 實(shí)例監(jiān)聽到生命周期
lifecycleOwner.getLifecycle().addObserver(this);
}
//可以使用 @OnLifecycleEvent 注解來監(jiān)聽 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");
}
}
注意: 觀察者可以觀測(cè)到提供者的當(dāng)前狀態(tài),所以不需要從構(gòu)造函數(shù)調(diào)用
addLocationListener()
方法庄撮。他是在觀察者添加到生命周期所有者的時(shí)候被調(diào)用的。
運(yùn)行APP毙籽,在旋轉(zhuǎn)屏幕的時(shí)候會(huì)有下面的打印
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
D/BoundLocationMgr: Listener added
D/BoundLocationMgr: Listener removed
使用 Android 模擬器改變?cè)O(shè)備的位置洞斯,UI 就會(huì)更新,如下圖:
在 Fragmnet 之間共享 ViewModel
在 Fragmnet 之間共享 ViewModel 之前坑赡,我們需要做一些前期準(zhǔn)備
首先需要?jiǎng)?chuàng)建一個(gè) Activity 烙如,該 Activity 中包含兩個(gè) Fragment,在其布局文件中添加兩個(gè) 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亚铁,這個(gè) Fragment 也很簡單,只是包含了一個(gè) 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>
然后就是創(chuàng)建一個(gè)包含 LiveData 的 ViewModel徘溢,如下:
public class SeekBarViewModel extends ViewModel {
public MutableLiveData<Integer> seekbarValue = new MutableLiveData<>();
}
運(yùn)行這一步中的代碼吞琐,你會(huì)發(fā)現(xiàn)這是兩個(gè)彼此獨(dú)立的SeekBar實(shí)例:
使用 ViewModel
在 Fragment 之間通信,以便當(dāng)一個(gè) SeekBar
更改時(shí)然爆,另一個(gè) SeekBar
將被更新:
使用 ViewMode 進(jìn)行通信的代碼如下:
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() {
// 當(dāng) SeekBar 變化的時(shí)候更新 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) {
}
});
// 當(dāng) ViewModel 變化的時(shí)候更新 SeekBar
mSeekBarViewModel.seekbarValue.observe((LifecycleOwner) getActivity(),
new Observer<Integer>() {
@Override
public void onChanged(@Nullable Integer value) {
if (value != null) {
mSeekBar.setProgress(value);
}
}
});
}
}
注意: 你應(yīng)該使用 Activity 作為生命周期的持有者站粟,因?yàn)槊總€(gè) Fragment 的生命周期都是獨(dú)立的。
至此曾雕,你應(yīng)該對(duì) AAC 有一個(gè)初步的認(rèn)識(shí)了奴烙。