Android Architecture Components

前言:

作為一名移動互聯(lián)網(wǎng)App研發(fā)人員毅该,在實際項目的研發(fā)過程中博秫,保質(zhì)保量高效率,方便快捷眶掌,同時方便開發(fā)者之間的互相協(xié)作地完成開發(fā)任務挡育,尤為重要。為了達成這個目的朴爬,我們一般會進行一些可復用即寒,可擴展性,方便其他開發(fā)者調(diào)用的模塊化和組件化的框架的搭建召噩。當然其中不乏一些優(yōu)秀的官方或第三方的框架母赵,比如圖片庫:Fresco, UImageLoader Glide;網(wǎng)絡庫具滴,OkHttp,Retrofit凹嘲,Volley;消息分發(fā) : EventBus等等构韵。這些優(yōu)秀的庫的出現(xiàn)周蹭,極大的方便了無數(shù)的App 開發(fā)者,并在數(shù)以萬計的App中使用疲恢。 而今天要介紹的是Google I / O 大會上發(fā)布的架構(gòu)化組件凶朗,即Android Architecture Components。

什么是Android Architecture Components

A collection of libraries that help you design robust, testable, and maintainable apps. Start with classes for managing your UI component lifecycle and handling data persistence.
Android架構(gòu)化組件是通過提供管理開發(fā)者UI 組件的生命周期和數(shù)據(jù)持久化方案的一系列庫显拳,來幫助開發(fā)者設計出棚愤,穩(wěn)健性,可測試性和可維護的App應用萎攒。

在此之前遇八,Google官方并沒有推薦指定任何的應用程序開發(fā)架構(gòu),這意味著耍休,我們可以自由的選擇設計模式刃永,Mvp Mvc MvvM等。但是同時羊精,這也意味著我們需要編寫大量的基礎框架代碼來實現(xiàn)這些設計模型斯够。不僅如此,Google官方對組件(View ,Fragment, Activity等)的生命周期管理喧锦,并沒有提供一套完整的可復用的解決方案读规,這對開發(fā)者來說會產(chǎn)生極大的困擾。比如我們在組件中進行異步操作燃少,如查詢數(shù)據(jù)庫束亏,網(wǎng)絡請求,異步解析數(shù)據(jù)阵具,設置定時器時碍遍,一旦組件銷毀定铜,異步操作完成回調(diào)UI線程時,就極易出現(xiàn)如空指針怕敬,內(nèi)存泄漏等各種問題揣炕。而今天要介紹的Architecture Components就可以很輕松的解決這些問題。

Architecture Components主要包括兩部分:

1)生命周期管理組件: ViewModel东跪,LiveData畸陡,LifecycleObserver,LifecycleOwner

New lifecycle-aware components help you manage your activity and fragment lifecycles. Survive configuration changes, avoid memory leaks and easily load data into your UI using Above.
能感知并管理activity fragment的生命周期的組件虽填,可以幫助開發(fā)者在配置變化中保持數(shù)據(jù)不丟失丁恭,避免內(nèi)存泄漏,并且方便地將數(shù)據(jù)交給UI層展現(xiàn)卤唉。

2)本地數(shù)據(jù)持久化: Room

Avoid boilerplate code and easily convert SQLite table data to Java objects using Room
. Room provides compile time checks of SQLite statements and can return RxJava, Flowable and LiveData observables.
Room可以幫助開發(fā)者避免寫大量樣板代碼涩惑,并且可以地將數(shù)據(jù)庫表數(shù)據(jù),轉(zhuǎn)換成Java 實體數(shù)據(jù)桑驱。Room提供SQLite語句的編譯時檢查,并且可以返回 Rxjava Flowable 和LiveData類型的可被觀察數(shù)據(jù)跛蛋。

1熬的、ViewModel:

ViewModel是用來以一種能感知生命周期的方式來存儲和管理與UI有關的數(shù)據(jù)。它允許數(shù)據(jù)在配置發(fā)生變化時赊级,能夠存活下來而不丟失押框。比如最常見的,Activity 橫豎屏切換時理逊,我們先想想正常流程下橡伞,橫豎屏切換時,Activity的生命周期是怎樣的晋被。

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Chronometer chronometer = findViewById(R.id.chronometer);
    chronometer.start();
}

布局文件如下:

   <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>

