成人免费xxxxx在线视频软件_久久精品久久久_亚洲国产精品久久久_天天色天天色_亚洲人成一区_欧美一级欧美三级在线观看

Jetpack Compose布局優(yōu)化實(shí)踐

開發(fā) 項(xiàng)目管理
在項(xiàng)目開發(fā)中列表布局占多數(shù),在 Compose? 中實(shí)現(xiàn)列表使用延時(shí)布局它包含了 LazyColumn、LazyRow?等布局,比如上一節(jié)使用 LazyColumn實(shí)現(xiàn)了一個(gè)客戶列表。

01、前言

我們內(nèi)部團(tuán)隊(duì)使用 Jetpack Compose 開發(fā)項(xiàng)目已近一年,經(jīng)歷了簡單布局到復(fù)雜布局的應(yīng)用,對(duì) Compose 的使用越來越成熟,構(gòu)造了很多易用的基礎(chǔ)組合,提升了項(xiàng)目的開發(fā)效率,與此同時(shí) Compose 布局的一些性能問題也慢慢凸顯出來,因此專門對(duì) Compose 布局優(yōu)化進(jìn)行了調(diào)研工作,旨在減少重組提高性能,規(guī)避負(fù)面效應(yīng),提高應(yīng)用穩(wěn)定性。結(jié)合具體場景來具體分析。

02、使用 remember 減少計(jì)算

我們構(gòu)造一個(gè)客戶列表,代碼如下:

@Composable
fun ClientList(list: MutableList<ClientInfo>, modifier: Modifier) {
    LazyColumn(modifier = modifier) {
        items(list) {
            ClientItem(it)
        }
    }
}

接著增加一個(gè)需求,將客戶列表按照年齡排序,我們改動(dòng)一下代碼:

@Composable
fun ClientList(list: MutableList<ClientInfo>, modifier: Modifier) {
    LazyColumn(modifier = modifier) {
        items(list.sortedBy { it.age }) {
            ClientItem(it)
        }
    }
}

上面代碼能夠正確運(yùn)行,只不過會(huì)有一點(diǎn)問題,就是每次重組都會(huì)對(duì) list 執(zhí)行排序操作。眾所周知在 Compose 中可組合項(xiàng)可能會(huì)非常頻繁的重組,也就意味著排序操作可能會(huì)非常頻繁的執(zhí)行,這顯然是不行的,因?yàn)榕判蚩赡軙?huì)占用較多的資源,導(dǎo)致布局卡頓。最理想的狀態(tài)應(yīng)該是數(shù)據(jù)變動(dòng)或者排序規(guī)則變動(dòng)才會(huì)觸發(fā)排序,達(dá)到這種狀態(tài)我們可以使用 remember或者將排序操作放到 ViewModel 當(dāng)中:

@Composable
fun ClientList(list: MutableList<ClientInfo>, modifier: Modifier) {
    // 通過remember方法,將list的排序結(jié)果緩存起來,當(dāng)list發(fā)生變化時(shí),才會(huì)重新排序
    val sortList = remember(key1 = list) {
        list.sortedBy { it.age }
    }
    LazyColumn(modifier = modifier) {
        items(sortList) {
            ClientItem(it)
        }
    }
}

在開發(fā)過程中應(yīng)該謹(jǐn)記一條規(guī)則:重組可能會(huì)頻繁的執(zhí)行,因此盡量避免在組合內(nèi)寫一些會(huì)引起副作用的代碼。

03、Lazy布局使用key

在項(xiàng)目開發(fā)中列表布局占多數(shù),在 Compose 中實(shí)現(xiàn)列表使用延時(shí)布局它包含了 LazyColumn、LazyRow等布局,比如上一節(jié)使用 LazyColumn實(shí)現(xiàn)了一個(gè)客戶列表。

接上繼續(xù)以客戶列表布局為例,如果對(duì)客戶列表進(jìn)行增加或者刪除,列表布局是如何重組的呢?為了探究這個(gè)問題,稍微改下代碼,增加一個(gè)添加客戶的按鈕:

Column {
    Row(modifier = Modifier.fillMaxWidth()) {
        Text(text = "添加新客戶", modifier = Modifier.clickable {
            Log.d("compose demo", "添加新客戶")
            //手動(dòng)插入一條數(shù)據(jù) 
            list.add(5, ClientInfo("新添加客戶", 5))
        })
    }
    ClientList(...)
}

然后在 LazyColumn作用域以及ClientItem中加上日志信息:

@Composable
fun ClientList(list: SnapshotStateList<ClientInfo>, modifier: Modifier) {
    LazyColumn(modifier = modifier) {
        Log.d("compose demo", "LazyColumn update")
        itemsIndexed(list) { _, item ->
            ClientItem(item)
        }
    }
}

