利用 LiveData 和 ViewModel 搭建應(yīng)用內(nèi)數(shù)據(jù)中心

背景

在 App 中意蛀,存在這樣幾組數(shù)據(jù)阴挣,需要在所有 Activity 和 Fragment 間共享蚌斩,也可以被任意對象訪問惹盼,被訪問時(shí),如果數(shù)據(jù)沒有初始化惑灵,就會(huì)先進(jìn)行初始化,數(shù)據(jù)準(zhǔn)備完畢后再傳遞給訪問者眼耀。實(shí)現(xiàn)方式有多種英支,比如利用 RxJava 將各組數(shù)據(jù)設(shè)計(jì)成可觀察對象。這里我們采用了 LiveData 和 ViewModel 哮伟,因?yàn)樵?Android 平臺干花,它們有著自己獨(dú)特的優(yōu)勢。

LiveData

LiveData 是能感知生命周期的楞黄,它遵循宿主(如 Activity池凄,F(xiàn)ragment,Service)的生命周期鬼廓,僅會(huì)在宿主生命周期激活時(shí)肿仑,才會(huì)通知更新數(shù)據(jù),使用 LiveData 也有以下的優(yōu)勢:

  • 能及時(shí)更新宿主數(shù)據(jù)狀態(tài)
  • 沒有內(nèi)存泄漏
  • 不會(huì)再有當(dāng)宿主處于 onStop 時(shí)更新數(shù)據(jù)可能造成 crash 的情況
  • 無需手動(dòng)處理生命周期
  • 始終保持?jǐn)?shù)據(jù)最新
    ...

LiveData 常用的有下面幾種方法:

getValue()

獲取當(dāng)前數(shù)據(jù)

observe(LifecycleOwner owner, Observer<T> observer)

在 Lifecycle 宿主中訂閱 LiveData

setValue(T value)

更新數(shù)據(jù),注意此方法只能在主線程調(diào)用

postValue(T value)

更新數(shù)據(jù)尤慰,此方法允許在異步線程中調(diào)用

ViewModel

ViewModel 能很好的為 Activity 和 Fragment 管理數(shù)據(jù)馏锡,將數(shù)據(jù)相關(guān)的操作與 UI 層分離。
...

實(shí)現(xiàn)

首先定義 Data伟端,能在應(yīng)用內(nèi)任意訪問的數(shù)據(jù)杯道。

interface Data {

    fun isEmpty(): Boolean

    fun refresh()

}

這里看到其中定義了兩個(gè)方法,isEmpty()责蝠, 用來判斷該數(shù)據(jù)是否為空党巾,refresh(),用來更新數(shù)據(jù)自身霜医,比如拉取遠(yuǎn)端的最新數(shù)據(jù)齿拂,或從數(shù)據(jù)庫讀取。

接下來以用戶數(shù)據(jù)為例支子,首先創(chuàng)建一個(gè) User 類來定義數(shù)據(jù)結(jié)構(gòu):

data class User(var userId: Int, var userName: String, var userEmail: String) {
    fun isEmpty(): Boolean = userId.isNullOrBlank() && userName.isNullOrBlank() && userEmail.isNullOrBlank()
}

這里 User 類持有了用戶 Id, 用戶名和用戶郵箱创肥。接下來定義 UserData:

class UserData: LiveData<User>(), Data {

    override fun isEmpty(): Boolean {
        return value == null || value.isEmpty()
    }

    override fun refresh() {
        //異步拉取最新數(shù)據(jù)
        asyncLoadData(newData -> 
            //更新
            postValue(newData)
        )
    }

}

asyncLoadData 拉取到數(shù)據(jù)后,調(diào)用 postValue 更新 UserData值朋。

下面我們設(shè)計(jì)一個(gè)數(shù)據(jù)中心叹侄,所有的 Data 都存儲(chǔ)在數(shù)據(jù)中心里:

object DataCentral {

    private val safeDataTable = ConcurrentHashMap<String, Data>()