我們先來看下圖:(現(xiàn)在用的錄屏轉(zhuǎn)gif軟件真的很難用兑徘,很難把握時機點,動圖有點怪羡洛,見諒~)

切換時重置.gif

這是一個計時Demo挂脑,在進行橫豎屏切換時,我們發(fā)現(xiàn)欲侮,計數(shù)被重置了崭闲。這是因為Activity在 橫豎屏切換時,Activity被銷毀威蕉,并重新onCreate了刁俭。chronometer 生成了一個新的對象,所以計數(shù)重置了韧涨。但是眾說周知牍戚,橫豎屏切換時侮繁,數(shù)據(jù)被重置,在任何app應用中都是無法接受的翘魄。這時會有人說鼎天,可以通過android:configChanges="orientation|keyboardHidden" 等等,各種設置來實現(xiàn)Activity不被onCreate暑竟。這里且不說這種方式在高低版本兼容中可能出現(xiàn)的問題斋射,橫豎屏切換時的生命周期變化本身就可以專門寫一篇文章來說明了〉纾總之一句話罗岖,通過android:configChanges 來設置,以實現(xiàn)數(shù)據(jù)不被重置腹躁,太桑包!麻!煩纺非!那么還有其他方式來輕松的解決這個問題嗎哑了,是的,有烧颖,就是ViewModel弱左。

先看圖:

111.gif

從圖中我們可以看到,在橫豎屏切換后炕淮,時間的數(shù)據(jù)并沒有被重置拆火。

ChronometerViewModel :

public class ChronometerViewModel extends ViewModel {
@Nullable
private Long mStartTime;

@Nullable
public Long getStartTime() {
    return mStartTime;
}

public void setStartTime(final long startTime) {
    this.mStartTime = startTime;
}
}

Activity:

protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);

    Log.d("ansen", "onCreate------>2");
    // The ViewModelStore provides a new ViewModel or one previously created.
    ChronometerViewModel chronometerViewModel
            = ViewModelProviders.of(this).get(ChronometerViewModel.class);

    // Get the chronometer reference
    Chronometer chronometer = findViewById(R.id.chronometer);

    if (chronometerViewModel.getStartTime() == null) {
        // If the start date is not defined, it's a new ViewModel so set it.
        long startTime = SystemClock.elapsedRealtime();
        chronometerViewModel.setStartTime(startTime);
        chronometer.setBase(startTime);
    } else {
        // Otherwise the ViewModel has been retained, set the chronometer's base to the original
        // starting time.
        chronometer.setBase(chronometerViewModel.getStartTime());
    }

    chronometer.start();
}

ChronometerViewModel 在橫豎屏切換后,是會繼續(xù)存活的涂圆,ChronometerViewModel 里的mStartTime的數(shù)據(jù)自然也是存活的们镜,這樣就可以達到我們在前文預期的效果。
那么ViewModel是如何做到的呢润歉?

ChronometerViewModel chronometerViewModel
            = ViewModelProviders.of(this).get(ChronometerViewModel.class);

這行代碼中的this模狭,為AppCompatActivity, 實現(xiàn)了LifecycleOwner接口。Framework保證卡辰,只要LifecycleOwner是存活著的胞皱,ViewModel就處于存活狀態(tài)。在ViewModel的LifecycleOwner因為配置變化而銷毀時(比如橫豎屏切換)九妈,ViewModel本身不會銷毀反砌,新的LifecycleOwner 實例會和存活的ViewModel建立連接。說明圖表如下:


ViewModel.png

注意:
1)Activity在配置變化如橫豎屏切換時萌朱,Activity會被銷毀宴树,但是ViewModel不會因為配置變化而銷毀。但是如果我們調(diào)用Activity 的finish()方法晶疼,activity 生命周期完全結(jié)束酒贬,處于finished狀態(tài)時又憨,ViewModel也會隨之銷毀。
2)如果ViewModel持有View或Context的引用锭吨,可能會造成內(nèi)存泄漏蠢莺,要避免這種情況。ViewModel的onCleared()方法零如,對于其持有的更長生命周期的對象來取消訂閱躏将,或者清除引用是有效的,但是不是用來取消View 或Context的引用考蕾,要避免這種行為祸憋。

2、LiveData:

在介紹LiveData之前肖卧,我們先思考上文中提出過的一個問題蚯窥。
異步操作(網(wǎng)絡請求,數(shù)據(jù)庫查詢塞帐,數(shù)據(jù)解析拦赠,定時器刷新)等結(jié)束回調(diào)給主線程更新UI前,如果此時Activity被銷毀了葵姥,此時要怎么處理才能避免內(nèi)存泄漏和空指針等異常問題矛紫?
ok,ok,ok,我知道,作為一個老生長談牌里,歷史悠久的問題,聰明的你肯定知道答案务甥。
比如 ondestroy()中做如下處理
a) 異步操作: AsyncTask.cancel() (如果已經(jīng)執(zhí)行了牡辽,異步操作也是取消不了的)
b) 定時器: TimeTask.cancel (),Timer.cancel()
c) Handler: Handler.removeAllMessagesAndCallBacks(null)
d)View : 對activity view作非空判斷避免空指針
e) Retrofit+ RxJava 取消網(wǎng)絡請求敞临,通過取消訂閱來不再回調(diào)UI線程态辛。
......
哇,看到上面列的這些挺尿,覺得大家真的是厲害奏黑,內(nèi)存泄漏,NullPointer處理都考慮到了编矾,有經(jīng)驗熟史!可是轉(zhuǎn)念一想,不免又覺得很失落窄俏。且不說上面所述是否列完了所有情況(當然當然沒有蹂匹,肯定還有很多其他類似需要處理的生命周期管理的問題),而且還因為凹蜈,我發(fā)現(xiàn)每個開發(fā)者限寞,都需要關注這個問題忍啸,在做每個功能時,都需要考慮是否會出現(xiàn)上述的問題以及如何處理履植,而Android 截至上個Google IO大會之前计雌,竟然沒有一套面向所有開發(fā)者的可信賴的解決方案。這就很難受了玫霎。凿滤。。

我們知道鼠渺,上述的問題鸭巴,其實就是一個生命周期管理的問題。在UI不需要或者不能進行更新操作的時候拦盹,從其他線程回調(diào)數(shù)據(jù)過來強行更新鹃祖,就會出現(xiàn)問題。而解決這個問題普舆,現(xiàn)在就有了一個非常好的解決方案:
LiveData恬口。

LiveData is an observable data holder class. Unlike a regular observable, LiveData is lifecycle-aware, meaning it respects the lifecycle of other app components, such as activities, fragments, or services. This awareness ensures LiveData only updates app component observers that are in an active lifecycle state.

LiveData 是一個可觀察數(shù)據(jù)的容器。不像其他的可觀察者沼侣,LiveData 是可以感知生命周期的祖能,這意味著它能感知其他的app 組件如 activity fragment service等。這種生命周期感知能力使LiveData可以只在它的那些組件觀察者active時通知更新(比如onResume等)蛾洛。

LiveData同時也作為一個觀察者养铸,在它的觀察對象(比如Activity Fragment等組件)的生命周期處于STARTED或者RESUMED狀態(tài)時,LiveData處于活躍狀態(tài)轧膘,LiveData只會在它的觀察者們處于活躍狀態(tài)時才會通知他們進行更新钞螟。LiveData的觀察者如果本身生命周期處于不活躍狀態(tài),那么他們不會收到LiveData更新的通知谎碍。

我們先來看一個例子鳞滨,還是一個時鐘的例子,時間顯示在自動更新蟆淀。

LiveDataTimerViewModel:

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() cannot be called from a background thread so post to main thread.
            mElapsedTime.postValue(newValue);
        }
    }, ONE_SECOND, ONE_SECOND);

}

public LiveData<Long> getElapsedTime() {
    return mElapsedTime;
}
}

Activity:

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();
}

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);
}
}

我們可以看到LiveDataTimerViewModel 繼承自ViewModel拯啦,ViewModel的作用上文已說過,這里不再贅述熔任。
LiveDataTimerViewModel 擁有LiveData對象MutableLiveData褒链,操作的數(shù)據(jù)類型是Long型的時間戳。
構(gòu)造方法中笋敞,每一秒鐘會更新一次數(shù)據(jù)碱蒙,并將值postValue給LiveData對象mElapsedTime。注意:
因為定時器的線程是異步線程,而Activity是在UI線程中監(jiān)聽LiveData的時間變化的赛惩,所以此時需要用postValue給LiveData哀墓。而不是setValue(當然如果數(shù)據(jù)變化不是在異步線程而是在主線程中進行的,則需要用setValue)喷兼。

