JetPack 初見 - 使用 LiveData 實現組件之間數據共享

18年穀歌退出了新的開發套件 JetPack (噴氣揹包),本文將淺顯的討論一下其中 LiveData 的一些使用方法與注意事項。
JetPack

1. LiveData 是什麼?

LiveData 是一個可觀察的數據持有者類。與常規observable不同,LiveData是生命週期感知的,這意味着它關注其他應用程序組件的生命週期,例如 ActivityFragmentService。這確保了 LiveData 僅更新處於活動生命週期狀態的應用程序組件觀察者。

這帶來的優點是顯而易見的

  1. 確保UI匹配數據狀態
  2. 不存在內存泄漏
  3. 不會因爲 Activity 停止而崩潰
  4. 不需要手動處理生命週期
  5. 適配配置更改(如設備旋轉)
  6. 資源共享(如使用一個單例對象持有 LiveData 實現全局資源共享 )

2. 如何使用 LiveData

  1. 添加依賴 (根據實際需求添加)
dependencies {
    def lifecycle_version = "2.0.0"

    // ViewModel and LiveData
    implementation "androidx.lifecycle:lifecycle-extensions:$lifecycle_version"
    // alternatively - just ViewModel
    implementation "androidx.lifecycle:lifecycle-viewmodel:$lifecycle_version" // For Kotlin use lifecycle-viewmodel-ktx
    // alternatively - just LiveData
    implementation "androidx.lifecycle:lifecycle-livedata:$lifecycle_version"
    // alternatively - Lifecycles only (no ViewModel or LiveData). Some UI
    //     AndroidX libraries use this lightweight import for Lifecycle
    implementation "androidx.lifecycle:lifecycle-runtime:$lifecycle_version"

    annotationProcessor "androidx.lifecycle:lifecycle-compiler:$lifecycle_version" // For Kotlin use kapt instead of annotationProcessor
    // alternately - if using Java8, use the following instead of lifecycle-compiler
    implementation "androidx.lifecycle:lifecycle-common-java8:$lifecycle_version"

    // optional - ReactiveStreams support for LiveData
    implementation "androidx.lifecycle:lifecycle-reactivestreams:$lifecycle_version" // For Kotlin use lifecycle-reactivestreams-ktx

    // optional - Test helpers for LiveData
    testImplementation "androidx.arch.core:core-testing:$lifecycle_version"
}
  1. 創建一個 LiveData 實例用於保存特定類型數據,這通常在自己定義的 ViewModel 內內完成 。
  2. 創建一個 Observer 對象並重寫 onChanged() 方法,當 LiveData 對象中的數據發生變化時,會調用該方法,並傳遞包裝在LiveData中的數據。該方法在主線程調用,通常用於變更 UI 狀態。
  3. 將 LiveData 與 Observer 建立訂閱關係。調用 LiveData 的 observe(LifecycleOwner owner, Observer observer) 來建立訂閱關係,該方法第一個參數爲 LifecycleOwner ,第二個參數爲上一步創建的 Observer 對象。

文至此處,各位讀者朋友們應該已經從中窺出 LiveData 之所以能擁有生命週期感知能力,就是因爲在最後一步中,我們傳入了一個 LifecycleOwner

我們一層一層查看 AppCompatActivity 等組件都會發現,該類實現了 LifecycleOwner接口,所以我們的 LiveData 可以在每個組件中跟隨生命週期變化而變化。便不會存在組件生命週期結束了,卻錯誤的試圖去更新UI,而導致崩潰這種情況(說的就是你 RxJava)。

3. 實操 - Activity 與 Fragment 之間的數據共享

  1. 創建一個持有LiveData的類,一般來說是 ViewModel,當然也可以是一個單例,本文使用單例模式。這將使得我們的 LiveData 的生命週期是整個 APP 的生命週期。
data class Person(var name: String,
                  var age: Int,
                  var sex: Int)
                  
object SingletonLiveData {
    private val list = ArrayList<Person>()
    val personList: MutableLiveData<ArrayList<Person>> by lazy {
        MutableLiveData<ArrayList<Person>>()
    }

    fun addPerson(person: Person) {
        list.add(person)
        personList.value = list
    }

    fun clear() {
        list.clear()
        personList.value = list
    }
}

注意我們想要更新LiveData中的數據時有兩種方式,一:在主線程中操作可以使用setValue() ;二:在子線程中操作,使用postValue()

  1. 在組件中使用 LiveData 實現數據共享
    在Activity中使用:
class DemoActivity : AppCompatActivity() {
    
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_demo)
        val observer =  Observer<ArrayList<Person>>{
            textView.text = it.last().toString()
        }
        SingletonLiveData.personList.observe(this,observer)
        var age = 19
        btnAdd.setOnClickListener {
            SingletonLiveData.addPerson(Person("張三",++age,1))
        }
        supportFragmentManager.beginTransaction()
                .add(R.id.fl_content,BlankFragment.newInstance())
                .commit()
    }
}

