Architecture Components 知識梳理(2) - LiveData 示例

一、概述

在學(xué)習(xí)完Lifecycle之后,我們?nèi)绾瓮ㄟ^Lifecycle讓除了Activity/Fragment之外的其它對象都可以很方便地收到當(dāng)前頁面生命周期的回調(diào)。

今天我們來學(xué)習(xí)ViewModel+LiveData,它們共同的特點就是綁定到了UI組件(Activity/Fragment)上,可以收到生命周期的回調(diào),簡單地來說,這兩個組件的作用分別為:

  • LiveData:在Lifecycle范圍內(nèi) 監(jiān)聽數(shù)據(jù) 的變化。
  • ViewModel:在Lifecycle范圍內(nèi) 存儲和共享數(shù)據(jù)

今天,我們就一起來學(xué)習(xí)一下LiveData的使用場景。

二、LiveData+ViewModel

這個Demo的功能很簡單,在界面上有一個Btn,點擊Btn后異步加載數(shù)據(jù),數(shù)據(jù)回來以后通知UI來更新。

Demo 界面

2.1 創(chuàng)建 ViewModel 和 LiveData

/**
 * 繼承于 ViewModel。
 *
 * @author lizejun
 */
public class DataViewModel extends ViewModel {

    private static final String TAG = DataViewModel.class.getSimpleName();

    //這里創(chuàng)建一個 MutableLiveData,<?> 為要提供的數(shù)據(jù)類型,這里我們聲明為 List。
    private MutableLiveData<List<String>> mWatcher;
    private Handler mWorkHandler;

    /**
     * 加載數(shù)據(jù),在實際當(dāng)中,加載數(shù)據(jù)的操作要放在 Repository 中進行,而不要放在 Model 中,
     * 它只是負責(zé)數(shù)據(jù)和 UI 的交互過程。
     *
     */
    public void load() {
        if (mWorkHandler == null) {
            HandlerThread thread = new HandlerThread("DataViewModel");
            thread.start();
            mWorkHandler = new Handler(thread.getLooper());
        }
        mWorkHandler.post(new Runnable() {

            @Override
            public void run() {
                //模擬加載數(shù)據(jù)的過程。
                try {
                    Thread.sleep(1000);
                } catch (Exception e) {
                    e.printStackTrace();
                }
                List<String> result = makeResult();
                setResults(result);
            }

        });
    }

    /**
     * 獲取數(shù)據(jù)的監(jiān)控者。
     *
     * @return 監(jiān)控者。
     */
    public MutableLiveData<List<String>> getWatcher() {
        Log.d(TAG, "Call getWatcher");
        if (mWatcher == null) {
            mWatcher = new MutableLiveData<>();
        }
        return mWatcher;
    }

    /**
     * 設(shè)置數(shù)據(jù)。
     *
     * @param results 設(shè)置數(shù)據(jù)。
     */
    private void setResults(List<String> results) {
        Log.d(TAG, "Call setResults");
        //當(dāng)數(shù)據(jù)加載完以后,調(diào)用 setValue/postValue 方法設(shè)置數(shù)據(jù)。
        if (Looper.getMainLooper() == Looper.myLooper()) {
            getWatcher().setValue(results);
        } else {
            getWatcher().postValue(results);
        }
    }

    private List<String> makeResult() {
        List<String> result = new ArrayList<>();
        result.add("蘋果 - 1");
        result.add("蘋果 - 2");
        result.add("蘋果 - 3");
        result.add("蘋果 - 4");
        result.add("蘋果 - 5");
        result.add("蘋果 - 6");
        return result;
    }
}

我們創(chuàng)建一個繼承ViewModelDataViewModel,前面我們也提到過,ViewModel作用是存儲和共享數(shù)據(jù),這里的作用就是 存儲

我們所需要的真實數(shù)據(jù)則是被保存在MutableLiveData<?>當(dāng)中,?就是真實數(shù)據(jù)的類型,而MutableLiveData就是我們所說的LiveData,它除了負責(zé)保存數(shù)據(jù)外,還負責(zé)在數(shù)據(jù)變化的時候,通知它的觀察者。

LiveData中插入數(shù)據(jù)的方式有兩種:

  • setValue:只允許在主線程中調(diào)用。
  • postValue:主線程/子線程中均可調(diào)用。

在上面的Demo中,我們在load()中模擬了異步獲取數(shù)據(jù)的操作的邏輯,并最終向LiveData中插入數(shù)據(jù)。