接下來看LiveData的observe方法篮绰。

public void observe(@NonNull LifecycleOwner owner, @NonNull Observer<T> observer)

1)將所給的Observer加入到所給的Owner的生命周期范疇的Observer的列表中。事件分發(fā)會在主線程中進行季惯,如果LiveData 已經(jīng)set了數(shù)據(jù)吠各,那么這個數(shù)據(jù)變化會被發(fā)送給Observer。
2)Observer只會在Owner處于活躍狀態(tài)時(STARTED或者RESUMED狀態(tài))時才會接收到變化的通知勉抓。
3)如果Owner狀態(tài)變?yōu)榱薉ESTROYED狀態(tài)贾漏,那么這個Observer會被移除。
4)在Owner處于不活躍狀態(tài)時藕筋,數(shù)據(jù)發(fā)生變化不會通知給Observer纵散。但是當Owner重新變活躍時,它會接收到最后一次可用的數(shù)據(jù)更新通知隐圾。
5)只要Owner不處于DESTROYED狀態(tài) 伍掀, LiveData會一直持有Observer的強引用。一旦Owner變?yōu)镈ESTROYED狀態(tài)暇藏,LiveData會移除對Observer的引用蜜笤。
6)如果Owner在observe方法調(diào)用時處于DESTROYED狀態(tài),LiveData會忽略此次調(diào)用盐碱。
7)如果給定的Oberver和Owner已經(jīng)在觀察列表中了把兔,LiveData會忽略此次調(diào)用。但是如果Oserver在觀察列表中擁有多個Owner, LiveData會報IllegalArgumentException瓮顽。

現(xiàn)在看這個例子就比較簡單了垛贤。觀察者是elapsedTimeObserver ,它監(jiān)聽LiveData里的long類型的時間變化趣倾。elapsedTimeObserver 的生命周期擁有者是this即ChronoActivity3 。在ChronoActivity3 處于活躍狀態(tài)時某饰,LiveData會將時間的變化通知給elapsedTimeObserver儒恋,并在onChange回調(diào)中更新UI。

此時我相信大家應該注意到一個點黔漂。就是我們不需要在每次數(shù)據(jù)變化后诫尽,再去refresh一遍UI了,我們只需要將需要觀察的數(shù)據(jù)放入LiveData中炬守,然后將數(shù)據(jù)變化通知給組件牧嫉。這在大量不同數(shù)據(jù)變化需要更新不同UI的場景下,大大解放了我們的雙手,我們再也不用在每個數(shù)據(jù)變化時酣藻,都去調(diào)用refreshUI方法了曹洽!

LiveData的優(yōu)勢:

1)LIveData確保你的UI使用的是最新的數(shù)據(jù),而且還不需要你在數(shù)據(jù)變化時自己調(diào)用更新辽剧。
2)不會內(nèi)存泄漏送淆。(我們上文討論過這個問題。因為Observer只會在自己的生命周期擁有者怕轿,LiveCycleOwner active的狀態(tài)下偷崩,才會接收到數(shù)據(jù)更新的通知,所以不會再有內(nèi)存泄漏的問題撞羽。)
3)不會在Activity銷毀后還接收到更新UI的指令導致崩潰阐斜。
4)開發(fā)者不需要再手動管理生命周期。
5)即使在配置發(fā)生變化時诀紊,比如activty的橫豎屏切換谒出,觀察者還是能收到最新的有效數(shù)據(jù)。
6)這一點感覺很好用渡紫。LiveData可以以單例模式在應用中存在到推。這時它就可以被應用中,任何需要它的觀察者監(jiān)聽惕澎,以實現(xiàn)多頁面多處監(jiān)聽更新等莉测。

再介紹兩個類:
MutableLiveData : 以public形式暴露了LiveData的setValue和postValue方法。
MediatorLiveData:可以觀察其他的LiveData數(shù)據(jù)唧喉,并在其發(fā)生變化時通知給MediatorLiveData捣卤。

3、生命周期管理(LifeCycleObserver和LifeCycleOwner)