@Composable
fun ClientItem(info: ClientInfo) {
    Log.d("compose demo", "item  name=${info.name} 重組")
    Text(text = "${info.name} ${info.age}", modifier = Modifier.height(44.dp))
}

接下來運(yùn)行一次,并點(diǎn)擊添加新客戶按鈕,控制臺(tái)輸出如下:

com.czx.demo       D  添加新客戶
com.czx.demo       D  LazyColumn update
com.czx.demo       D  item  name = 添加新客戶 重組
com.czx.demo       D  item  name = name ---- 5 重組
com.czx.demo       D  item  name = name ---- 6 重組
com.czx.demo       D  item  name = name ---- 7 重組
com.czx.demo       D  item  name = name ---- 8 重組
com.czx.demo       D  item  name = name ---- 9 重組
com.czx.demo       D  item  name = name ---- 10 重組
com.czx.demo       D  item  name = name ---- 11 重組
com.czx.demo       D  item  name = name ---- 12 重組
com.czx.demo       D  item  name = name ---- 13 重組
com.czx.demo       D  item  name = name ---- 14 重組

我們發(fā)現(xiàn)除了新添加的客戶項(xiàng)之外,在此位置之后的所有可見的客戶項(xiàng)都觸發(fā)了不必要的重組。如果想讓列表只重組新增項(xiàng),那么這里就要使用 key參數(shù)來避免這些不必要的重組,key參數(shù)是一個(gè)任意類型的值,用于標(biāo)識(shí)布局,并確保 Compose 框架在重新計(jì)算布局時(shí)正確地處理它們。改動(dòng)代碼加上key參數(shù):

@Composable
fun ClientList(list: SnapshotStateList<ClientInfo>, modifier: Modifier) {
    LazyColumn(modifier = modifier) {
        Log.d("compose demo", "LazyColumn update")
        //key參數(shù)指定
        itemsIndexed(list, key = { _, item -> item.id }) { _, item ->
            ClientItem(item)
        }
    }
}

需要注意的是 key參數(shù)要保證唯一性這樣才能確保 Compose 框架能夠正確地計(jì)算和更新列表項(xiàng),加上 key參數(shù)代碼運(yùn)行后臺(tái)輸出如下:

com.czx.demo       D  添加新客戶
com.czx.demo       D  LazyColumn update
com.czx.demo       D  item  name = 添加新客戶 重組

之前的不必要重組沒有了,只重組了添加項(xiàng),符合預(yù)期。

Tips: 這里一定要保證 key參數(shù)的唯一性,否則會(huì)出現(xiàn)不必要的重組,影響性能。

04、使用derivedStateOf限制重組

繼續(xù)使用上面的客戶列表,新增一個(gè)需求當(dāng)?shù)谝粋€(gè)可見項(xiàng)大于0的時(shí)候,展示回到頂部的按鈕,按照需求我們對(duì)代碼做如下改動(dòng):

1.增加listState來監(jiān)聽列表狀態(tài):

val listState = rememberLazyListState()

2.通過listState獲取當(dāng)前可見項(xiàng),判斷是否展示回到頂部 button :

val showButton = listState.firstVisibleItemIndex > 0

3.回到頂部按鈕顯隱:

if (showButton){
    ScrollToTopButton()
 }

再將列表包裹一層布局整體代碼如下:

Box {
    val listState = rememberLazyListState()

    ClientList(...)

    val showButton = listState.firstVisibleItemIndex > 0

     if (showButton){
       Log.d("compose demo", "button 重組")
       ScrollToTopButton()
    }
}

運(yùn)行代碼并上下滑動(dòng)列表,控制臺(tái)輸出:

com.czx.demo       D  item  name = name ---- 17 重組
com.czx.demo       D  item  name = name ---- 18 重組
com.czx.demo       D  item  button 重組
com.czx.demo       D  item  button 重組

可以看到觸發(fā)了多次重組,雖然 showButton只關(guān)心 firstVisibleItemIndex是否是從 0 變?yōu)榉?0 ,但是這種寫法當(dāng) firstVisibleItemIndex大于 0 時(shí)會(huì)一直被觸發(fā),從而引起了不必要的重組。要想規(guī)避這種情況可以使用 derivedStateOf()函數(shù)來處理頻繁變更的數(shù)據(jù):

val showButton by remember {
    derivedStateOf {
        listState.firstVisibleItemIndex > 0
    }
}

控制臺(tái)輸出:

com.czx.demo       D  item  name = name ---- 17 重組
com.czx.demo       D  item  name = name ---- 18 重組
com.czx.demo       D  item  button 重組
com.czx.demo       D  item  name = name ---- 19 重組
com.czx.demo       D  item  name = name ---- 20 重組
com.czx.demo       D  item  name = name ---- 21 重組
com.czx.demo       D  item  name = name ---- 22 重組

