敞开生长之旅!这是我参加「日新方案 2 月更文应战」的第 9 天,点击查看活动概况

往期文章
鬼话Compose筑基(1) – ()
鬼话Compose筑基(2) – ()
鬼话Compose筑基(3) – ()

智能重组

重组会越过尽可能多的内容

假如界面的某些部分无效,Compose 会尽力只重组需求更新的部分。这意味着,它能够越过某些内容以重新运转单个按钮的可组合项,而不履行界面树中在其上面或下面的任何可组合项。

经过之前文章中的比如能够发现,Composable 函数在重组中被调用时,假如参数与前次调用时相比没有产生变化,则函数的履行会越过重组,提高重组性能,咱们把这种重组称为智能重组。可是实际上有些时分参数没有变化也会触发重组。

鬼话Compose筑基(3) – ()的终究留了一个思考题,假如列表中的一个电影目标的特点产生了更新,Compose是怎么智能重组的呢?咱们来看下面的代码:

override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, true)
    setContent {
        println("SetContent")
        //提高状况到setContent函数
        val moviesState = mutableStateListOf(
            Movie(120, 1L, "教父1"),
            Movie(135, 2L, "教父2"),
            Movie(155, 3L, "教父3"),
        )
        Row {
            //横向排列,先放一个MovieScreen的竖向列表,传参moviesState
            MovieScreen(movies = moviesState)
            //再放一个竖向排列的布局,分别放两个按钮
            Column {
                //点击第一个按钮在列表的头部增加一条电影目标
                Button(onClick = { moviesState.add(0, Movie(166, 4L, "疤面煞星")) }) {
                    Text(text = "点击增加一条")
                }
                //点击第二个按钮咱们把列表头部的目标更新(id title 不变 duration变成177)
                Button(
                    onClick = {
                        //依照一般的习惯咱们应该是用 moviesState[0].duration=177去更新目标的特点
                        //此处咱们更新的是列表,而不是列表中的元素。为什么呢?下面会解释
                        moviesState[0] = Movie(
                            id = 4L,
                            duration = 177,
                            title = "疤面煞星",
                        )
                    },
                ) {
                    Text(text = "点击改动一条")
                }
            }
        }
    }
}
//上节的内容,咱们用当时域的唯一id来标识每个可组合项的实例,来完成智能重组
@Composable
fun MovieScreen(movies: List<Movie>) {
    LazyColumn {
        this.items(movies, key = { item: Movie -> item.id }) {
            MovieOverview(movie = it)
        }
    }
}
data class Movie(
    val duration: Int,
    val id: Long,
    val title: String
)
@Composable
fun MovieOverview(movie: Movie){
    println("${ movie.title }的可组合项开端初始构建或重构")
    Column(modifier = Modifier) {
        Text(text = movie.title)
        Text(text = "时长${movie.duration}分钟")
    }
}

初次运转日志如下

 I  SetContent
 I  教父1的可组合项开端初始构建或重构
 I  教父2的可组合项开端初始构建或重构
 I  教父3的可组合项开端初始构建或重构

现在咱们先点击第一个按钮在列表头部增加一条数据,再来看看日志

 I  疤面煞星的可组合项开端初始构建或重构

到这儿为止其实很好的验证了上篇文章咱们介绍的内容。咱们经过可组合项实例的标识,完成了智能重构。在增加新的条目时,之前在列表中的条目都越过了重组。下面咱们再点击第二个按钮去更新一下刚增加进来的条目,日志打印

 I  疤面煞星的可组合项开端初始构建或重构

条目在页面上显示的时长从166改写到177了,达到了咱们的预期作用。可是在上面的代码中咱们点击按钮去改动头部条目内容的写法存在歧义。咱们新创建了一个Movie实例去替换了列表中的元素,而不是直接改动目标的特点。那么为什么咱们要这样做呢?依照直接改目标特点的思路咱们改动一下代码:

//要改哪个特点,咱们首要要把改特点的申明val为var,这儿以duration为例
data class Movie(
    var duration: Int,
    val id: Long,
    val title: String
)
//然后在第二个按钮的点击咱们直接改值
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, true)
    setContent {
        println("SetContent")
        //提高状况到setContent函数
        val moviesState = mutableStateListOf(
            Movie(120, 1L, "教父1"),
            Movie(135, 2L, "教父2"),
            Movie(155, 3L, "教父3"),
        )
        Row {
            //横向排列,先放一个MovieScreen的竖向列表,传参状况moviesState到函数
            MovieScreen(movies = moviesState)
            //再放一个竖向排列的布局,分别放两个按钮
            Column {
                //点击第一个按钮在列表的头部增加一条电影目标
                Button(onClick = { moviesState.add(0, Movie(166, 4L, "疤面煞星")) }) {
                    Text(text = "点击增加一条")
                }
                Button(
                    onClick = {
                        moviesState[0].apply {
                            duration.value=177
                        }
                    },
                ) {
                    Text(text = "点击改动一条")
                }
            }
        }
    }
}

其他不变咱们运转一下再看看日志

初次运转日志跟之前相同

 I  SetContent
 I  教父1的可组合项开端初始构建或重构
 I  教父2的可组合项开端初始构建或重构
 I  教父3的可组合项开端初始构建或重构

然后咱们点击按钮增加一条,页面确实增加了,可是再看日志

 I  教父1的可组合项开端初始构建或重构
 I  教父2的可组合项开端初始构建或重构
 I  教父3的可组合项开端初始构建或重构
 I  疤面煞星的可组合项开端初始构建或重构

发现之前的智能重组没有作用了!!一切可组合项都进行了重组。并且点击第二个按钮来改写刚增加的条目时,页面并没有改写。所以仅仅把val duration改成了var duration就导致了智能重组失效,并且发现直接修正moviesState里边元素的目标并不会触发调集的更新也就是收不到告诉,从而导致可组合项不会去重组,终究页面也不会改写。咱们下面来一个一个的解决这两个问题。

官方文档有句话是这样说的。假如组合中已有可组合项,当一切输入都处于安稳状况且没有变化时,能够越过重组。那么咱们反过来了解,假如输入处于不安稳状况且有变化时,不能越过重组,也就是不能智能重组了。结合上面的比如,能够推断出Movie数据类被Compose归为了不安稳类型导致了智能重组失败。那么安稳类型和是否可变是怎么界说的呢?

安稳类型

  • 安稳类型界说

    安稳类型必须契合以下协定:

    • 关于相同的两个实例,其equals的成果将一直相同。
    • 假如类型的某个公共特点产生变化,组合将收到告诉。
    • 一切公共特点类型也都是安稳。

一句话总结一下:全部公共特点必须是不可变类型或许公共特点产生改动时能在组合中收到告诉就是安稳类型

别的Compose 编译器也会将其视为安稳的类型。

  • 一切基元值类型:BooleanIntLongFloatChar等。
  • 字符串
  • 一切函数类型 (lambda)

上面的比如中咱们为了修正特点,把Movie数据类的公共特点duration变成了var(可变类型),所以导致Movie也变成了不安稳类型,才导致了智能重组失败。已知的解决办法是把duration 还原成val,可是仅仅这样做的话,咱们是不能直接改动duration的值的,那么就无法完成咱们的预期。所以依照上面的安稳类型界说的要求再把duration改成可被追寻的MutableState,看看作用

//这儿咱们先用第一种改法,把duration还原成val,然后类型改成可被追寻的MutableState的安稳类型来完成后边的修正特点后重构改写页面
data class Movie(
    val duration: MutableState<Int>,
    val id: Long,
    val title: String
)
@Composable
fun MovieOverview(movie: Movie){
    println("${ movie.title }的可组合项开端初始构建或重构")
    Column(modifier = Modifier) {
        Text(text = movie.title)
        //经过value来取值
        Text(text = "时长${movie.duration.value}分钟")
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, true)
    setContent {
        println("SetContent")
        val moviesState = mutableStateListOf(
            //构造数据的时分咱们也相应的改动
            Movie(mutableStateOf(120), 1L, "教父1"),
            Movie(mutableStateOf(135), 2L, "教父2"),
            Movie(mutableStateOf(155), 3L, "教父3"),
        )
        Row {
            MovieScreen(movies = moviesState)
            Column {
                //构造数据的时分咱们也相应的改动
                Button(onClick = { moviesState.add(0, Movie(mutableStateOf(166), 4L, "疤面煞星")) }) {
                    Text(text = "点击增加一条")
                }
                Button(
                    onClick = {
                        moviesState[0].apply {
                            duration.value=177
                        }
                    },
                ) {
                    Text(text = "点击改动一条")
                }
            }
        }
    }
}