生命周期組件可以根據(jù)其他組件(如Activity Fragment等)的生命周期狀態(tài)變化進行相應操作八孝。這些組件可以幫助你寫出更好組織董朝,更輕量級,更容易管理的代碼干跛。

想象一下子姜,如果我們要實現(xiàn)一個類似微信搖一搖震動的功能,而這個功能在用戶鎖屏時楼入,即頁面不可見時哥捕,是不能觸發(fā)的,那么我們需要對onResume嘉熊,onPause進行監(jiān)聽遥赚,操作如下:

class MyShakeListener {
public MyShakeListener (Context context, Callback callback) {
    // ...
}

void start() {
    // connect to system location service
}

void stop() {
    // disconnect from system location service
}
}

class MyActivity extends AppCompatActivity {
private MyShakeListener myShakeListener;

@Override
public void onCreate(...) {
    myShakeListener= new MyShakeListener(this, (location) -> {
        // update UI
    });
}

@Override
public void onStart() {
    super.onStart();
    myShakeListener.start();
    // manage other components that need to respond
    // to the activity lifecycle
    }

@Override
public void onStop() {
    super.onStop();
    myShakeListener.stop();
    // manage other components that need to respond
    // to the activity lifecycle
    }
}

這樣操作是沒有問題的,但是一個應用中大多數(shù)情況下是絕對不止一個功能需要監(jiān)聽類似的生命周期狀態(tài)阐肤。一般我們用到的EventBus,ButterKnife等以及我們自定義的監(jiān)聽都需要用到類似功能凫佛。此時我們發(fā)現(xiàn)讲坎,我們需要在Activity或者Fragment的生命周期中,管理這么多的組件的相關生命周期方法愧薛,這樣也太不優(yōu)雅了晨炕。

我們可以通過LifecycleOwner 和LifecycleObserver來更好地處理生命周期狀態(tài)變化的問題。

生命周期Events和狀態(tài)變化如下圖:

lifecycle-states.png
class MyShakeListener implements LifecycleObserver {
private boolean enabled = false;
public MyShakeListener(Context context, Lifecycle lifecycle, Callback callback) {
   ...
}

@OnLifecycleEvent(Lifecycle.Event.ON_START)
void start() {
    if (enabled) {
       // connect
    }
}

public void enable() {
    enabled = true;
    if (lifecycle.getCurrentState().isAtLeast(STARTED)) {
        // connect if not connected
    }
}

@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
void stop() {
    // disconnect if connected
}
}
myLifecycleOwner.getLifecycle().addObserver(new MyObserver());

通過LifeCycleObserver以及LifeCycleOwner厚满, 一個類可以通過給其中的方法添加注解的方式來管理生命周期府瞄,然后通過myLifecycleOwner(比如Activity Fragment)的addObserver() 方法實現(xiàn)Observer對LifeOwner的生命周期的監(jiān)聽。

在進行生命周期的管理時碘箍,我們希望在某些不合適的生命周期階段不觸發(fā)某些操作遵馆。例如我們不能在activity的state已經(jīng)保存的情況下,再去提交事務操作丰榴,這樣會引發(fā)崩潰货邓。而LifeCycleOwner允許我們查詢當前的生命周期狀態(tài)。LifeCycleOwner.getCurrentState()四濒,可以根據(jù)當前狀態(tài)來判斷是否允許進行某些操作换况。

現(xiàn)在的MyShakeListener就已經(jīng)是一個能感知生命周期的組件了。如果其他的Fragment 或者Activity需要用到它盗蟆,只需要實例化MyShakeListener即可戈二,其他剩余的操作都只在MyShakeListener中進行管理。

當然喳资,你也可以通過如下方式觉吭,使你的class變成一個LifecycleOwner。你可以使用LifecycleRegistry類來實現(xiàn)仆邓,但是同時你需要通過下面的方式來分發(fā)相應的生命周期狀態(tài)鲜滩。

public class MyActivity extends Activity implements LifecycleOwner {
private LifecycleRegistry mLifecycleRegistry;

@Override
protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    mLifecycleRegistry = new LifecycleRegistry(this);
    mLifecycleRegistry.markState(Lifecycle.State.CREATED);
}

@Override
public void onStart() {
    super.onStart();
    mLifecycleRegistry.markState(Lifecycle.State.STARTED);
}

@NonNull
@Override
public Lifecycle getLifecycle() {
    return mLifecycleRegistry;
}