連續(xù)滑動(dòng)只會(huì)觸發(fā)一次重組。

05、延遲讀取 

Compose 有三個(gè)階段 組合、布局和繪制 ,可以通過盡可能的跳過三個(gè)步驟中的一個(gè)或者多個(gè)來提高性能。

06、場景一

val color by animateColorBetween(Color.Red, Color.Blue)
Box(modifier = Modifier.fillMaxSize().background(color))

代碼能夠運(yùn)行并且滿足我們的要求,如果足夠細(xì)心可以發(fā)現(xiàn)這里隱藏著一個(gè)優(yōu)化點(diǎn),上面提到 Compose 的三個(gè)階段組合、布局和繪制,對(duì)于示例代碼而言,僅僅是改變背景顏色,不需要重組和布局,那么我們對(duì)代碼進(jìn)行優(yōu)化。

val color by animateColorBetween(Color.Red, Color.Blue)
Box(modifier = Modifier.fillMaxSize().drawBehind {  
    drawRect(color = color)
})

我們使用了 drawBehind()函數(shù),該函數(shù)發(fā)生在繪制時(shí)期,由于僅改變背景顏色,所以這里改變方框的背景顏色使用 drawRect達(dá)到一樣的效果,這樣繪制就成了唯一重復(fù)執(zhí)行的階段,進(jìn)而提高性能。

07、場景二

@Composable
fun SnackDetail() {
    //...
    Box(Modifier.fillMaxSize()) {  // Recomposition Scope Start
        val scroll = rememberScrollState(0)
        // ...
        Title(snack, scroll.value) //1.狀態(tài)讀取
        // ...
    } //Recomposition Scope End
}

@Composable
private fun Title(snack: Snack, scroll: Int) {
    //...
    val offset = with(LocalDensity.current) { scroll.toDp() }

    Column(
        modifier = Modifier
            .offset(y = offset) //2.狀態(tài)使用
    ) {
        //...
    }
}

對(duì) scroll.value的讀取會(huì)使 Box()發(fā)生重組,但是 scroll的使用卻不是在 Box()中,這種讀取與使用位置不一致的情況,往往會(huì)有性能優(yōu)化的空間。對(duì)于這種情況我們將讓讀取和使用位置一致:

@Composable
fun SnackDetail() {
    // ...

    Box(Modifier.fillMaxSize()) { // Recomposition Scope Start
        val scroll = rememberScrollState(0)
        // ...
        Title(snack) { scroll.value } 
        // ...
    } 
    // Recomposition Scope end
}

@Composable
private fun Title(snack: Snack, scrollProvider: () -> Int) {
    // ...
    val offset = with(LocalDensity.current) { scrollProvider().toDp() }
    Column(
        modifier = Modifier
            .offset(y = offset) // 狀態(tài)讀取+使用
    ) {
    // ...
    }
}

這樣當(dāng) scroll.value()變化時(shí)不會(huì)觸發(fā)重組,也就是在滑動(dòng)中唯二執(zhí)行的階段只有布局和繪制。

08、避免向后寫入

Compose中有個(gè)核心       假設(shè):您永遠(yuǎn)不會(huì)向已被讀取的狀態(tài)寫入數(shù)據(jù)。如果破壞了這個(gè)假設(shè)也就是向后寫入,可能會(huì)造成一些不必要的重組。

舉個(gè)例子:

@Composable
fun BadComposable() {
    var count by remember { mutableStateOf(0) }

    // Causes recomposition on click
    Button(onClick = { count++ }, Modifier.wrapContentSize()) {
        Text("Recompose")
    }

    Text("$count") //1
    count++ // Backwards write, writing to state after it has been read
}

點(diǎn)擊按鈕后會(huì) count++執(zhí)行,注釋1處讀取了 count因此會(huì)觸發(fā)重組,但是同時(shí)末尾處的 count++也會(huì)執(zhí)行,最終導(dǎo)致之前狀態(tài)過期,注釋 1 繼續(xù)讀取,然后陷入循環(huán),count++一直執(zhí)行,每一幀都在重組。這會(huì)造成嚴(yán)重的性能問題,所以應(yīng)該避免在組合中進(jìn)行狀態(tài)寫入,盡量在響應(yīng)事件中寫入狀態(tài)。

07、發(fā)布模式&R8優(yōu)化

Compose并不是 Android 系統(tǒng)庫,而是作為獨(dú)立的庫進(jìn)行引入。這樣做的好處就是可以兼容舊的安卓版本以及頻繁的更新功能,但是也會(huì)產(chǎn)生性能上的開銷,導(dǎo)致首次啟動(dòng)或者首次使用一個(gè)庫功能時(shí)變得比較慢。

