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. 添加依赖 (根据实际需求添加)
    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    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"
    }
  2. 创建一个 LiveData 实例用于保存特定类型数据,这通常在自己定义的 ViewModel 内内完成 。
  3. 创建一个 Observer 对象并重写 onChanged() 方法,当 LiveData 对象中的数据发生变化时,会调用该方法,并传递包装在LiveData中的数据。该方法在主线程调用,通常用于变更 UI 状态。
  4. 将 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 的生命周期。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    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()

  2. 在组件中使用 LiveData 实现数据共享
    在Activity中使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    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中使用:

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    13
    14
    15
    16
    17
    18
    19
    20
    21
    22
    23
    24
    25
    26
    27
    28
    29
    30
    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

1
2
3
4
5
6
7
8
9
10
11
12
13
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
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:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
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 替换成其他的 也不影响这个系统的运作。