4、Room:

Room是Android官方提供的新的數(shù)據(jù)持久化方案組件节值。
Room組件主要可以進行數(shù)據(jù)庫操作徙硅,并且可以和LiveData配合來監(jiān)聽數(shù)據(jù)庫變化,以更新UI搞疗。

Room中有幾個重要的注解:

@Database: 注解繼承自RoomDatabase的類嗓蘑,主要用于創(chuàng)建數(shù)據(jù)庫和Daos(數(shù)據(jù)訪問對象)。
@Entity :用來注釋實體類匿乃,@Database類通過entities屬性脐往,引用被@Entity注解的類,并通過
這個類的所有屬性作為表的列名來創(chuàng)建數(shù)據(jù)庫的表扳埂。
@Dao: 注解接口或抽象方法,用來提供訪問數(shù)據(jù)庫數(shù)據(jù)的方法瘤礁。在使用@Database注解的類中必須定義一個不帶參數(shù)的方法阳懂,這個方法返回使用@Dao注解的類呜达。

1) 用注解 @Entity 定義實體類

@Entity
public class User {

@PrimaryKey
@NonNull
public String id;

public String name;

public String lastName;

public int age;

}

2)用@Database創(chuàng)建RoomDatabase的子類作為數(shù)據(jù)庫赦役。

@Database(entities = {User.class, Book.class, Loan.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

private static AppDatabase INSTANCE;

public abstract UserDao userModel();

public abstract BookDao bookModel();

public abstract LoanDao loanModel();

public static void destroyInstance() {
    INSTANCE = null;
  }

}

3)@Dao 創(chuàng)建數(shù)據(jù)訪問接口Dao

@Dao
public interface UserDao {
@Query("select * from user")
List<User> loadAllUsers();

@Query("select * from user where id = :id")
User loadUserById(int id);

@Query("select * from user where name = :firstName and lastName = :lastName")
List<User> findUserByNameAndLastName(String firstName, String lastName);

@Insert(onConflict = IGNORE)
void insertUser(User user);

@Delete
void deleteUser(User user);

@Query("delete from user where name like :badName OR lastName like :badName")
int deleteUsersByName(String badName);

@Insert(onConflict = IGNORE)
void insertOrReplaceUsers(User... users);

@Delete
void deleteUsers(User user1, User user2);

@Query("SELECT * FROM User WHERE :age == :age") // TODO: Fix this!
List<User> findUsersYoungerThan(int age);

@Query("SELECT * FROM User WHERE age < :age")
List<User> findUsersYoungerThanSolution(int age);

@Query("DELETE FROM User")
void deleteAll();
}

4)在RoomDatabase中引用dao:

@Database(entities = {User.class, Book.class}, version = 1)
public abstract class AppDatabase extends RoomDatabase {

private static AppDatabase INSTANCE;

public abstract UserDao userModel();

public abstract BookDao bookModel();
}

5、 如何獲取數(shù)據(jù)并通知UI Controller更新UI

現(xiàn)在我們已經(jīng)了解了上述Android架構(gòu)化組件的基本用法,下面我們需要考慮一個問題北救。我們要如何更好的管理數(shù)據(jù)源,然后通知給UI Controller去更新數(shù)據(jù)呢懂更?是的砸王,沒錯,ViewModel + LiveData +Retrofit +Room +...

1)數(shù)據(jù)模型UserProfileViewModel 葱淳,用來保存需要UI更新所需要的數(shù)據(jù)

public class UserProfileViewModel extends ViewModel {
private String userId;
private User user;

public void init(String userId) {
    this.userId = userId;
}
public User getUser() {
    return user;
  }
}

2)如何通知UI更新

@Override
    public void onCreated(@Nullable Bundle savedInstanceState) {
    super.onActivityCreated(savedInstanceState);
    String userId = getArguments().getString(UID_KEY);
    UserProfileViewModel   viewModel = ViewModelProviders.of(this).get(UserProfileViewModel.class);
    viewModel.init(userId);
    viewModel.getUser().observe(this, user -> {
      // update UI
    });
  }

3)如何獲取數(shù)據(jù):

a钝腺、常見的網(wǎng)絡數(shù)據(jù)獲取方式(比如Retrofit)
b、本地持久化數(shù)據(jù)(比如Room)

