Android Jetpack 组件是库的集合,这些库是为协同工作而构建的,不过也可以单独采用,同时利用 Kotlin提高工作效率。可全部使用,也可混合搭配,这些库包括基础、架构、行为、界面四个模块,其中主要学习的是架构这个模块,也称为Android
Architecture,如图所示:
功能介绍:
Data Binding Library 可以在布局中通过表达式的语言绑定UI组件,可以在数据变化时更新Ui组件,减少很多样板代码,且提供更好的灵活性以及更强的兼容性,在Android 4.0以上设备运行。
环境配置,app的gralde中打开dataBinding的开关:
android {
...
dataBinding {
enabled = true
}
}
基本使用,XML文件定义,data binding的XML文件以layout作为根tag, 节点中包含该XML绑定的对象,接下来才是页面的布局文件,例如activity_main.xml:
/>
如何进行数据绑定?当我们在layout文件中使用了data binding之后,ide会生成相应的binding class,默认的名字也是根据layout文件名生成,例如xml文件名为xxx_yyy.xml,那么生成的class名为XxxYyyBinding,例如demo_activity.xml,生成DemoActivityBinding:
class DataBindingAcy : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
/**
* 获取dataBinding class 的两种方法
*/
val binding:DemoActivityBinding = DataBindingUtil.setContentView(this,R.layout.demo_activity)
val bindingOther:DemoActivityBinding = DemoActivityBinding.inflate(layoutInflater)
binding.user = User(100,"Demo")
}
}
表达式语言可以使用以下的一些操作符或者关键词:
例如:
三元操作符判断非空也可以用??来表述,例如:
android:text="@{user.firstName ?? user.secondName"}
//等同于
android:text="@{user.firstName != null ? user.firstName:user.lastName}"
以下的关键字需要不能在layout中使用:
data binding class可以避免空指针异常,例如当“@{user.name}"的user为空时,那么自动给这个值赋值为空,或者“@{user.id}"的user为空时,这个值默认设置为0.
在xml中使用集合:
更多的用法可以参考官方文档…
Data binding允许我们通过表达式去处理view相关的事件,事件属性名由listener决定,例如View.OnClickListener拥有一个onClick()方法,那么这个事件的属性名就是android:onClick.
方法引用:
事件可以直接绑定给方法,例如:
class MyHandlers{
fun onClickFriend(view:View){...}
xml中应用:
Listener bindings
例如:
class Presenter{
fun onSaveClick(task:Task){}
处理可观察的数据对象
Data Binding Library可以使得对象、变量、集合成为可观察的。当这些可观察数据对象绑定了UI,observable UI就能自动更新。
基本使用,定义observable类型的model类:
class ObservableUser {
val firstName = ObservableField()
val lastName = ObservableField()
val age = ObservableInt()
}
xml中:
java类/kotlin中:
获取生成的DemoActivityBinding对象,然后就可以获取到observableUser对象,给它赋值,当这个对象变化时,Ui就会自动更新,不用手动去重新设置。
通常管理生命周期时,都是直接实现各个生命周期函数,然后再相应的进行各种处理。但是这样会导致代码臃肿且难以维护,Lifecycle-Aware组件根据当前activity、fragment的生命周期状态自动的调整它们的行为。
这个功能所需的类和接口在这个包下“andorid.arch.leftcyle"。
大部分的在Android Framework中定义的组件都有附加的生命周期。生命周期由操作系统或者framework代码进行管理,由于这些是内核代码所以应用程序只能够遵从,尽量避免不合适的过多工作引起内存泄漏或者应用程序crash。
设想如果我们有一个需要展示当前设备位置的activity,一般的实现如下:
internal class MyLocationListener(
private val context: Context,
private val callback: (Location) -> Unit
) {
fun start() {
// connect to system location service
}
fun stop() {
// disconnect from system location service
}
}
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this) { location ->
// update UI
}
}
public override fun onStart() {
super.onStart()
myLocationListener.start()
Util.checkUserStatus{
result ->
if(result){
myLocationListener.start(). //可能存在activity已经stop了,这里才回调,那么这个listener可能就一直都存活,一直无法stop。
}
}
}
public override fun onStop() {
super.onStop()
myLocationListener.stop()
// manage other components that need to respond
// to the activity lifecycle
}
}
实际应用场景上,可能需要在生命周期中管理很多这种类似的代码,也有可能造成生命周期的泄漏。
Lifecycle就是解决上面问题的,Lifecycle用两个枚举去追踪生命周期状态与其关联的组件,
Event
生命周期events事件由framework和Lifecycle进行派发,这些事件映射到activity和fragment的回调事件。
State
表示被Lifecycle追踪的组件的当前状态。
State状态图
Lifecycle可以通过添加方法注解来监控组件的生命周期,然后可以通过addObserver()来添加一个观察者,例如:
class MyObserver : LifecycleObserver {
@OnLifecycleEvent(Lifecycle.Event.ON_RESUME) //绑定到Lifecycle的onResume()
fun connectListener() {
...
}
@OnLifecycleEvent(Lifecycle.Event.ON_PAUSE). //绑定到Lifecycle的onPause()
fun disconnectListener() {
...
}
}
那么当组件执行onResume()或者onPause()时,就会调用connectListener()或者disconnectListener().如何解决上面的生命周期泄漏的问题呢,可以添加一个开关,如下:
class MyActivity : AppCompatActivity() {
private lateinit var myLocationListener: MyLocationListener
override fun onCreate(...) {
myLocationListener = MyLocationListener(this, lifecycle) { location ->
// update UI
}
Util.checkUserStatus { result ->
if (result) {
myLocationListener.enable() //执行了回调才打开开关
}
}
}
}
enable()中根据条件打开开关:
internal class MyLocationListener(
private val context: Context,
private val lifecycle: Lifecycle,
private val callback: (Location) -> Unit
) {
private var enabled = false
@OnLifecycleEvent(Lifecycle.Event.ON_START)
fun start() {
if (enabled) {
// connect
}
}
fun enable() {
enabled = true
if (lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)) { //根据是否已经started才打开开关
// connect if not connected
}
}
@OnLifecycleEvent(Lifecycle.Event.ON_STOP)
fun stop() {
// disconnect if connected
}
}
activity中注册:
class MyActivity : Activity(), LifecycleOwner {
private lateinit var mLifecycleRegistry: LifecycleRegistry
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mLifecycleRegistry = LifecycleRegistry(this)
mLifecycleRegistry.markState(Lifecycle.State.CREATED)
}
public override fun onStart() {
super.onStart()
mLifecycleRegistry.markState(Lifecycle.State.STARTED)
}
override fun getLifecycle(): Lifecycle {
return mLifecycleRegistry
}
}
LiveData是一种可观察型的数据对象,并且是有生命周期意识的,确保在数据更新时,去更新处于active状态的组件。LiveData型对象知道观察者的生命周期,相比起Observable fields更加便利,所以在Android studio3.1之后的版本,都可以用LiveData去替换Observable fields.
ViewModel是用于存储和管理Ui相关数据的。ViewModel允许数据在配置发生改变时能够保留下来。ViewModel用LiveData来保存相关数据。如上面使用一样,实现ViewModel然后创建LiveData型数据对象。
创建LiveData数据对象:
class NameLiveDataModel :ViewModel() {
//create a LiveData with a String
val currentName:MutableLiveData by lazy {
MutableLiveData()
}
}
LiveData如何绑定UI控件呢?
class NameLiveDataAcy : AppCompatActivity() {
private lateinit var mModel:NameLiveDataModel
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: DemoActivityBinding = DataBindingUtil.setContentView(this, R.layout.demo_activity)
binding.setLifecycleOwner(this)
mModel = ViewModelProviders.of(this).get(NameLiveDataModel::class.java)
//create the observer which updates the UI
val nameObserver = Observer{
newName -> tv_firstName.text = newName
}
//通过observe()将LiveData型数据和UI控件绑定起来
mModel.currentName.observe(this,nameObserver)
//更新LiveData对象
mBtn.setOnClickListener{
val anotherName = "John Doe"
mModel.currentName.setValue(anotherName) //会调用nameObserver的onChanged(),然后更新控件UI
}
}
}