关于MutableState

MutableState

  • 其本身不能被观察状态。

mutableStateOf()

  • mutableStateOf创建一个可以被观察状态的对象。
  • 当状态发生变化的时候,就能够通知UI框架,做响应的UI刷新。这就是声明式的UI编写方式。

使用方案

等号方式:

 val name = mutableStateOf("RyanHuen")

 setContent{
     Text(name.value)
 }

by 方式:

  • by关键字会自动引入kotlin的Delegate代理
  • Delegate代理会自动帮我们在runtime时候处理数据的setValue和getValue,所以使用by就能省却相关的“.value”操作。
var name by mutableStateOf("RyanHuen")

setContent{
    Text(name)
}

原理

SnapshotMutableState
  • MutableState -> StateObject -> StateRecord -> 链表
  • 支持事务功能的状态管理
    • 即:支持批量更新、同时支持撤销等相关行为
    • 通过StateRecord链表可以获取到整个State链的任意节点
  • readable()
    • 当有任何的MutableState的value被读取时,readable()函数会invoke所有observe这个value的观察者,通过这种机制,标记value相关的代码发生了状态变更。
fun <T : StateRecord> T.readable(state: StateObject, snapshot: Snapshot): T {
    // invoke the observer associated with the current snapshot.
    snapshot.readObserver?.invoke(state)//状态标记
    return readable(this, snapshot.id, snapshot.invalid) ?: readError()//取值
}

Snapshot
  • Compose中有多个Snapshot,Snapshot之间有先后关系。因为存在先后关系,所以通过Snapshot也能持续获取StateRecord中的值。
  • 同一个StateRecord对应一个Snapshot,但是一个Snapshot可以对应多个StateRecord。
readable
  • Snapshot内部的readable函数,对应一个readObserver。
  • 当环境中有任意位置对Snapshot对应的stateRecord进行读取时,将invoke对应的readObserver,让观察方记录下来有任何地方使用过对应的数据。
writeable
  • Snapshot内部的writeable函数,对应一个writeObserver。
  • 当环境中有任意位置对Snapshot对应的stateRecord进行写入时,将invoke对应的writeObserver,让观察方针对read过的数据使用区块进行标记失效。
  • 当标记为失效的UI部分,在随后的Compose过程中,就会进行UI的刷新。

remember

Recompose Scope(重组作用域)

  • 在编译期的时候,包含可变变量的Compose部分代码块,会根据变量可变情况,重新包装组合代码,将某一部分可能会因为变量变化,引起的重新布局等情况的代码组合包装起来。
  • 这种代码块包装后的区域,就是重组作用域。这部分作用域内的代码块,就会随时根据变量变化的情况,进行Recompose。

作用域内失效

  • 如下代码:
setContent{
    var name by mutableStateOf("ryanhuen")
    Text(name)
    	
    lifecycleScope.launch{
    	name = "new name"
    }
}
  • 上述代码在执行的过程中,因为重组作用域内,包含了对name这个MutableState的重新声明合初始化。因此,即便我们在协程中进行了数据变更,从UI上,也会让name被重新初始化成其他对象。
  • 最终造成协程没有更新成功UI的现象。
  • 这就叫做:作用域内失效。

缩小作用域

setContent{
    var name by mutableStateOf("ryanhuen")
    Box{
    	Text(name)
    }
    	
    lifecycleScope.launch{
    	name = "new name"
    }
}
  • 通过上述代码的方式,将Text()的重组作用域缩小,就能够让name持续生效。
  • 最终的伪代码效果会类似如下行为:
setContent{
    var name by mutableStateOf("ryanhuen")
    Box{
    	WrapperFunction{//重组作用域会将Text(name)部分独立包装,此时就不会造成失效了
    		Text(name)
    	}
    }
    	
    lifecycleScope.launch{
    	name = "new name"
    }
}
  • 但是上述方案,是一个非常没有意义的Workaround,我们当然不能这么去做
  • 同时我们也可以尝试避免在Compose的代码块中,声明mutableState,将所有mutableState都放在Compsoe外部声明初始化即可。
  • 但这种方式无疑也会影响我们的编码方便程度。

引入Remember

  • remember是一个@Composable函数,只能在Compose代码块中使用
  • 当我们在Compose的代码块内部使用MutableState时,需要我们针对性地将MutableState进行remember包装
setContent{
    var name by remember { mutableStateOf("ryanhuen") }
    Text(name)
    
    lifecycleScope.launch{
    	name = "new name"
    }
}
  • remember的作用大概类似于kotlin中的 by lazy{}。
  • 当首次执行到remember的时候,会执行lambda中的代码进行初始化一个mutableState。
  • 随后的每一次执行到remember,将会直接返回初始化好的对应mutableState对象。
  • 这样在重组作用域内,name的对象不会被重建,从而做到了Text()和mutableState始终绑定。

全量remember

  • 我们永远无法从代码上单独辨识哪一部分时重组作用域内会失效的变量。
  • 因为Compose的代码块是多嵌套的写法,即使在某一个嵌套内不会重组,但是也难保在更外围的嵌套不会导致失效。
  • 因此既然无法识别。那就将所有需要保护的数据,全都包装上remember即可。

避免重业务无法处理

@Composable
fun requestPicAndShow(url :String){
    
    var picBitmap by remember{ url.toBitmap }//伪代码,帮助我们通过一个url获取到bitmap
    Image(picBitmap)
}
//上述代码,使用remmber,在外部频繁调用requestPicAndShow()函数的时候,就能避免网络请求获取图片的事情被频繁执行,频繁重组,影响效率

参数remember

  • remember(xargs) : 支持我们传入最多三个参数
  • 避免每次url都重新请求bitmap,但是一旦url改变,我们确实需要重新请求bitmap
@Composable
fun requestPicAndShow(url :String){
    
    var picBitmap by remember(url){ url.toBitmap }//伪代码,帮助我们通过一个url获取到bitmap
    Image(picBitmap)
}
//上述代码,就能够控制,当url变更,会重新执行lambda中的代码,否则会始终复用旧数据

 评论