2.2 使用方式

下面,讓我們來看一下完整的使用過程,以及一些特殊的場景。

/**
 * LiveData 學(xué)習(xí) Demo。
 */
public class LiveDataActivity extends AppCompatActivity {

    private Button mBtnRefresh;
    private TextView mTvResult;
    private DataViewModel mViewModel;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_live_data);
        mTvResult = findViewById(R.id.tv_result);
        //1.創(chuàng)建 ViewModel。
        mViewModel = ViewModelProviders.of(this).get(DataViewModel.class);
        //2.添加觀察者。
        mViewModel.getWatcher().observe(this, new Observer<List<String>>() {

            @Override
            public void onChanged(@Nullable List<String> strings) {
                Log.d("DataViewModel", "onChanged");
                String tvDisplay = "";
                for (String result : strings) {
                    tvDisplay += (result + "\n");
                }
                //4.數(shù)據(jù)發(fā)生了改變后會回調(diào)到這里。
                mTvResult.setText(tvDisplay);
            }
        });
        mBtnRefresh = findViewById(R.id.btn_refresh);
        mBtnRefresh.setOnClickListener(new View.OnClickListener() {

            @Override
            public void onClick(View v) {
                //3.觸發(fā)加載。
                Log.d("DataViewModel", "mViewModel.load()");
                mViewModel.load();
            }
        });
    }

    @Override
    protected void onResume() {
        super.onResume();
        Log.d("DataViewModel", "onResume()");
    }

    @Override
    protected void onPause() {
        super.onPause();
        Log.d("DataViewModel", "onPause()");
    }
}

首先,我們通過ViewModelProviders.of(this).get(DataViewModel.class)創(chuàng)建了DataViewModel的實例,而不是通過直接new的方式,這么做的原因有兩個:

  • 通過of傳入的Activity,其在內(nèi)部實現(xiàn)了與Activity的綁定。
  • 為了數(shù)據(jù)的共享,這點我們暫時還沒有體會到,后面會講。

接著,獲取到DataViewModel當(dāng)中的MutableLiveData對象,調(diào)用其observe方法,該方法接受兩個參數(shù),分別對應(yīng)于其要綁定到的組件以及一個監(jiān)聽者,當(dāng)我們改變了MutableLiveData內(nèi)部持有的數(shù)據(jù)后,將會回調(diào)該監(jiān)聽者,我們就可以在里面做更新數(shù)據(jù)的操作。

當(dāng)我們點擊 加載 按鈕后,log輸出如下所示:

點擊加載按鈕

現(xiàn)在,讓我們模擬一種特殊的場景,在點擊加載按鈕后,迅速按下HOME鍵讓頁面處于inactive的狀態(tài),此時的輸出為:

點擊加載后迅速按下 HOME 鍵

可以發(fā)現(xiàn),沒有回調(diào) onChanged() 方法,之后重新進入界面:
重新進入界面

在重新進入界面之后,才回調(diào)了onChanged()方法,也就是說,在界面inactive的狀態(tài)下發(fā)生了數(shù)據(jù)的改變,不會立即通知觀察者,而是要等到界面重新active之后,才會調(diào)用observeronChanged()方法。

除了observe方法外,還有一個observeForever,它只接受一個觀察者參數(shù),也就是說,它并不關(guān)注當(dāng)前界面是否active,都會回調(diào)數(shù)據(jù)。

        //不與組件的生命周期綁定。
        mViewModel.getWatcher().observeForever(new Observer<List<String>>() {

            @Override
            public void onChanged(@Nullable List<String> strings) {
                Log.d("DataViewModel", "onChanged");
                String tvDisplay = "";
                for (String result : strings) {
                    tvDisplay += (result + "\n");
                }
                //4.數(shù)據(jù)發(fā)生了改變后會回調(diào)到這里。
                mTvResult.setText(tvDisplay);
            }
        });

用相同的操作驗證的結(jié)果如下,即使界面處于inactive狀態(tài),也會回調(diào)onChanged()方法:

不與組件的生命周期綁定

因此,為了避免內(nèi)存泄漏,我們應(yīng)當(dāng)在界面銷毀的時候,調(diào)用MutableLiveDataremoveObserver方法。

2.3 小結(jié)

整個數(shù)據(jù)的流向如下所示:

數(shù)據(jù)流向