    fun accessor() = this@DataCentral

    fun <T: Data> query(clazz: Class<T>): T?{
        val queryKey = clazz.simpleName
        if (!safeDataTable.containsKey(queryKey) || safeDataTable[queryKey] == null){
            try {
                safeDataTable[queryKey] = clazz.newInstance()
            }catch (e: InstantiationException){
                //Data block init error
                ...
                return null
            }catch (e: IllegalAccessException){
                //Data block init error
                ...
                return null
            }catch (e: ExceptionInInitializerError){
                //Data block init error
                ...
                return null
            }catch (e: SecurityException){
                //Data block init error
                ...
                return null
            }catch (e: Exception){
                ...
            }
        }

        return safeDataTable[queryKey] as? T
    }

}

從上面的代碼中,可以看到所有的 Data 其實(shí)存儲(chǔ)在了一個(gè) HashMap 里昨登,這里采用了 ConcurrentHashMap 以支持并發(fā)操作趾代。另外我們以具體 Data 類的類名來作為存儲(chǔ)的 Key,這樣避免在數(shù)據(jù)量大時(shí)丰辣,需要維護(hù)相當(dāng)數(shù)量的 key撒强。每一組數(shù)據(jù)也只會(huì)在首次被訪問時(shí),才會(huì)在數(shù)據(jù)中心創(chuàng)建笙什,創(chuàng)建的方式是通過無參構(gòu)造函數(shù)直接新建實(shí)例飘哨,所以在定義每個(gè) Data 時(shí),需要包含一個(gè)無參的構(gòu)造函數(shù)琐凭。

現(xiàn)在數(shù)據(jù)中心有了芽隆,我們還需要一個(gè)媒介,用來訪問數(shù)據(jù)中心统屈,同時(shí)承擔(dān)與數(shù)據(jù)中心交互以外的部分職責(zé)胚吁,就像一個(gè)代理。顯然該 ViewModel 出場了:

class DataCentralAgent: ViewModel() {

    private val dataCentralAccessor = DataCentral.accessor()

    fun <T: Data> get(clazz: Class<T>): T?{
        return dataCentralAccessor.query(clazz)?.apply {
            if (isEmpty()) {
                refresh()
                ...
            }
        }
    }

    override fun onCleared() {
        super.onCleared()
        ...
    }
}

我們看到 get(clazz: Clazz<T>) 方法愁憔,傳入需要獲取的具體 Data 類型腕扶,數(shù)據(jù)中心會(huì)將 Data 查詢出來并返回,同時(shí)對數(shù)據(jù)進(jìn)行判空吨掌,如果為空半抱,便調(diào)用 refresh() 方法更新該數(shù)據(jù)的值脓恕。

那么在 Activity 和 Fragment 中,我們只需要通過 DataCentralAgent 來 get 我們需要的數(shù)據(jù)就可以了代虾,即使數(shù)據(jù)還沒有準(zhǔn)備好进肯,它也會(huì)自己默默在后臺更新自己,接著借助于 LiveData 的特性將更新好的數(shù)據(jù)傳遞給訂閱者棉磨。我們還可以寫一個(gè)輔助類江掩,來幫助快速創(chuàng)建 Agent:

object DataCentralAgentFactory {

    @MainThread
    fun createFor(activityOwner: FragmentActivity): DataCentralAgent =
            ViewModelProviders.of(activityOwner).get(DataCentralAgent::class.java)

    @MainThread
    fun createFor(fragmentOwner: Fragment): DataCentralAgent =
            ViewModelProviders.of(fragmentOwner).get(DataCentralAgent::class.java)

}

下面我們寫一個(gè)訪問示例:

class MyFragment: Fragment {

      override fun onCreate(savedInstanceState: Bundle?) {
        DataCentralAgentFactory
              .createFor(this)
              .get(UserData::class.java)
              ?.observe(this@MyFragment, object: Observer<User> {
                  override fun onChanged(user: User?) {
                     ...
                  }
              })  
        super.onCreate(savedInstanceState)
        ...
      }
      
}

