前言
Android Architecture Components,簡稱 AAC ,是 Google IO 2017 大會新推出的 app 架構,從使用感受上說是對 MVVM 的官方加強,AAC 可以解決我們常見的一些內存泄露的場景使用,添加了更易操作的 view 生命周期管理。
拓展一下,MVC / MVP / MVVM 這種代碼架構組織方式都是基于:UI 驅動數據 ,我們常用的 EventBus 框架則是:數據驅動 UI。數據和 UI 誰驅動誰,這其實就是我們架構的核心,可以決定我們如何組織代碼機構,如何具體的書寫代碼。
仔細想了2天,我說下自己的體會:
UI 驅動數據
我管這種方式叫:主動持有型。大家仔細想想,我們在頁面中 findviewbyid 持有控件的對象,然后給UI 控件注冊交互方法,頁面對象持有 Persenter 邏輯對象,Persenter 邏輯持有數據層對象,這一層層對象之間都是通過上游對象調用下游對象的方法,接受數據或是傳入 callback 回調的方式,實現這一層層上下游對象之間的交互的,這一層層上下游對象擁有不同的生命周期,相互支持,就容易造成內存泄露,最大的問題是對象類型的強依賴,代碼解耦不容易,我們在做組件化很麻煩,需要寫大量的公共中間通訊接口,有時候還不一定能解決強依賴的問題。我覺得不太適合如今 越來越大型的 app 開發和公司內部多 app 聯動開發的需求了。數據驅動 UI
我管這種方式叫:事件驅動型。我們在使用 EventBus 時發送一個 info 數據對象出去就行,我們不管有多少對象會處理這個數據對象,如果有10個對象會消費這個數據讀寫,那么在 EventBus 架構中我們不用持有這10個對象,就避免了對象類型的強耦合,給我們使用組件化帶來了巨大的便利,這就是我們組件化,甚至插件化改造當前項目的基石。一個 info 數據對象就是一個事件,這樣看我們就是用數據來驅動 UI 了。
之前我頁嘗試過使用 EventBus 來搭建我的事件驅動型 app 架構,嘗試過會發現問題很多,info 類爆炸,內存泄露等問題很難解決,不是我一個人恩那個解決的。但是今天我們帶來的這個官方的 AAC 架構 ,就是基于事件驅動型的新的架構體系,大大解決了我們在組件化過程中的痛點,可以實現高度解耦。
大家可以試想,EventBus 是一個中間件,一個可以在所有 module 中通用的,EventBus 內部維護了所有的消費 info 數據的消費對象,EventBus.getInstance() 我們可以注冊消費對象,這就簡單的實現了 module 之間的解耦,module A 提供數據,不論游多少 module 去消費這個 info 數據,module A 都可以不關心,頁不用持有這些消費 module 的引用。這就是基于事件的 app 架構的基礎,之后不論游多復雜的架構,都是基于這個思想的,基于事件的架構核心痛點就是:高度解耦。具體應用應用就是為組件化,插件化掃平障礙
大家也可以看看我摘錄別人博客的一些思路:
我說的也是我自己個人的認識,有異議歡迎大家在下面噴我啊,一定要噴啊......
學習資料
AAC 的是去年出的,第一時間獲得了高關注,奈何本人小白一個,現在才剛剛看到這個 AAC 架構,網上的學習資料很多,基礎的部分大家還是詳細去看看我貼出來的資料,我就簡單的總結一下知識點,然后會重點說一下我的認識。
基礎學習資料:
- AAC(Android Architecture Components)
- 淺談Android Architecture Components
- Android 項目最新架構
- Android 架構組件 Room 介紹及使用
- Android 應用架構組件(Architecture Components)實踐
官方demo:
-
BasicSample
演示如何使用a SQLite database 和 Room做持久化,同時也使用了ViewModel和LiveData。 -
PersistenceContentProviderSample
演示如何使用Room通過 Content Provider暴露數據。 -
GithubBrowserSample
一個使用 Architecture component,Dagger以及Github API的高級示例,需要Android Studio 2.4。
官方視頻學習資源:
-
架構組件之 ViewModel | 中文教學視頻
官方稱 Lifecycles 這3個組件為生命周期管理組件,我覺得已經不僅僅是生命周期管理了,而是把 app 架構改造成響應式架構了,整個 app 的事件和數據流動完全基于 Observable 和 Observer 的訂閱來實現。觀官方問法當所言,ViewModel 是解決 UI 和數據之間的邏輯,但是不宜讓 ViewModel 處理過多的頁面邏輯,若是頁面邏輯和設置比較復雜,Persenter 還是有必要存在的。
開源學習 demo:
-
awaker
一個挺不錯的 demo ,很詳細的使用了 AAC 框架,代碼封裝的不錯。個人感官,AAC 框架使用的有些畏手畏腳,很中規中矩,AAC 參與獲取數據,刷新頁面顯示,沒有探討更深入的用 AAC 把 app 架構向數據,事件流方向修改。另外數據層中有些職責不清晰,在 respositroy 在最外層類內封裝了 respositroyHelp 具體頁面實現層的情況下, respositroy 最外層類還是涉及到一些具體的業務邏輯,另外 SQL 數據庫層不是和網絡層同一個級別的,頁面有管理層對象。但是總體來說,還是不錯的, 尤其是 數據層適合一看,查漏補缺,看別人的實現完善自己的實現。 -
ArchitecturePractice
這個 demo 比較簡單,數據層頁很簡單,但是數據層層次很清晰,適合看過上面的 demo 再來看看這個反思一下數據層到底應該怎么寫。 - 使用Android Architecture Component開發應用(附demo)
本文 demo:
-
BW_AAC_Demo
看點是用 LiveData 改造了一下 repositroy 數據層,UI 層通過 ViewModule 獲取這個 LiveData,建立通道聯系刷新數據。簡單用 RXJava 延遲 2 秒發送一條數據,模擬一下網絡請求。
補充資料:
-
[譯] Architecture Components 之 Guide to App Architecture
翻譯的 AAC 官方文檔,詳細講解了官方的學習 demo,里面是一個系列博客,詳細分節講解了 AAC 的每個部分。各位要是看我的文章哪里看糊涂了,不妨去這里尋找答案。 -
安卓架構組件-分頁庫
這個有意思啊,使用 AAC 組件開發列表分頁加載,作者使用 PagerList 來管理分頁狀態,隔壁根據分頁狀態從 DataSource 中獲取數據,作者使用數據庫舉例子的,remote 遠程資源同樣也可以,值得一看。 - 淺析MVP中model層設計【從零開始搭建android框架系列(7)】
- Android 生命周期架構組件與 RxJava 完美協作
AAC 主要內容
-
Lifecycle
生命周期管理,把原先Android生命周期的中的代碼抽取出來,如將原先需要在onStart()等生命周期中執行的代碼分離到Activity或者Fragment之外。 -
LiveData
一個數據持有類,持有數據并且這個數據可以被觀察被監聽,和其他Observer不同的是,它是和Lifecycle是綁定的,在生命周期內使用有效,減少內存泄露和引用問題。 -
ViewModel
代替 persenter 的角色,生命周期長于 activity / fragment ,一種新的保存數據的方式。同時是與Lifecycle綁定的,使用者無需擔心生命周期。可以在多個Fragment之間共享數據,比如旋轉屏幕后Activity會重新create,這時候使用ViewModel還是之前的數據,不需要再次請求網絡數據。 -
Room
Google 推出的一個Sqlite ORM庫,不過使用起來還不錯,使用注解,極大簡化數據庫的操作,有點類似Retrofit的風格。
上面4個就是這次 AAC 架構的核心 API 了,通過這幾個 API 我們可以搭建一套基于事件的 app 架構出來,LiveData 和其他一些 API 可以簡單的看做是 Google 版的 RXJAVA ,只不過是針對 android 系統的,功能上也是很簡單,沒有 RXJAVA 那么強大的變換和線程控制。但是這正是我們所需要的,因為有了 RXJAVA ,我們在復雜的業務場景中用 RXJAVA 就好了,LiveData 使用簡單,學習成本低,我們用來搭建基于數據流的響應式架構體系的 app 是最合適的。
AAC 架構圖如下:
添加依賴
allprojects {
repositories {
jcenter()
maven { url 'https://maven.google.com' } //添加此行
}
}
//For Lifecycles, LiveData, and ViewModel
implementation 'android.arch.lifecycle:common-java8:1.1.1'
implementation 'android.arch.lifecycle:extensions:1.1.1'
implementation 'android.arch.lifecycle:runtime:1.1.1'
//For Room
compile "android.arch.persistence.room:runtime:1.1.0-alpha1"
annotationProcessor "android.arch.persistence.room:compiler:1.1.0-alpha1"
// For Room RxJava support, add:
compile "android.arch.persistence.room:rxjava2:1.1.0-alpha1"
上面的依賴使用的是 Java8,不用添加 apt 或者 kapt 或者 annotationProcessor,但是需要設置 compileOptions
android {
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
}
Lifecycle
很多文章說的廢話多,不直接, Android 頁面生命周期加強,把頁面的生命周期變成一個 Observable ,這個 Observable 我們無法直接使用,而是配合注解寫在需要 hook 生命周期的位置,最大的好處就是脫離了 callback 或是 Proxy 代理類,在代碼上靈活太多了,Lifecycle 會掃描所有代碼中有標記的方法,然后注冊給生命周期的 Observable 對象里面去,這種寫法是不是和 EventBus 一樣啊。最直接的在寫代碼時這就是 基于事件的魅力,我們不用再耗費心神設計代碼結構,寫那些 callback / Proxy 代理類了。
所以 AAC 這個基于事件的架構是學起來最通透的,一切都是 Observable / Observer,都是觀察者和被觀察者的關系,一切都是基于注冊的方式運行。
既然一切都是 Observable / Observer 的,那么管理頁面生命周期的 Observable / Observer 哪里來呢,這就要說到 LifecycleActivity / LifecycleFragment 了,我是用 API 26做基準編譯代碼的,AppCompatActivity / V4 包下的 Fragment 都已經兼容了 AAC 架構了,所以 LifecycleActivity / LifecycleFragment 過時了,大家注意一下:
然后我們通過這個 API 可以獲取這個 Observable Lifecycle
Lifecycle lifecycle = getLifecycle();
那么這個 Observer 呢,我們直接 new 一個 LifecycleObserver 對象出來也行,或是某個類實現這個接口也行,最后注冊到 Observable 就行,下面看代碼:
MyLifecyleTextView 實現 LifecycleObserver 接口
@SuppressLint("AppCompatCustomView")
public class MyLifecyleTextView extends TextView implements LifecycleObserver {
public MyLifecyleTextView(Context context) {
super(context);
}
public MyLifecyleTextView(Context context, @Nullable AttributeSet attrs) {
super(context, attrs);
}
public MyLifecyleTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr, int defStyleRes) {
super(context, attrs, defStyleAttr, defStyleRes);
}
public MyLifecyleTextView(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
super(context, attrs, defStyleAttr);
}
@OnLifecycleEvent(Lifecycle.Event.ON_CREATE)
public void creat() {
Log.d("AAA", "oncreat...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void start() {
Log.d("AAA", "onstart...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void resume() {
Log.d("AAA", "onresume...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void pasue() {
Log.d("AAA", "onpause...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stop() {
Log.d("AAA", "onstop...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void destroy() {
Log.d("AAA", "ondestroy...");
}
}
綜合使用
public class MainActivity extends AppCompatActivity {
private MyLifecyleTextView tx_lifectle;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tx_lifectle = findViewById(R.id.tx_lifecley);
getLifecycle().addObserver(tx_lifectle);
getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void start() {
Log.d("AAA", "new_onstart...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME)
public void resume() {
Log.d("AAA", "new_onresume...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE)
public void pasue() {
Log.d("AAA", "new_onpause...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stop() {
Log.d("AAA", "new_onstop...");
}
@OnLifecycleEvent(Lifecycle.Event.ON_DESTROY)
public void destroy() {
Log.d("AAA", "new_ondestroy...");
}
});
}
}
上面的代碼基本包含了 Lifecycle 的常用應用場景了,不管你事讓某個自定義 view hook 某個生命周期也行,還是直接注冊一個觀察者對象也行,在代碼上我們完全脫離了頁面生命周期函數里,頁面或是 penserent ,viewmodule 都不用再保存相關的 callback ,proxy,listener 在頁面的生命周期函數里執行了,在 app 代碼層面實現了基于觀察者模式的代碼思路。減少強制持有屬性,功能,代理對象,這也是一種解耦啊,和以前對象.方法比起來,代碼是越寫越靈活啊,這就是趨勢啊。
好了,上面 XBB 了一下,Lifecycle 還沒說完呢,我們了解了 Lifecycle 如何使用,代碼里我們可以看到有很多帶注解的函數
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
public void stop() {
Log.d("AAA", "new_onstop...");
}
既然說了 Lifecycle 像 EventBus ,那么大家就不會對這注解函數陌生了吧,我來說說這個 Lifecycle.Event.ON_STOP 。API 的名字起來很規矩,大家一看應該就知道了,ON_STOP 就是代表了 onStop() 這個生命周期函數了,以此類推就不用多重復了
另外通過 Lifecycle 我們還可以獲取當前 view 的生命周期狀態,這在之前是需要自己去維護的,有了這個 API 我們在很多時候會方便很多啊。
Lifecycle.State currentState = getLifecycle().getCurrentState();
if( currentState == Lifecycle.State.STARTED ){
xxxxxxxxx
}
另外官方有 Lifecycle 對應的生命周期圖,大家看看
Lifecycle 注冊的生命周期回調方法需要說一下是即使生效的,這點必須要說清楚,不說有的童鞋可能會以為第一次無效,其實我們想想,對于聲明周期來說,第一次無效是不符合設計思路和場景需求的。
Lifecycle 的最后我得數據說一下這個接口 LifecycleRegistryOwner ,AppCompatActivity / V4 包下的 Fragment 都是通過實現這個接口來兼容 AAC 架構的,其實這個接口很簡單,里買就一個方法,需要我們返回 Lifecycle 的核心功能類 LifecycleRegistry 即可。看代碼,自己實現 Lifecycle :
import android.arch.lifecycle.LifecycleRegistry;
import android.arch.lifecycle.LifecycleRegistryOwner;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
public class MainActivity extends AppCompatActivity implements LifecycleRegistryOwner {
private LifecycleRegistry lifecycleRegistry = new LifecycleRegistry(this);
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
proxy.zj.com.lifecycledemo.MyTv mTv = findViewById(R.id.dfttv);
mTv.setLifecycle(getLifecycle());
getLifecycle().addObserver(mTv);
mTv.setLifeCycleEnable(true);
}
@Override
public LifecycleRegistry getLifecycle() {
return lifecycleRegistry;
}
}
其實我們用不到 LifecycleRegistryOwner 這個借口,但是為啥要說呢,系統只是在 Activity 和 Fragment 層面兼容了 AAC ,我們要是需要自己的自定義 view 對外提供聲明周期管理的話記得自己實現了。按著上面的代碼來就行,很簡單,一看就會,不會來找我。
摘一段官方文檔的翻,出自:[譯] Architecture Components 之 Handling Lifecycles
Lifecycles 的最佳實踐
保持 UI 控制器(Activity 和 Fragment)盡可能的精簡。它們不應該試圖去獲取它們所需的數據;相反,要用 ViewModel 來獲取,并且觀察 LiveData 將數據變化反映到視圖中。
嘗試編寫數據驅動(data-driven)的 UI,即 UI 控制器的責任是在數據改變時更新視圖或者將用戶的操作通知給 ViewModel。
將數據邏輯放到 ViewModel 類中。ViewModel 應該作為 UI 控制器和應用程序其它部分的連接服務。注意:不是由 ViewModel 負責獲取數據(例如:從網絡獲取)。相反,ViewModel 調用相應的組件獲取數據,然后將數據獲取結果提供給 UI 控制器。
使用 Data Binding 來保持視圖和 UI 控制器之間的接口干凈。這樣可以讓視圖更具聲明性,并且盡可能減少在 Activity 和 Fragment 中編寫更新代碼。如果你喜歡在 Java 中執行該操作,請使用像 Butter Knife 這樣的庫來避免使用樣板代碼并進行更好的抽象化。
如果 UI 很復雜,可以考慮創建一個 Presenter 類來處理 UI 的修改。雖然通常這樣做不是必要的,但可能會讓 UI 更容易測試。
不要在 ViewModel 中引用 View 或者 Activity 的 context。因為如果 ViewModel 存活的比 Activity 時間長(在配置更改的情況下),Activity 將會被泄漏并且無法被正確的回收。
liveData
liveData 簡單來說就是一個可以根據觀察者自身生命周期,在觀察者需要結束時自動解綁的 Observable,并且結合了 DataBingding 的特點,liveData 自身數據改變時可以通知所有的觀察者對象。哈哈,所以說完上面 Lifecycle 才能來看 liveData ,這個生命周期管理自然是依托 Lifecycle 了,他倆本身就是一個體系下的東東啊。
liveData 是 abstract 的一個類,有3個關鍵方法:
- onActive
liveData 注冊的觀察者數量從 0 到 1時會執行,相當于初始化方法 - onInactive
liveData 注冊的觀察者數量回到 0 時會執行 - setValue
數據改變通知所有觀察者
其他不多說,先來看看一個最簡單的 liveData 例子:
- applicaton 中對外提供一個 liveData 對象
- MainActivity 獲取這個 liveData 然后注冊一個觀察者對象
- MainActivity 可以啟動一個新的頁面
MyApplication
public class MyApplication extends Application {
private static MyApplication INSTANCE;
public MyLiveData liveData;
public static MyApplication getInstance() {
return INSTANCE;
}
@Override
public void onCreate() {
super.onCreate();
INSTANCE = this;
liveData = new MyLiveData();
}
LiveData
public class MyLiveData extends LiveData<String> {
@Override
protected void setValue(String value) {
super.setValue(value);
Log.d("BBB", "setValue..." + value);
}
@Override
protected void onActive() {
super.onActive();
Log.d("BBB", "onActive...");
}
@Override
protected void onInactive() {
super.onInactive();
Log.d("BBB", "onInactive...");
}
}
MainActivity
public class MainActivity extends AppCompatActivity {
private MyLifecyleTextView tx_lifectle;
private Button btn_01;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
tx_lifectle = findViewById(R.id.tx_lifecley);
getLifecycle().addObserver(tx_lifectle);
getLifecycle().addObserver(new LifecycleObserver() {
@OnLifecycleEvent(Lifecycle.Event.ON_START)
public void start() {
Log.d("AAA", "new_onstart...");
}
});
btn_01 = findViewById(R.id.btn_one);
btn_01.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
// data.setValue("AAAAAAAAA");
startActivity(new Intent(MainActivity.this, SecondActivity.class));
}
});
MyApplication.getInstance().liveData.observe(this, new Observer<String>() {
@Override
public void onChanged(@Nullable String s) {
Log.d("BBB", "數據改變..." + s);
}
});
}
liveData 是可以使用泛型的,Google 推出 liveData 的初衷,就是用來包裹數據,包裝成一個 Observable 的,所以一定要支持泛型才能使用的方便啊。
這里提醒一句,有人推薦使用 MutableLiveData ,我的確是看到游 demo 使用 MutableLiveData 了。
這個例子,我們重點來看一下 onActive / onInactive 這個2個方法和觀察者生命周期的互動,這點很重要,弄懂這點,這2個方法我們才能得心應手,要不會出問題的。 我會打印 mainactivity 的生命周期函數 和 livadata 的相關方法
根據上文所說, liveData 注冊的觀察者數量從 0 到 1時會執行 onActive , liveData 注冊的觀察者數量回到 0 時會執行 onInactive ,我們看看這個經典的例子,一個頁面啟動另一個頁面再回來,這包含一個完整的生命周期了。
那我們要是再第二個頁面 oncreat 函數里面也注冊一個觀察者呢,大家猜猜啊,挺有意思的。
觀察以上,可以得出一下結論:
- liveData 觀察者計數以 onPause 為分界點,觀察者走 onPause 生命周期函數,liveData 觀察者計數會 -1,觀察者走 onStart 生命周期函數,liveData 觀察者計數會 +1
- liveData 的 onActive / onInactive 方法是根據注冊的觀察者數量是否為0觸發的,不是每一個觀察者的生命周期變動都會觸發 onActive / onInactive 這2個方法,只有再觀察者數量為0時才行,這點一定要注意
另外我們可以獲取 liveData 里面的數據
liveData.getValue();
觀察者在注冊到 liveData 后,不會觸發執行一次 setvalue 方法,這點搞清楚基本就 OK 了
2019.5.02 補充
liveData 內部有值的情況下,首次注冊 liveData 也是會觸發一次 setvalue 相應的,setvalue 沒有值就不會。例子:A -> B -> A -> B ,第一次從 B 返回事,因為 liveData 已經有值了,A 此時會觸發一次 setvalue ,再次從 A 到 B,還是因為 liveData 已經有值了,B 雖然是新創建的 Activity ,但是因為 liveData 由數值,還是會觸發一次 setvalue
總之就是只要 liveData 內部有數值,那么注冊的頁面主要生命周期可以相應了,那么就會觸發一次 setvalue,這里我用的還好是最新的 1.1.1 的版本,liveData 這種機制不太適合大多數場景,還是推薦使用 RxBus 可以相應的更精確
Lifecycle 和 liveData 綜合使用的例子
看這個 UserData 他可以在 view 生命周期變動時調 setvalue 方法,通知所有觀察者數據有變動
public class UserData extends LiveData implements LifecycleObserver {
private static final String TAG = "UserData";
public UserData() {
}
@Override
protected void onActive() {
super.onActive();
Log.e(TAG, "onActive");
}
@Override
protected void onInactive() {
super.onInactive();
Log.e(TAG, "onInactive");
}
@Override
protected void setValue(Object value) {
super.setValue(value);
Log.e(TAG, "setValue");
}
}
這種情況比較少使用,你想這個 liveData 要和具體一個 view 的生命周期綁定,根據生命周期變動處理數據,發送新數據給所有注冊者。在組件化的思路里,比較適合一個模塊對外提供公共數據,然后這個模塊有變動或是注銷,再通知其他有數據關聯的模塊數據變動。
LiveData 有以下優點:
沒有內存泄漏:因為 Observer 被綁定到它們自己的 Lifecycle 對象上,所以,當它們的 Lifecycle 被銷毀時,它們能自動的被清理。
不會因為 activity 停止而崩潰:如果 Observer 的 Lifecycle 處于閑置狀態(例如:activity 在后臺時),它們不會收到變更事件。
始終保持數據最新:如果 Lifecycle 重新啟動(例如:activity 從后臺返回到啟動狀態)將會收到最新的位置數據(除非還沒有)。
正確處理配置更改:如果 activity 或 fragment 由于配置更改(如:設備旋轉)重新創建,將會立即收到最新的有效位置數據。
資源共享:可以只保留一個 MyLocationListener 實例,只連接系統服務一次,并且能夠正確的支持應用程序中的所有觀察者。
不再手動管理生命周期你可能已經注意到,fragment 只是在需要的時候觀察數據,不用擔心被停止或者在停止之后啟動觀察。由于 fragment 在觀察數據時提供了其 Lifecycle,所以 LiveData 會自動管理這一切。
看一個經典的 LiveData 應用例子
一個定位的例子,這里寫的簡單,manage ,factroy ,都沒寫,但是這個例子展示了:如何用 LiveData 包裝數據,結合單例作為 app 的全局數據使用,這個單例可以寫再這個自定義的 LiveData 里,也可以寫在 manage 里有管理類來管理要更好一點
public class LocationLiveData extends LiveData<Location> {
private static LocationLiveData sInstance;
private LocationManager locationManager;
@MainThread
public static LocationLiveData get(Context context) {
if (sInstance == null) {
sInstance = new LocationLiveData(context.getApplicationContext());
}
return sInstance;
}
private SimpleLocationListener listener = new SimpleLocationListener() {
@Override
public void onLocationChanged(Location location) {
setValue(location);
}
};
private LocationLiveData(Context context) {
locationManager = (LocationManager) context.getSystemService(Context.LOCATION_SERVICE);
}
@Override
protected void onActive() {
locationManager.requestLocationUpdates(LocationManager.GPS_PROVIDER, 0, 0, listener);
}
@Override
protected void onInactive() {
locationManager.removeUpdates(listener);
}
}
Transformations
LiveData 就是一個 Observable ,那么官方提供了一個 Transformations 類,包含 map 和 switchMap 轉換操作,和 Rxjava 的 map 、flatMap 一樣,
- Transformations.map()
LiveData<User> userLiveData = ...;
LiveData<String> userName = Transformations.map(userLiveData, user -> {
user.name + " " + user.lastName
});
- Transformations.switchMap()
private LiveData<User> getUser(String id) {
// ... 這里可以通過遠程獲取數據
}
LiveData<String> userId = ...;
LiveData<User> user = Transformations.switchMap(userId, id -> getUser(id) );
map() 方法接受一個數據,返回一個新的數據,這個看著不難,switchMap() 方法接受一個數據,然后依賴另一個 LiveData 加工,然后返回數據。要是看著不太懂的話,看這里:[譯] Architecture Components 之 LiveData
ViewModel
這個最好理解,ViewModel 就是 MVP 中,P 的角色,當然 ViewModel 還有他的獨特之處。ViewModel 的生命周期雖然總體上還是跟著 view 的,但是 ViewModel 存在的時間比 view 要長一些。我們看看這幾個場景:
- 屏幕旋轉,系統主動殺死造成的 Activity 或者 Fragment 被銷毀或重新創建,所以保存于其中的數據有可能會丟失
- 在 Activity 或者 Fragment 中會經常發起一些需要一定時間才會返回結果的異步請求調用
ViewModel 生命周期圖表明了 ViewModel 的生命周期是有些區別,單總體趨同與 view 的生命周期的,提供了一種新的 view 數據保存模式,比如屏幕旋轉時 activity 重新創建 ViewModel 還是原來那個對象。
ViewModel 是個基類,需要我們繼承他,沒有特殊方法需要去處理,集成完后,直接創建對象就可以使用了。
MyViewModule myViewModule2 = ViewModelProviders.of(this).get(MyViewModule.class);
還有一個 AndroidViewModel ,期中可以獲取 application ,只不過這個 application 需要我們自己傳入,另外 ViewModel 的 of 方法還可以接受一個 ViewModelProvider.NewInstanceFactory 的參數,可以支持自定義構造方法,想傳幾個參數都沒問題
public class MyViewModule extends AndroidViewModel {
public String name;
public MyViewModule(@NonNull Application application, String name) {
super(application);
this.name = name;
}
public void show() {
Toast.makeText(getApplication(), "測試...", Toast.LENGTH_SHORT).show();
}
public static class Factroy extends ViewModelProvider.NewInstanceFactory {
public Application application;
public String name;
public Factroy(Application application, String name) {
this.application = application;
this.name = name;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull Class<T> modelClass) {
return (T) new MyViewModule(application, name);
}
}
}
創建 MyViewModule 對象
MyViewModule.Factroy factroy = new MyViewModule.Factroy(getApplication(), "AAA");
MyViewModule myViewModule = ViewModelProviders.of(this, factroy).get(MyViewModule.class);
使用 ViewModule 可以在同一個 actitivity 的多個 Fragment 之間共享數據
public class SharedViewModel extends ViewModel {
private final MutableLiveData<Item> selected = new MutableLiveData<Item>();
public void select(Item item) {
selected.setValue(item);
}
public LiveData<Item> getSelected() {
return selected;
}
}
public class MasterFragment extends Fragment {
private SharedViewModel model;
public void onActivityCreated() {
model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
itemSelector.setOnClickListener(item -> {
model.select(item);
});
}
}
public class DetailFragment extends LifecycleFragment {
public void onActivityCreated() {
SharedViewModel model = ViewModelProviders.of(getActivity()).get(SharedViewModel.class);
model.getSelected().observe(this, { item ->
// 更新 UI
});
}
}
- Activity 不需要知道該通信的任何事情
- Fragment 之間不受相互影響。除了 ViewModel 之外,Fragment 不需要了解彼此,就算一個 Fragment 被銷毀了,另一個也可以正常工作。而且每個 Fragment 都有自己獨立的生命周期,不受其他 Fragment 的影響。
寫到這里其實我們可以看到一個問題,ViewModel 內部不應該持有外部 view 的引用,ViewModel 的聲明周期比 Fragment / Activity 都長,ViewModel 要是持有了外部 view 的引用就會造成內存泄露,需要注意!
ViewModel 和外部 view 的交互看過上面的大家應該都明悟了,就是 LiveData 了,用這個 LiveData 代替 view 的交互接口同 ViewModel 通信,ViewModel 返回 LiveData ,然后 view 獲取這個 LiveData 再去注冊 updata 方法,這樣避免 ViewModel 持有外部 view 造成的內存泄露
ViewModel vs SavedInstanceState
ViewModels 提供了一種在配置更改時保存數據的簡便方式,但是如果應用進程被操作系統殺死,那么數據則沒有機會被恢復。
通過 SavedInstanceState 保存的數據,存在于操作系統進程的內存中。當用戶離開應用數個小時之后,應用的進程很有可能被操作系統殺死,通過 SavedInstanceState 保存的數據,則可以在 Activity 或者 Fragment 重新創建的時候,在其中的 onCreate() 方法中通過 Bundle 恢復數據。
Room
谷歌推出的一個Sqlite ORM庫,不過使用起來還不錯,使用注解,極大簡化數據庫的操作,有點類似Retrofit的風格
不過 Room 還是沒有脫離 SQL 語言,在注解中還是要寫 SQL 語句的,一般移動端的 數據庫沒有太高的要求,這樣我們其實可以使用 Room 這個數據庫,Room 存的是對象,取的也是對象,是對象類型數據庫了。要是 app 項目的數據庫要求高的話,在來使用 Room 也是個不錯的選擇
Room 的詳細學習看這個:淺談Android Architecture Components | Android 架構組件 Room 介紹及使用
AAC 框架帶給我們的改變
上面我們已經基本看過了 AAC 框架新的 API ,經過 AAC 改造,我們不用擔心內存泄露的場景了,頁面的生命周期函數處理可以寫在頁面之外,更靈活了,整個 app 的架構可以像 Rxjava 一樣,改成基于數據和事件的流式架構了,可以讓我們更簡單容易分離,組合代碼結構。
說帶具體的還是 LiveData 對數據層的改造對我們影響最大,我們再來看看上面官方給出的 AAC 架構圖
Google 官方的頁面,數據流程如下:
- Respositroy 判斷環境,管理緩存,返回數據
- ViewModel 管理數據的 LiveData
- 頁面獲取 LiveData 注冊 updata 界面更新方法
- 最后 ViewModel 觸發 Respositroy 請求數據,通過 LiveData 更新數據
很明顯,這是一個最簡單的數據,顯示例子,展示的一個頁面擁有一個 LiveData 對象來更新數據,這是傳統的邏輯思路。但是大家想想,LiveData 是數據流式的,我們完全可以讓 Respositroy 返回一個靜態的全局的 LiveData ,需要的頁面去注冊 updata 方法,頁面關閉的時候可以自動接觸綁定,這才是 LiveData 的初衷啊,當然使用嘛,不要死板,怎么靈活,怎么簡單,便利,怎么來。
上面涉及到我們對數據的應用范圍了和類型了,LiveData 的出現讓我們對數據的包裝使用產生了一些思考
數據的作用范圍我分為3種:
- 單個頁面范圍內
- 單個功能模塊范圍內(module)
- 整個 app 范圍內
數據的類型我分為2種:
- 原始數據類型,不做處理
- Reponse<Object> ,把數據和加載狀態綜合一起
- Reponse + Object ,把數據和加載狀態分開,這樣可以視情況選擇
結合不同的數據的應用范圍,對數據的處理:
- 單個頁面范圍
對于單個頁面范圍的 data ,Respositroy 返回非靜態的 LiveData < Reponse<Object>> 帶加載狀態的 data - 單個功能模塊范圍內(module)
Respositroy 需要單獨成 module ,Respositroy 返回 LiveData <Reponse> ,LiveData < Object> ,有可能這個 module 的數據,別的 module 也是需要的,但是對于加載狀態來說只是本功能 module 有意義,其他 module 只關心 data 的。 - 整個 app 范圍內
同上面單個功能模塊范圍內(module)的處理,視情況可以不提供加載狀態的 Reponse
數據的經典應用場景舉例:
- 單個頁面范圍內
這是最常見的,我們請求的大數據接口都是,比如商品詳情頁,不同 ID 的商品有不同數據,那么別的頁面顯然是無法使用這個數據的 - 單個功能模塊范圍內(module)
這個比較少見,是隨著 app 的組件化出現的,經典的例子有定位,定位的數據只要對外提供靜態的 LiveData <Adress> 就行,這個定位數據隨著定位模塊的卸載而刪除,要是設計成整個 app 范圍內頁無妨,號保存即可。 - 整個 app 范圍內
這個場景更少,但是是難設計的,最考研代碼功底的。比如全局緩存,這個每個 app 都需要,還有個人中心數據,這個設計好了非常厲害了,對于編碼水平提供有很大助力啊
總結一下就是用 LiveData 改造了一下 repositroy 數據層,UI 層通過 ViewModule 獲取這個 LiveData,建立通道聯系刷新數據,這樣把整個 app 基于數據流改造成響應式架構,適應組件化,平臺化高度封裝,業務模塊分離的需求。
本文 demo 思路
首先本文 demo 如下:
思路是用 LiveData 改造了一下 repositroy 數據層,UI 層通過 ViewModule 獲取這個 LiveData,建立通道聯系刷新數據。簡單用 RXJava 延遲 2 秒發送一條數據,模擬一下網絡請求。
比較好的代碼研究資料:
我的 demo 思路如下:
先來看看我的頁面,主界面中游2個紅色背景的 textview 分別用來顯示2個級別的數據:單次請求的網絡數據和全局數據,全局數據在另外一個頁面中加載,然后再返回看看 LiveData 的同步效果是否好用。
數據層都封裝再 Respositroy 中,Respositroy 持有一個 netDataSource 用于獲取網絡數據,然后 Respositroy 處理數據然后返回。其中數據都是用 response 來包裹,response 中有 code 用來發送數據加載中的各種狀態
畫了一個簡單的數據流動圖,可以看到我這里的 Respositroy 數據層封裝的還是比較簡單的,Respositroy 內部只維護了一個 BookNetDataSource 遠程數據源。
來簡單看下代碼:
BookNetDataSource -> 遠程數據源
public class BookNetDataSource {
private Book mPrivateBook = new Book("私有數據在此");
private Book mPublicBook = new Book("共有數據在此");
public Observable<Book> getPrivateBook() {
return getData(new Book("私有數據如如下:private"));
}
public Observable<Book> getPublicBook() {
return getData(new Book("全局數據如如下: public"));
}
private Observable<Book> getData(final Book book) {
return Observable.timer(2, TimeUnit.SECONDS)
.map(new Function<Long, Book>() {
@Override
public Book apply(Long aLong) throws Exception {
return book;
}
}).subscribeOn(Schedulers.io());
}
}
BookNetDataSource 返回了2個數據,private 數據表示一次性數據,public 表示全局緩存數據,用 rxjava 延遲2秒模擬一下網絡狀態
BookRespositroy 數據層
public class BookRespositroy {
public static MutableLiveData<Response<Book>> PUBLIC_LIVEDATA;
private MutableLiveData<Response<Book>> mBookLiveData;
private BookNetDataSource mBookNetDataSource;
static {
PUBLIC_LIVEDATA = new MutableLiveData<>();
}
public static MutableLiveData<Response<Book>> getPublicLiveData() {
return PUBLIC_LIVEDATA;
}
public static void refreshPublicData() {
PUBLIC_LIVEDATA.setValue(new Response<Book>(Response.CODE_LOADING, null));
new BookNetDataSource().getPublicBook()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Book>() {
@Override
public void accept(Book book) throws Exception {
PUBLIC_LIVEDATA.setValue(new Response<Book>(Response.CODE_SUCCESS, book));
}
});
}
public BookRespositroy() {
mBookNetDataSource = new BookNetDataSource();
mBookLiveData = new MutableLiveData<>();
}
public MutableLiveData<Response<Book>> getPrivateLiveData() {
return mBookLiveData;
}
public void refreshPrivateData() {
mBookLiveData.setValue(new Response<Book>(Response.CODE_LOADING, null));
mBookNetDataSource.getPrivateBook()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Book>() {
@Override
public void accept(Book book) throws Exception {
mBookLiveData.setValue(new Response<Book>(Response.CODE_SUCCESS, book));
}
});
}
}
PUBLIC_LIVEDATA 這個是全局緩存數據,這個由 Respositroy 數據層維護是比較恰當的,mBookLiveData 這個是一次性數據,這個一次性對應的是 BookRespositroy 這個對象的生命周期,MainActivity 通過 viewModule 獲取到這2個 LiveData 管道然后注冊自己的 UI 刷新方法
省略無關代碼
public class MainActivity extends AppCompatActivity {
private ActivityMainBinding binding;
private MyViewModule myViewModule;
private ProgressDialog dialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// 初始化 DataBinding
binding = DataBindingUtil.setContentView(this, R.layout.activity_main);
// 使用自定義構造器方式創建 ViewModule 對象
MyViewModule.Factroy factroy = new MyViewModule.Factroy(getApplication(), "AAA");
myViewModule = ViewModelProviders.of(this, factroy).get(MyViewModule.class);
// 從 ViewModule 中獲取頁面私有數據源的管道,建立聯系,Response 中包含請求響應狀態碼
myViewModule.getPrivateBookLiveData().observe(this, new Observer<Response<Book>>() {
@Override
public void onChanged(@Nullable Response<Book> response) {
if (response == null) {
return;
}
int code = response.code;
if (code == Response.CODE_LOADING) {
dialog.show();
return;
}
if (code == Response.CODE_SUCCESS) {
dialog.dismiss();
binding.setViewData(response.data.getName());
return;
}
}
});
// 從 ViewModule 中獲取全局公共數據源的管道,建立聯系
BookRespositroy.getPublicLiveData().observe(this, new Observer<Response<Book>>() {
@Override
public void onChanged(@Nullable Response<Book> response) {
if (response == null) {
return;
}
int code = response.code;
if (code == Response.CODE_LOADING) {
dialog.show();
return;
}
if (code == Response.CODE_SUCCESS) {
dialog.dismiss();
binding.setAppData(response.data.getName());
return;
}
}
});
}
最后 MainActivity 通過 viewModule 通知 respositroy 獲取數據,然后刷新 livedata ,livedata 調用 setVale 方法就能通知所有注冊其上的對象更新數據了。
public void refreshPrivateData() {
mBookLiveData.setValue(new Response<Book>(Response.CODE_LOADING, null));
mBookNetDataSource.getPrivateBook()
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Consumer<Book>() {
@Override
public void accept(Book book) throws Exception {
mBookLiveData.setValue(new Response<Book>(Response.CODE_SUCCESS, book));
}
});
}
注意這里面,loading 的狀態是由 respositroy 發送的而不是 viewmodule ,加載狀態通過 response 中的 code 碼來承載。
核心點:
- 數據的流動基于觀察者模式來注冊,數據源蹭蹭包裝拋出 LiveData 這個包裹數據源的 observable 用于注冊,UI 層獲取 LiveData 注冊 UI 刷新方法,那么整個數據通道就正式建立了,這時通知數據層加載數據就可以了
- 數據層中對數據源進行封裝,對 remote,file,sql,cache 需要分別封裝,具體的數據源返回的不是具體的數據,而是 rxjava 中的 observable ,因為整個數據流是基于響應式的,要是這里返回個具體數據那么思路就不對了,或者用 callback 傳遞給數據源都是對響應式變成的不理解。
最后
最后想說一下 respositroy 數據層的封裝一定要量力而行,我這里 respositroy 層支持持有 net 遠程數據源了,這個一般即使我們的需求了,沒必要過度去封裝,反而不美。但是要是對數據游比較多的緩存需求,那么就需要再 respositroy 中封裝一個 DataSource 來管理數據源了,比如圖片加載庫,glide 、fresco ,他們都是對數據緩存有要讀要起的,你看代碼的代碼思路專門有一個數據源管理類對外提供數據。
另外若是 respositroy 數據層中業務處理很復雜的話,我們可以把具體的業務邏輯處理分離成u 一個個的 helper 類,google 源碼中就有很多的 helper 類來輔助管理具體的功能
若是 app 設計有自己的特殊業務 code 的話,我們可以把處理放在 respositroy 數據層中,在 app 初始化時配置一個靜態的處理類出來,然后我們在 respositroy 數據層中獲取這個處理類來優先處理特務業務 code ,為啥不放在 respositroy 的基類中,是因為很多 api 我們不需要處理特殊 code ,為了代碼靈活一下犧牲下代碼封裝性。
ps:最后的最后,說一下寫的不好,我的認識也淺顯,歡迎大家踴躍噴我,希望通過大家的評論提高進步。