通過LiveData作為中介者,實現(xiàn)了UI組件和數(shù)據(jù)組件的隔離,對于UI組件來說,它只負責(zé) 發(fā)起請求操作在數(shù)據(jù)變化的時候更新界面,而對于數(shù)據(jù)組件來說,它負責(zé) 接受請求改變LiveData,而通知UI組件的操作則是由LiveData來完成的,因此它可以根據(jù)當(dāng)前UI組件的狀態(tài)active/inative進行控制。

三、繼承 LiveData

繼承LiveData的時候,我們一般會重寫它的下面兩個方法,它們的含義為:

  • onActive():當(dāng)LiveData有一個活躍的觀察者時調(diào)用。
  • onInactive():當(dāng)LiveData沒有任何一個活躍的觀察者時調(diào)用。

而我們繼承LiveData的場景主要有兩種:

  • 通過onActive/onInactive回調(diào),我們可以知道 是否有觀察者正在活動,這有利于我們注冊和反注冊類似于傳感器或者廣播的監(jiān)聽。
  • 通過將LiveData設(shè)置為單例的,讓其在多個Activity內(nèi)共享數(shù)據(jù),只要數(shù)據(jù)發(fā)生了變化,那么任何一個處于active狀態(tài)的觀察者就會收到通知。

以下就是一個通過繼承LiveData,并重寫onActive/onInactive方法實現(xiàn)監(jiān)聽網(wǎng)絡(luò)變化的例子。

public class NetLiveData extends LiveData<Boolean> {

    private BroadcastReceiver mBroadcastReceiver;
    private static Context sAppContext;
    private static volatile NetLiveData sInstance;
    private AtomicBoolean mNotice = new AtomicBoolean(false);

    public static NetLiveData getInstance(Context context) {
        if (sInstance == null) {
            synchronized (NetLiveData.class) {
                if (sInstance == null) {
                    sInstance = new NetLiveData(context);
                }
            }
        }
        return sInstance;
    }

    private NetLiveData(Context context) {
        sAppContext = context.getApplicationContext();
    }

    @Override
    public void observe(@NonNull LifecycleOwner owner, @NonNull final Observer<Boolean> observer) {
        super.observe(owner, new Observer<Boolean>() {

            @Override
            public void onChanged(@Nullable Boolean aBoolean) {
                if (mNotice.compareAndSet(true, false)) {
                    observer.onChanged(aBoolean);
                }
            }
        });

    }

    @Override
    protected void onActive() {
        super.onActive();
        registerBroadcast(sAppContext);
    }

    @Override
    protected void onInactive() {
        super.onInactive();
        unRegisterReceiver(sAppContext);
    }

    @Override
    protected void setValue(Boolean value) {
        super.setValue(value);
        mNotice.set(true);
    }

    /**
     * 注冊網(wǎng)絡(luò)連接監(jiān)聽。
     */
    private void registerBroadcast(Context context) {
        if (mBroadcastReceiver == null) {
            mBroadcastReceiver = new BroadcastReceiver() {

                @Override
                public void onReceive(Context context, Intent intent) {
                    setValue(isNetworkAvailable(context));
                }

            };
            IntentFilter filter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
            context.registerReceiver(mBroadcastReceiver, filter);
        }
    }

    /**
     * 取消網(wǎng)絡(luò)連接監(jiān)聽。
     */
    private void unRegisterReceiver(Context context) {
        if (mBroadcastReceiver != null) {
            context.unregisterReceiver(mBroadcastReceiver);
            mBroadcastReceiver = null;
        }
    }

    /**
     * 獲取當(dāng)前網(wǎng)絡(luò)是否連接,連接返回 true,未連接返回 false。
     *
     * @param context 上下文。
     * @return 網(wǎng)絡(luò)連接連接返回 true,否則返回 false。
     */
    private static boolean isNetworkAvailable(Context context) {
        ConnectivityManager connectivityManager = (ConnectivityManager) context
                .getSystemService(Context.CONNECTIVITY_SERVICE);
        NetworkInfo[] networkInfo = connectivityManager.getAllNetworkInfo();
        if (networkInfo != null && networkInfo.length > 0) {
            for (int i = 0; i < networkInfo.length; i++) {
                if (networkInfo[i].getState() == NetworkInfo.State.CONNECTED) {
                    return true;
                }
            }
        }
        return false;
    }
}

四、參考文獻

(1) Android架構(gòu)組件 (二) - LiveData
(2) 關(guān)于使用 Android MVVM + LiveData 模式的一些建議

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務(wù)。