Jetpack mvvm 三部曲(二) LiveData

在上篇講了下ViewModel 這次接著講LiveData

下一篇 Jetpack mvvm 三部曲(三) DataBinding
先放下本jetpak系列在學習過程寫的demojetpackDemo
  • 先貼下官方的鏈接LiveData

  • 根據官方給的說發LiveData可為數據提供觀察者(就是一個接口),在數據進行改變的時候活躍的觀察者可以獲取到數據的變化非活躍的觀察者是不能收到變化通知的

  • 我在這里寫了個計時器先記錄生命周期的狀態以及LiveData數值,當數據產生變化打印看下生命周期方法以及當前的數值,可以看到在app回到桌面進入到onPause后觀察者就收不到通知了,而重新從后臺回到app進入onResume狀態觀察者又能收到通知了。


    log
  • LiveData能做到這點還是要歸功于LifecycleOwner接口,當然AndroidX的AppCompatActivity類已經幫我們實現了LifecycleOwner接口

  • 先看下怎么實現在看下源碼

  • 引用(2.2.0目前是最新版本) 其余版本可參考

    implementation 'androidx.lifecycle:lifecycle-livedata:2.2.0'
官方提示
public class MyViewModel extends ViewModel {

    public MutableLiveData<String> stringMediatorLiveData;

    public MyViewModel() {
        stringMediatorLiveData = new MutableLiveData<>();
    }

}

  • 實現監聽LiveData數據的變化使用observe方法傳入LifecycleOwner,在實現觀察者Observer接口就行了,通過setValue 或者 postValue改變LiveData的值就能在Observer接口的onChanged方法收到改變后的數值了。
  • 這里有一個注意的點在主線程使用可以用setValue,如果是子線程改變值用postValue,因為在多線程中操作數據是不安全的,而postValue是通過synchronized關鍵字確保線程安全


    官方提醒

    postValue方法
  MyViewModel  viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        viewModel.stringMediatorLiveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("MainActivity",s+"----");
            }
        });
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.stringMediatorLiveData.setValue("哈哈哈哈");    
            }
        });
  • 實現的代碼敲完了,接著看下源碼。


    圖1

    圖2

    圖3

    圖4

    圖5

    圖6

    圖7

    圖8

    圖9
  • 圖一是傳LifecycleOwner過去

  • 圖二是傳遞保存在一個map對象中

  • 圖3是把LifecycleObserver拿到更新生命周期狀態

  • 圖4是回調LiveDataon中LifecycleBoundObserver類的StateChanged方法見圖5

  • 圖6調用dispatchingValue改變值的時候會通過mObservers這個map對象進行遍歷見圖7,從map集合中取出觀察者ObserverWrapper接口調用onChanged方法進行回調通知注意圖8的第一個紅框return如果observer.mActiveactivity的活躍狀態為false就直接不走下面的方法了,這個mAtive參數的賦值見圖5 activeStateChanged(shouldBeActive()); shouldBeActive這個方法見圖9

-------------------------------分割線-------------------------------

  • 到這里LiveData你就有了初步的認知,在上面的例子是直接通過MutableLiveData去實現的。
  • MutableLiveData既可以去改變值也可以監聽值的變化但是為了數值的維護客觀性(ps.不要直接通過MutableLiveData去setValue如果多個地方改變值不太容易看出來不方便后期),我們可以通過LiveData去寫(MutableLiveData其實是繼承自LiveData只不過就是暴露了下setValue跟postValue方法)


  • 鑒于LiveData的setValue和postValue方法修飾關鍵詞是protected(ps.包名不同無法調用該方),我們可以這么去實現。
public class MyViewModel extends ViewModel {
    private MutableLiveData<String> stringMediatorLiveData;
    public LiveData<String> stringLiveData;

    public MyViewModel() {
        stringMediatorLiveData = new MutableLiveData<>();
        stringLiveData = stringMediatorLiveData;
        this.user = new User("張三",11);
        this.count = 13;
    }