运转后点击增加按钮日志如下

I  疤面煞星的可组合项开端初始构建或重构

OK,这个时分Movie现已满意了Compose对安稳类型的要求,所以增加的时分是智能重组。再来点击第二修正按钮看看

I  疤面煞星的可组合项开端初始构建或重构

被修正的这条产生了重组,页面也按预期更新了。这样修正确实完成了预期,可是现实中的完成咱们一般不这么写。假定咱们的数据类Movie是经过服务器接口的数据生成的,那么duration的特点就不能界说成MutableState了,所以咱们能够用by mutableStateOf特点委托的方法来获取一个var(非final)的字段来修正和追寻数据类的特点。

data class Movie(
    val duration: Int,
    val id: Long,
    val title: String,
) {
    //经过吧duration特点委托出去,在维持了该类安稳类型的前提下,完成了公共特点的可追寻来完成后边的智能重组
    var durationState by mutableStateOf(duration)
}
@Composable
fun MovieOverview(movie: Movie) {
    println("${movie.title}的可组合项开端初始构建或重构")
    Column(modifier = Modifier) {
        Text(text = movie.title)
        Text(text = "时长${movie.durationState}分钟")
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, true)
    setContent {
        println("SetContent")
        val moviesState = mutableStateListOf(
            Movie(120, 1L, "教父1"),
            Movie(135, 2L, "教父2"),
            Movie(155, 3L, "教父3"),
        )
        Row {
            MovieScreen(movies = moviesState)
            Column {
                Button(onClick = { moviesState.add(0, Movie(166, 4L, "疤面煞星")) }) {
                    Text(text = "点击增加一条")
                }
                Button(
                    onClick = {
                        moviesState[0].apply {
                            durationState=177
                        }
                    },
                ) {
                    Text(text = "点击改动一条")
                }
            }
        }
    }
}

现在完成了预期作用如下

大话Compose筑基(4)

@Stable注解

这玩意儿就比较猛了。在类或许接口上用,直接告诉编译器当时类或许接口是安稳类型。当用于函数或特点时,此注解指示假如传入相同的参数,函数将回来相同的成果。仅当参数和成果自身是安稳、不可变或是基本类型时,这才有意义。由于安稳类型的特性,所以咱们只能在确保不会有改动的状况下才会用此注解,可是这种状况少之又少,滥用的话往往会造成一些预期外的影响。咱们稍微改一下前面的比如

@Stable
data class Movie(
    var duration: Int,
    val id: Long,
    val title: String,
)
@Composable
fun MovieOverview(movie: Movie) {
    println("${movie.title}的可组合项开端初始构建或重构")
    Column(modifier = Modifier) {
        Text(text = movie.title)
        Text(text = "时长${movie.duration}分钟")
    }
}
override fun onCreate(savedInstanceState: Bundle?) {
    super.onCreate(savedInstanceState)
    WindowCompat.setDecorFitsSystemWindows(window, true)
    setContent {
        println("SetContent")
        val moviesState = mutableStateListOf(
            Movie(120, 1L, "教父1"),
            Movie(135, 2L, "教父2"),
            Movie(155, 3L, "教父3"),
        )
        Row {
            MovieScreen(movies = moviesState)
            Column {
                Button(onClick = { moviesState.add(0, Movie(166, 4L, "疤面煞星")) }) {
                    Text(text = "点击增加一条")
                }
                Button(
                    onClick = {
                        moviesState[0].apply {
                            duration=177
                        }
                    },
                ) {
                    Text(text = "点击改动一条")
                }
            }
        }
    }
}

运转后发现,增加的流程完成了预期的智能重组,可是修正的流程并没有改写页面。由于@Stable注解让编译器认为这两个相同实例的equals的成果将一直持平。所以也就越过了重组,没有完成预期。

小结

此篇作为筑基系列的倒数第二篇,也可能是最难了解的一篇。尽管乍一看内容不多,但其实融入了之前3篇的很多内容。尽管示例代码较多,但核心代码就那几行,不要一开端就被唬住了,信任只要静心下来把示例代码看懂,也算是Compose入门了。打好了根底后,后边不管是自学还是看一些大佬写的高阶的Compose文章都会事半功倍,自然会进入一个快速提高的阶段。

敞开生长之旅!这是我参加「日新方案 2 月更文应战」的第 9 天,点击查看活动概况