public interface Webservice {
/**
 * @GET declares an HTTP GET request
 * @Path("user") annotation on the userId parameter marks it as a
 * replacement for the {user} placeholder in the @GET path
 */
@GET("/users/{user}")
Call<User> getUser(@Path("user") String userId);
}

4)封裝UserRepository :

public class UserRepository {
private Webservice webservice;
// ...
public LiveData<User> getUser(int userId) {
    // This is not an optimal implementation, we'll fix it below
    final MutableLiveData<User> data = new MutableLiveData<>();
    webservice.getUser(userId).enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            // error case is left out for brevity
            data.setValue(response.body());
        }
    });
    return data;
  }
}

Android官方推薦我們使用Repository模式來進行數(shù)據(jù)獲取邏輯的封裝赞厕。Repository可以給app其余模塊提供一個干凈的api接口艳狐,這樣其他模塊就會獨立于數(shù)據(jù)獲取的邏輯之外,能更專注于他們本身的業(yè)務范疇皿桑。Repository知道從哪里獲取數(shù)據(jù)毫目,也知道數(shù)據(jù)更新時該如何調(diào)用。Repository可以將其他模塊诲侮,比如本地持久化數(shù)據(jù)(Room)镀虐,網(wǎng)絡數(shù)據(jù)(Web Service), 緩存數(shù)據(jù)(Cache)等串聯(lián)起來。
此時我們發(fā)現(xiàn)沟绪,ViewModel只需要從UserRepository來更新數(shù)據(jù)刮便,但是ViewModel并不知道,也不需要關心這些數(shù)據(jù)從哪里來以及怎么來近零。從而達到ViewMode是與數(shù)據(jù)獲取邏輯獨立開的目的诺核。

5)將ViewModel和Repository 串聯(lián)起來。

public class UserProfileViewModel extends ViewModel {
private LiveData<User> user;
private UserRepository userRepo;

@Inject // UserRepository parameter is provided by Dagger 2
public UserProfileViewModel(UserRepository userRepo) {
    this.userRepo = userRepo;
}

public void init(String userId) {
    if (this.user != null) {
        // ViewModel is created per Fragment so
        // we know the userId won't change
        return;
    }
    user = userRepo.getUser(userId);
}

public LiveData<User> getUser() {
    return this.user;
  }
}

6) UserRepository從緩存中讀取數(shù)據(jù)久信,如果緩存中沒有窖杀,則從網(wǎng)絡讀取。

public class UserRepository {
private Webservice webservice;
// simple in memory cache, details omitted for brevity
private UserCache userCache;
public LiveData<User> getUser(String userId) {
    LiveData<User> cached = userCache.get(userId);
    if (cached != null) {
        return cached;
    }

    final MutableLiveData<User> data = new MutableLiveData<>();
    userCache.put(userId, data);
    // this is still suboptimal but better than before.
    // a complete implementation must also handle the error cases.
    webservice.getUser(userId).enqueue(new Callback<User>() {
        @Override
        public void onResponse(Call<User> call, Response<User> response) {
            data.setValue(response.body());
        }
    });
    return data;
  }
}

7)UserRepository從數(shù)據(jù)庫中獲取數(shù)據(jù)(Room)裙士,如果本地數(shù)據(jù)中沒有入客,則從網(wǎng)絡獲取。

@Singleton
public class UserRepository {
private final Webservice webservice;
private final UserDao userDao;
private final Executor executor;

@Inject
public UserRepository(Webservice webservice, UserDao userDao, Executor executor) {
    this.webservice = webservice;
    this.userDao = userDao;
    this.executor = executor;
}

public LiveData<User> getUser(String userId) {
    refreshUser(userId);
    // return a LiveData directly from the database.
    return userDao.load(userId);
}

private void refreshUser(final String userId) {
    executor.execute(() -> {
        // running in a background thread
        // check if user was fetched recently
        boolean userExists = userDao.hasUser(FRESH_TIMEOUT);
        if (!userExists) {
            // refresh the data
            Response response = webservice.getUser(userId).execute();
            // TODO check for error etc.
            // Update the database.The LiveData will automatically refresh so
            // we don't need to do anything else here besides updating the database
            userDao.save(response.body());
        }
    });
  }
}

總結(jié):

final-architecture.png