    public void update(String s){
        stringMediatorLiveData.setValue(s);
    }
}
  • 使用
 MyViewModel  viewModel = new ViewModelProvider(this).get(MyViewModel.class);
        viewModel.stringLiveData.observe(this, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                Log.e("MainActivity",s+"----");
            }
        });
       
        textView.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View view) {
                viewModel.update("哈哈哈哈");
            }
        });
  • 雖然多寫了寫代, 但是從代碼維護來看更合理寫(ps.官方也是建議這么做的)

-------------------------------分割線-------------------------------

如果就想去實時監聽數據更新不管頁面可見不可見那怎么辦

  • 這個時候observeForever方法就出馬了
  • 看了上面得源碼部分就應該知道LiveData的observe接口是怎么根據可見不可見狀態被調用的
  • observeForever方法直接把狀態改成了全程可見,而不像observe方法由LifecycleBoundObserver根據shouldBeActive方法去設定了。


    image.png

    image.png

    image.png
  • 把observe改成observeForever方法就能實時去監聽數值變化了,畢竟observeForever直接把狀態設置成可見了,源碼那快圖8那個considerNotify方法就不會因為不可見狀態return調了不去執行下面接口回調的方法了
  Observer<Integer> integerObserver = new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.e("LiveDataActivity",string+"--->"+integer);
            }
        };
        model.time.observeForever(integerObserver);
  • 注意observeForever要自己去removeObserver掉監聽,因為observe方法中LifecycleBoundObserver自己處理了


    image.png
在扯下MediatorLiveData
  • MediatorLiveData是MutableLiveData的子類

  • MutableLiveData這個類是繼承LiveData把postValue和setValue暴露出來


    image.png
  • MediatorLiveData這個就有點意思了正如他的名字Mediator(中介的意思)

  • MediatorLiveData的核心是私有靜態內部類Source



    image.png
  • 先記住紅框的部分很重要,addSource就是把另一個LiveData由Source進行代理,監聽當被代理的LiveData數值發生改變那么他就會去回調傳入的觀察者onChanged方法

  • 原理很簡單那有什么用呢

  • 假設下面的場景實時知道文本框輸入了多少個字


    image.png
  public MutableLiveData<String> message = new MutableLiveData<>();
  public MediatorLiveData<Integer> messageNumber = new 
  MediatorLiveData<>();
  messageNumber.setValue(0);
  messageNumber.addSource(message, new Observer<String>() {
            @Override
            public void onChanged(String s) {
                messageNumber.setValue(s.length());
        }
 });
<LinearLayout
        android:layout_width="match_parent"
        android:orientation="horizontal"
        android:layout_height="wrap_content">
        <EditText
            android:id="@+id/edit"
            android:layout_width="200dp"
            android:layout_marginLeft="10dp"
            android:paddingLeft="10dp"
            android:paddingRight="10dp"
            android:text="@={mode.message}"
            android:singleLine="true"
            android:background="@drawable/b2"
            android:layout_height="50dp"/>

        <TextView
            android:layout_width="wrap_content"
            android:layout_gravity="center"
            android:layout_marginLeft="10dp"
            android:textColor="#cc00"
            android:text='@{String.format("輸入了%d個字",mode.messageNumber)}'
            android:layout_height="wrap_content"/>
    </LinearLayout>
  • 這樣實現就行了
  • 當然絕對不能忘記注銷LiveData的監聽,原因嘛在observeForever也講了的,我們去ViewModel的onCleared方法做就好了,畢竟onCleared是ViewModel執行的最后一個方法。
   @Override
    protected void onCleared() {
        super.onCleared();
        messageNumber.removeSource(message);
    }
總結下
  • 普通數據使用MutableLiveData和LiveData就行了
  • 設計到數據聯動的時候考慮下MediatorLiveData
最后在說1個東西轉換
  • 在項目中我們經常回運到數據轉換的問題比如int轉換成string或者int轉換成另一個對象
map
public class LiveDataViewModel extends ViewModel {
    private MutableLiveData<User> userMutableLiveData;
    public LiveData<Integer> age;
    