在Fragment中使用:

class BlankFragment : Fragment() {

    lateinit var lastPersonTv:TextView

    companion object {
        fun newInstance() = BlankFragment()
    }
    
    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.blank_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        lastPersonTv = view.findViewById(R.id.lastPerson)
        var age = 19
        view.findViewById<Button>(R.id.btnAddF).setOnClickListener {
            SingletonLiveData.addPerson(Person("李四",++age,1))
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        val observer =  Observer<ArrayList<Person>>{
            lastPersonTv.text = it.size.toString()
        }
       SingletonLiveData.personList.observe(this,observer)
    }
}

實際效果預覽:
Activity 與 Fragment 之間數據共享
圖示中白色區域爲Activity,淺黃色區域爲一個 Fragment,我們在act 與 frag 互不持有的狀況下實現了數據的共享,並且及時的將最新的數據狀態反饋到 UI 上。

4. 擴展閱讀 ViewModel 的作用範圍

上文中持有 LiveData 的是我們創建的一個單例對象,他的作用範圍是整個APP,任何一個組件在訂閱該 LiveData 的時候,都將獲得相同的數據。但是如果我們配合ViewModel來使用呢?

我們修改部分代碼:
創建一個持有 LiveData 的 ViewModel

class PersonViewModel : ViewModel() {

    val list = ArrayList<Person>()

    val personList: MutableLiveData<ArrayList<Person>> by lazy {
        MutableLiveData<ArrayList<Person>>()
    }

    fun addPerson(person: Person) {
        list.add(person)
        personList.value = list
    }
}

修改 Activity:

class DemoActivity : AppCompatActivity() {

    private lateinit var model:PersonViewModel

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        setContentView(R.layout.activity_demo)
        val observer =  Observer<ArrayList<Person>>{
            textView.text = it.last().toString()
        }

//        SingletonLiveData.personList.observe(this,observer)
        model = ViewModelProviders.of(this).get(PersonViewModel::class.java)
        model.personList.observe(this,observer)
        var age = 19
        btnAdd.setOnClickListener {
//            SingletonLiveData.addPerson(Person("張三",++age,1))
            model.addPerson(Person("張三",++age,1))
        }
        supportFragmentManager.beginTransaction()
                .add(R.id.fl_content,BlankFragment.newInstance())
                .commit()
    }
}

修改 Fragment:

class BlankFragment : Fragment() {

    lateinit var lastPersonTv:TextView

    companion object {
        fun newInstance() = BlankFragment()
    }

    private lateinit var viewModel: PersonViewModel

    override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?,
                              savedInstanceState: Bundle?): View? {
        return inflater.inflate(R.layout.blank_fragment, container, false)
    }

    override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
        super.onViewCreated(view, savedInstanceState)
        lastPersonTv = view.findViewById(R.id.lastPerson)
        var age = 19
        view.findViewById<Button>(R.id.btnAddF).setOnClickListener {
//            SingletonLiveData.addPerson(Person("李四",++age,1))
            viewModel.addPerson(Person("李四",++age,1))
        }
    }

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        viewModel = ViewModelProviders.of(this).get(PersonViewModel::class.java)
        val observer =  Observer<ArrayList<Person>>{
            Logger.d("數據變化了")
            lastPersonTv.text = it.size.toString()
        }
//       SingletonLiveData.personList.observe(this,observer)
       viewModel.personList.observe(this,observer)
    }
}

我們會發現數據不再共享了,這是爲什麼麼?

原因其實很簡單,這涉及到 ViewModel 的作用範圍, LiveData 的作用範圍取決於持有他的對象。如果是一個單例模式對象持有,那麼這個 LiveData 全局共有,數據變化時,所有訂閱他的組件都將調用 onChanged() 方法。

但是 ViewModel 不是單例模式,如果使用 ViewModel 持有 LiveData 是不是就不能在組件之間共享數據了呢?答案是可以!

我們觀察 ViewModel 創建的方法ViewModelProviders.of(this).get(PersonViewModel::class.java)會發現,of 方法共有四個重載,它可以接受 Fragment 也可以接受 FragmentActivity。

其實我們只需要在 Fragment 中獲取 ViewModel 時,傳入 getActivity() 即可,這樣Activity 與 其所屬的 Fragment 就可以通過 ViewModel 持有的 LiveData 來實現數據共享。也就是說 ViewModel 通過 of() 方法的傳入值,來決定了 ViewModel 的作用範圍。

在多個Fragment嵌套時,使用 LiveData 有如下優勢:

  1. Activity 不需要做任何事,甚至不知道這次交互,完美解耦。
  2. Fragment 只需要 與ViewModel交互,不需要知道對方 Fragment 的狀態甚至是否存在,更不需要持有其引用。所有當對方 Fragment 銷燬時,不影響本身任何工作。
  3. Fragment 生命週期互不影響,甚至 fragment 替換成其他的 也不影響這個系統的運作。
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章