UI Controller通過ViewModel來更新數(shù)據(jù)腿椎,而ViewModel通過LiveData監(jiān)聽數(shù)據(jù)變化并通知給UI層更新桌硫。ViewModel通過Repository模式來封裝數(shù)據(jù)獲取邏輯。這些數(shù)據(jù)可以來自緩存(Cache)啃炸,本地持久化數(shù)據(jù)(Room Sqlite),以及網(wǎng)絡請求(Retroft OKHttp)等铆隘。通過上述方式,整個數(shù)據(jù)獲取的業(yè)務模塊南用,UI Controller及App的其他模塊膀钠,就能更加的獨立和解耦掏湾,整個app的業(yè)務框架就更清晰,更容易擴展和維護了肿嘲。

1融击、Android Architecture Components官方文檔
2、android-lifecycles
3雳窟、android-persistence
4尊浪、App 組件化/模塊化之路——Android 架構(gòu)化組件
5、Android Room使用

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末封救,一起剝皮案震驚了整個濱河市拇涤,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌兴泥,老刑警劉巖工育,帶你破解...
    沈念sama閱讀 219,270評論 6 508
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異搓彻,居然都是意外死亡如绸,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,489評論 3 395
  • 文/潘曉璐 我一進店門旭贬,熙熙樓的掌柜王于貴愁眉苦臉地迎上來怔接,“玉大人,你說我怎么就攤上這事稀轨《笃辏” “怎么了?”我有些...
    開封第一講書人閱讀 165,630評論 0 356
  • 文/不壞的土叔 我叫張陵奋刽,是天一觀的道長瓦侮。 經(jīng)常有香客問我,道長佣谐,這世上最難降的妖魔是什么肚吏? 我笑而不...
    開封第一講書人閱讀 58,906評論 1 295
  • 正文 為了忘掉前任,我火速辦了婚禮狭魂,結(jié)果婚禮上罚攀,老公的妹妹穿的比我還像新娘。我一直安慰自己雌澄,他們只是感情好斋泄,可當我...
    茶點故事閱讀 67,928評論 6 392
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著镐牺,像睡著了一般炫掐。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上睬涧,一...
    開封第一講書人閱讀 51,718評論 1 305
  • 那天募胃,我揣著相機與錄音沛厨,去河邊找鬼。 笑死摔认,一個胖子當著我的面吹牛,可吹牛的內(nèi)容都是我干的宅粥。 我是一名探鬼主播参袱,決...
    沈念sama閱讀 40,442評論 3 420
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼秽梅!你這毒婦竟也來了抹蚀?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,345評論 0 276
  • 序言:老撾萬榮一對情侶失蹤企垦,失蹤者是張志新(化名)和其女友劉穎环壤,沒想到半個月后,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體钞诡,經(jīng)...
    沈念sama閱讀 45,802評論 1 317
  • 正文 獨居荒郊野嶺守林人離奇死亡郑现,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,984評論 3 337
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發(fā)現(xiàn)自己被綠了荧降。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片接箫。...
    茶點故事閱讀 40,117評論 1 351
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖朵诫,靈堂內(nèi)的尸體忽然破棺而出辛友,到底是詐尸還是另有隱情,我是刑警寧澤剪返,帶...
    沈念sama閱讀 35,810評論 5 346
  • 正文 年R本政府宣布废累,位于F島的核電站,受9級特大地震影響脱盲,放射性物質(zhì)發(fā)生泄漏邑滨。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點故事閱讀 41,462評論 3 331
  • 文/蒙蒙 一宾毒、第九天 我趴在偏房一處隱蔽的房頂上張望驼修。 院中可真熱鬧,春花似錦诈铛、人聲如沸乙各。這莊子的主人今日做“春日...
    開封第一講書人閱讀 32,011評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽耳峦。三九已至,卻和暖如春焕毫,著一層夾襖步出監(jiān)牢的瞬間蹲坷,已是汗流浹背驶乾。 一陣腳步聲響...
    開封第一講書人閱讀 33,139評論 1 272
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留循签,地道東北人级乐。 一個月前我還...
    沈念sama閱讀 48,377評論 3 373
  • 正文 我出身青樓,卻偏偏與公主長得像县匠,于是被迫代替她去往敵國和親风科。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 45,060評論 2 355

推薦閱讀更多精彩內(nèi)容