    private List<User> users;
    public LiveDataViewModel() {
        userMutableLiveData = new MutableLiveData<>();
        age = Transformations.map(userMutableLiveData, new Function<User, Integer>() {
            @Override
            public Integer apply(User input) {
                return input.getAge();
            }
        });
     public void addUser(User user){
        //這里值得注意的是setValue主線程用沒問題,如果是子線程那就要用postValue
        // userMutableLiveData.postValue(user);
        if(Looper.myLooper() == Looper.getMainLooper()){
            userMutableLiveData.setValue(user);
        }else {
            userMutableLiveData.postValue(user);
        }
    }
}
  • 這時直接去監聽age就行了,只要userMutableLiveData的值有改變我們就能立馬監聽到User類中的年齡了
   model.age.observe(this, new Observer<Integer>() {
            @Override
            public void onChanged(Integer integer) {
                Log.e("MainActivity",integer+"--");
            }
        });
switchMap
  • switchMap和map同樣屬于數據轉換的一種,在實際項目中switchMap使用的頻率回比map更高map是通過監聽一個對象的變換然后進行轉換的,假設有另外一個場景通過用戶id進行網絡請求去拿到這個用戶的信息,按著map的寫法豈不是要有好幾個步驟先通過網絡請求去拿到user對象、再去賦值給LiveData、在轉換給LiveData、在對LiveData進行監聽改變ui。
  • 下面直接模擬下子線程查詢進行用戶查詢
public class LiveDataViewModel extends ViewModel {
    public LiveData<User> userLiveData;
    private MutableLiveData<Integer> userIndex = new MutableLiveData<>();

    private List<User> users;
    public LiveDataViewModel() {
        users = new ArrayList<>();
        users.add(new User("李四",16));
        users.add(new User("張三",18));
        users.add(new User("王武",17));
        users.add(new User("錢六",20));
        userLiveData =  Transformations.switchMap(userIndex, new Function<Integer, LiveData<User>>() {
            @Override
            public LiveData<User> apply(Integer input) {
                return getUser(input);
            }
        });
    }
    public void queryUser(int userId){
        userIndex.setValue(userId);
    }
    //模擬查詢
    public  LiveData<User> getUser(int userId){
        MutableLiveData<User> userMutableLiveData = new MutableLiveData<>();
        if(userId>users.size()-1){
            userMutableLiveData.setValue(new User("沒有該用戶",0));
            return userMutableLiveData;
        }
        new Thread(new Runnable() {
            @Override
            public void run() {
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
                userMutableLiveData.postValue(users.get(userId));
            }
        }).start();
        return userMutableLiveData;
    }
}
  • 監聽
    model.userLiveData.observe(this, new Observer<User>() {
            @Override
            public void onChanged(User user) {
                dataBinding.name.setText("名字:"+user.getName());
                dataBinding.tvAge.setText("年齡:"+user.getAge());

            }
        });
  • 當調用了queryUser方法就會去觸發switchMap方法中的Function接口apply方法去查詢用戶數據了
最后的最后說下onChanged回調是在主線程的,不然會拋異常這也解釋了子線程得用postValue去更新數值
  • setValue


    image.png

    image.png
  • postValue


    image.png

    image.png

到此LiveData便就結束了 寫的不對不好的地方請大家指點

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯系作者
平臺聲明:文章內容(如有圖片或視頻亦包括在內)由作者上傳并發布,文章內容僅代表作者本人觀點,簡書系信息發布平臺,僅提供信息存儲服務。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市,隨后出現的幾起案子,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 228,546評論 6 533
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 98,570評論 3 418
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 176,505評論 0 376
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,017評論 1 313
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 71,786評論 6 410
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 55,219評論 1 324
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 43,287評論 3 441
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 42,438評論 0 288
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 48,971評論 1 335
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 40,796評論 3 354
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 42,995評論 1 369
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 38,540評論 5 359
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,230評論 3 347
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 34,662評論 0 26
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 35,918評論 1 286
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 51,697評論 3 392
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 47,991評論 2 374