下圖是冷啟動(dòng)耗時(shí)對(duì)比(單位:ms):

圖片圖片

可以看到發(fā)布模式 +R8+Profile 下的冷啟動(dòng)耗時(shí)是最短的。發(fā)布模式一般默認(rèn)開啟了 R8 優(yōu)化,具體優(yōu)化細(xì)節(jié),這里不做展開。另外值得一提的是Profile,它是 Compose 官方定義的基準(zhǔn)配置文件,專門用來提高性能。

基準(zhǔn)配置文件中定義關(guān)鍵用戶歷程所需的類和方法,并與應(yīng)用的 APK 一起分發(fā)。在應(yīng)用安裝期間,ART 會(huì)預(yù)先編譯該關(guān)鍵代碼,以確保在應(yīng)用啟動(dòng)時(shí)可供使用。要定義一個(gè)良好的基準(zhǔn)配置文件并不容易,因而此 Compose 隨帶了一個(gè)默認(rèn)的基準(zhǔn)配置文件。您無需執(zhí)行任何操作即可直接使用該配置文件。但是,如果選擇定義自己的配置文件,則可能會(huì)生成一個(gè)無法實(shí)際提升應(yīng)用性能的配置文件。

10、總結(jié)

以上結(jié)合代碼示例介紹了 Jetpack Compose中的布局優(yōu)化手段,總結(jié)下來就是在應(yīng)用開發(fā)中,應(yīng)盡量減少不必要的重組來提高性能。因此我們需要合理的使用 remember、 Lazy布局的key, derivedStateOf等手段,來遵循最佳性能實(shí)踐。

11、引用

Jetpack Compose 官方文檔:https://developer.android.com/jetpack/compose

本文轉(zhuǎn)載自微信公眾號(hào)「 搜狐技術(shù)產(chǎn)品」,作者「 蔡志學(xué)」,可以通過以下二維碼關(guān)注。

轉(zhuǎn)載本文請(qǐng)聯(lián)系「 搜狐技術(shù)產(chǎn)品」公眾號(hào)。

責(zé)任編輯:武曉燕 來源: 搜狐技術(shù)產(chǎn)品
相關(guān)推薦

2024-03-06 08:25:31

Compose開發(fā)界面

2025-05-26 08:24:45

2021-02-25 18:22:34

AndroidGoogle 移動(dòng)系統(tǒng)

2025-05-28 01:20:00

JetpackCompose元素

2014-07-29 15:23:06

Android

2025-05-29 02:20:00

2020-12-04 14:32:33

AndroidJetpackKotlin

2025-06-13 08:26:08

JetpackCompose按鈕

2020-03-23 15:15:57

MySQL性能優(yōu)化數(shù)據(jù)庫

2012-05-08 16:37:23

android

2013-03-27 09:17:17

Android開發(fā)AndroidList

2025-06-03 05:00:00

JetpackCompose技巧

2020-07-17 19:55:50

Vue前端性能優(yōu)化

2014-12-17 09:46:30

AndroidListView最佳實(shí)踐

2017-01-23 21:05:00

AndroidApp啟動(dòng)優(yōu)化

2022-06-06 12:19:08

抖音功耗優(yōu)化Android 應(yīng)用

2025-06-19 09:53:30

Spring性能優(yōu)化服務(wù)器

2013-09-17 10:17:39

Android布局

2009-12-31 15:21:48

Silverlight

2010-07-06 09:07:09

點(diǎn)贊
收藏

51CTO技術(shù)棧公眾號(hào)

主站蜘蛛池模板: 欧美日韩精品久久久免费观看 | 黄色日本片 | 97色在线视频 | 在线午夜| 欧洲免费毛片 | 久久亚洲综合 | 中文字幕日韩专区 | 欧美日韩国产在线观看 | 免费av电影网站 | 欧美一级免费 | 欧美日韩国产在线观看 | 免费欧美 | 综合色播| 欧美一级全黄 | 国产日韩91| 粉嫩粉嫩芽的虎白女18在线视频 | 久草院线 | 日本不卡免费新一二三区 | 黄色av一区 | 日本黄色一级片视频 | 亚洲香蕉在线视频 | 人人九九精 | 成人欧美一区二区三区黑人孕妇 | av中文字幕在线观看 | 九九热视频这里只有精品 | 四虎永久免费地址 | 成人av一区二区三区 | 成人免费淫片aa视频免费 | 岛国av一区二区 | 精品一区二区三区入口 | 久久精品国产免费 | 久久九精品| 中文在线一区二区 | www狠狠干 | 日韩成人免费视频 | 国产精品污www一区二区三区 | 欧美日韩三区 | 欧美寡妇偷汉性猛交 | 国内久久 | 日日干日日操 | 亚洲一区二区三区在线 |