到此一個(gè)應(yīng)用內(nèi)數(shù)據(jù)中心就搭建完成了。

(轉(zhuǎn)載請注明出處)

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末乘瓤,一起剝皮案震驚了整個(gè)濱河市环形,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌衙傀,老刑警劉巖抬吟,帶你破解...
    沈念sama閱讀 218,451評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異统抬,居然都是意外死亡火本,警方通過查閱死者的電腦和手機(jī),發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,172評論 3 394
  • 文/潘曉璐 我一進(jìn)店門聪建,熙熙樓的掌柜王于貴愁眉苦臉地迎上來钙畔,“玉大人,你說我怎么就攤上這事金麸∏嫖觯” “怎么了?”我有些...
    開封第一講書人閱讀 164,782評論 0 354
  • 文/不壞的土叔 我叫張陵挥下,是天一觀的道長揍魂。 經(jīng)常有香客問我,道長棚瘟,這世上最難降的妖魔是什么现斋? 我笑而不...
    開封第一講書人閱讀 58,709評論 1 294
  • 正文 為了忘掉前任,我火速辦了婚禮偎蘸,結(jié)果婚禮上庄蹋,老公的妹妹穿的比我還像新娘。我一直安慰自己禀苦,他們只是感情好蔓肯,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,733評論 6 392
  • 文/花漫 我一把揭開白布遂鹊。 她就那樣靜靜地躺著振乏,像睡著了一般。 火紅的嫁衣襯著肌膚如雪秉扑。 梳的紋絲不亂的頭發(fā)上慧邮,一...
    開封第一講書人閱讀 51,578評論 1 305
  • 那天调限,我揣著相機(jī)與錄音,去河邊找鬼误澳。 笑死耻矮,一個(gè)胖子當(dāng)著我的面吹牛,可吹牛的內(nèi)容都是我干的忆谓。 我是一名探鬼主播裆装,決...
    沈念sama閱讀 40,320評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼倡缠!你這毒婦竟也來了哨免?” 一聲冷哼從身側(cè)響起,我...
    開封第一講書人閱讀 39,241評論 0 276
  • 序言:老撾萬榮一對情侶失蹤昙沦,失蹤者是張志新(化名)和其女友劉穎琢唾,沒想到半個(gè)月后,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體盾饮,經(jīng)...
    沈念sama閱讀 45,686評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡采桃,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,878評論 3 336
  • 正文 我和宋清朗相戀三年,在試婚紗的時(shí)候發(fā)現(xiàn)自己被綠了丘损。 大學(xué)時(shí)的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片普办。...
    茶點(diǎn)故事閱讀 39,992評論 1 348
  • 序言:一個(gè)原本活蹦亂跳的男人離奇死亡,死狀恐怖号俐,靈堂內(nèi)的尸體忽然破棺而出泌豆,到底是詐尸還是另有隱情,我是刑警寧澤吏饿,帶...
    沈念sama閱讀 35,715評論 5 346
  • 正文 年R本政府宣布踪危,位于F島的核電站,受9級特大地震影響猪落,放射性物質(zhì)發(fā)生泄漏贞远。R本人自食惡果不足惜,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,336評論 3 330
  • 文/蒙蒙 一笨忌、第九天 我趴在偏房一處隱蔽的房頂上張望蓝仲。 院中可真熱鬧,春花似錦官疲、人聲如沸袱结。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,912評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽垢夹。三九已至,卻和暖如春维费,著一層夾襖步出監(jiān)牢的瞬間果元,已是汗流浹背促王。 一陣腳步聲響...
    開封第一講書人閱讀 33,040評論 1 270
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留而晒,地道東北人蝇狼。 一個(gè)月前我還...
    沈念sama閱讀 48,173評論 3 370
  • 正文 我出身青樓,卻偏偏與公主長得像倡怎,于是被迫代替她去往敵國和親迅耘。 傳聞我的和親對象是個(gè)殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,947評論 2 355

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