<output id="qn6qe"></output>

    1. <output id="qn6qe"><tt id="qn6qe"></tt></output>
    2. <strike id="qn6qe"></strike>

      亚洲 日本 欧洲 欧美 视频,日韩中文字幕有码av,一本一道av中文字幕无码,国产线播放免费人成视频播放,人妻少妇偷人无码视频,日夜啪啪一区二区三区,国产尤物精品自在拍视频首页,久热这里只有精品12

      Jetpack Compose(5)——生命周期與副作用函數(shù)

      一、 Composable 的生命周期

      Composable 組件都是函數(shù),Composable 函數(shù)執(zhí)行會得到一棵視圖樹,每一個 Composable 組件對應(yīng)視圖樹上的一個節(jié)點(diǎn)。Composable 的生命周期定義如下:

      • onActive(添加到視圖樹) Composable 首次被執(zhí)行,即在視圖樹上創(chuàng)建對應(yīng)的節(jié)點(diǎn)。
      • onUpdate(重組) Composable 跟隨重組不斷執(zhí)行,更新視圖樹上對應(yīng)的節(jié)點(diǎn)。
      • onDispose(從視圖樹移除) Composable 不再被執(zhí)行,對應(yīng)節(jié)點(diǎn)從視圖樹上移除。

      對于 Compose 編寫 UI 來說,頁面的變化,是依靠狀態(tài)的變化,Composable 進(jìn)行重組,渲染出不同的頁面。當(dāng)頁面可見時,對應(yīng)的節(jié)點(diǎn)被添加到視圖樹,當(dāng)頁面不可見時,對應(yīng)的節(jié)點(diǎn)從視圖樹移除。所以,雖然 Activity 有前后臺的概念,但是使用 Compose 編寫的頁面,對于 Composable 沒有前后臺切換的概念。當(dāng)頁面切換為不可見時,對應(yīng)的節(jié)點(diǎn)也被立即銷毀了,不會像 Activity 或者 Fragment 那樣在后臺保存實(shí)例。

      二、 Composable 的副作用

      上一篇將重組的文章講到,Composable 重組過程中可能反復(fù)執(zhí)行,并且中間環(huán)節(jié)有可能被打斷,只保證最后一次執(zhí)行的狀態(tài)時正確的。
      試想一個問題,如果在 Composable 函數(shù)中彈一個 Toast ,當(dāng) Composable 發(fā)生重組時,這個 Toast 會彈多少次,是不是就無法控制了。再比如,在 Composable 函數(shù)中讀寫函數(shù)之外的變量,讀寫文件,請求網(wǎng)絡(luò)等等,這些操作是不是都無法得到保證了。類似這樣,在 Composable 執(zhí)行過程中,凡是會影響外界的操作,都屬于副作用。在 Composable 重組過程中,這些副作用行為都難以得到保證,那怎么辦?為了是副作用只發(fā)生在生命周期的特定階段, Compose 提供了一系列副作用函數(shù),來確保行為的可預(yù)期性。下面,我們看看這些副作用函數(shù)的使用場景。

      2.1 SideEffect

      SideEffect 在每次成功重組的時候都會執(zhí)行。
      Composable 在重組過程中會反復(fù)執(zhí)行,但是重組不一定每次都會成功,有的可能會被中斷,中途失敗。 SideEffect 僅在重組成功的時候才會執(zhí)行

      特點(diǎn):

      1. 重組成功才會執(zhí)行。
      2. 有可能會執(zhí)行多次。
        所以,SideEffect 函數(shù)不能用來執(zhí)行耗時操作,或者只要求執(zhí)行一次的操作。

      典型使用場景,比如在主題中設(shè)置狀態(tài)欄,導(dǎo)航欄顏色等。

      SideEffect {
          val window = (view.context as Activity).window
          window.statusBarColor = colorScheme.primary.toArgb()
          WindowCompat.getInsetsController(window, view).isAppearanceLightStatusBars = darkTheme
      }
      

      2.2 DisposableEffect

      DisposableEffect 可以感知 Composable 的 onActiveonDispose, 允許使用該函數(shù)完成一些預(yù)處理和收尾工作。

      典型的使用的場景,注冊與取消注冊:

      DisposableEffect(vararg keys: Any?) {
          // register(callback)
          onDispose {
              // unregister(callback)
          }
      }
      

      這里首先參數(shù) keys 表示,當(dāng) keys 變化時, DisposableEffect 會重新執(zhí)行,如果在整個生命周期內(nèi),只想執(zhí)行一次,則可以傳入 Unit
      onDispose 代碼塊則會在 Composable 進(jìn)入 onDispose 時執(zhí)行。

      2.3 LaunchedEffect

      LaunchedEffect 用于在 Composable 中啟動協(xié)程,當(dāng) Composable 進(jìn)入 onAtive 時,LaunchedEffect 會自動啟動協(xié)程,執(zhí)行 block 中的代碼。當(dāng) Composable 進(jìn)入 onDispose 時,協(xié)程會自動取消。
      使用方法:

      LaunchedEffect(vararg keys: Any?) {
          // do Something async
      }
      

      同樣支持可觀察參數(shù),當(dāng) key 變化時,當(dāng)前協(xié)程自動結(jié)束,同時開啟新協(xié)程。

      2.4 rememberCoroutineScope

      LaunchedEffect 只能在 Composable 中調(diào)用,如果想在非 Composable 環(huán)境中使用協(xié)程,比如在 Button 的 OnClick 中開啟協(xié)程,并希望在 Composable 進(jìn)入 onDispose 時自動取消,則可以使用 rememberCoroutineScope 。
      具體用法如下:

      @Composable
      fun Test() {
          val scope = rememberCoroutineScope()
          Button(
              onClick = {
                  scope.launch {
                      // do something
                  }
              }
          ) {
              Text("click me")
          }
      }
      

      DisposableEffect 配合 rememberCoroutineScope 可以實(shí)現(xiàn) LaunchedEffect 同樣的效果,但是一般這樣做沒有什么意義。

      2.5 rememberUpdatedState

      rememberUpdatedState 一般和 DisposableEffect 或者 LaunchedEffect 配套使用。當(dāng)使用 DisposableEffect 或者 LaunchedEffect時,代碼塊中用到某個值會在外部更新,如何獲取到最新的值呢?看一個例子,比如玩王者榮耀時,預(yù)選英雄,然后將英雄顯示出來,十秒倒計(jì)時后,顯示最終選擇的英雄,倒計(jì)時期間,可以改變選擇的英雄。

      @Composable
      fun ChooseHero() {
          var sheshou by remember {
              mutableStateOf("狄仁杰")
          }
      
          Column {
              Text(text = "預(yù)選英雄: $sheshou")
              Button(onClick = {
                  sheshou = "馬可波羅"
              }) {
                  Text(text = "改選:馬可波羅")
              }
              FinalChoose(sheshou)
          }
      }
      
      @Composable
      fun FinalChoose(hero: String) {
          var tips by remember {
              mutableStateOf("游戲倒計(jì)時:10s")
          }
          LaunchedEffect(key1 = Unit) {
              delay(10000)
              tips = "最終選擇的英雄是:$hero"
          }
          Text(text = tips)
      }
      

      代碼運(yùn)行效果如下:

      我們預(yù)選了狄仁杰,倒計(jì)時期間,點(diǎn)擊 button, 改選馬可波羅,最終選擇的英雄確顯示狄仁杰。
      分析原因如下:在 FinalChoose 中參數(shù) hero 來源于外部,它的值改變,會觸發(fā)重組,但是,由于 LaunchedEffect 函數(shù),key 賦值 Unit, 重組過程中,協(xié)程代碼塊并不會重新執(zhí)行,感知不到外部的變化。要使能夠獲取到外部的最新值,一種方式是將 hero 作為 LaunchedEffect 的可觀察參數(shù)。修改代碼如下:

      @Composable
      fun FinalChoose(hero: String) {
          var tips by remember {
              mutableStateOf("游戲倒計(jì)時:10s")
          }
          LaunchedEffect(key1 = hero) {
              delay(10000)
              tips = "最終選擇的英雄是:$hero"
          }
          Text(text = tips)
      }
      

      此時再次執(zhí)行,在倒計(jì)時期間,我們點(diǎn)擊 button, 改變預(yù)選英雄,結(jié)果顯示正常了,最終選擇的即為馬可波羅。但是該方案并不符合我們的需求,前面講到, LaunchedEffect 的參數(shù) key,發(fā)生變化時,協(xié)程會取消,并重新啟動新的協(xié)程,這意味著,當(dāng)?shù)褂?jì)時過程中,我們改變了 key , 重新啟動的協(xié)程能夠獲取到改變后的值,但是倒計(jì)時也重新開始了,這顯然不是我們所期望的結(jié)果。

      rememberUpdatedState 就是用來解決這種場景的。在不中斷協(xié)程的情況下,始終能夠獲取到最新的值??匆幌?rememberUpdatedState 如何使用。
      我們把 LaunchedEffect 的參數(shù) key 還原成 Unit。使用 rememberUpdatedState 定義 currentHero。

      @Composable
      fun FinalChoose(hero: String) {
          var tips by remember {
              mutableStateOf("游戲倒計(jì)時:10s")
          }
      
          val currentHero by rememberUpdatedState(newValue = hero)
      
          LaunchedEffect(key1 = Unit) {
              delay(10000)
              tips = "最終選擇的英雄是:$currentHero"
          }
          Text(text = tips)
      }
      

      這樣,運(yùn)行結(jié)果就符合我們的預(yù)期了。

      2.6 derivedStateOf

      上面的例子中,有一點(diǎn)不完美的地方,游戲倒計(jì)時時間沒有更新。下面使用 derivedStateOf 來優(yōu)化這個功能。

      @Composable
      fun FinalChoose(hero: String) {
          var time by remember {
              mutableIntStateOf(10)
          }
      
          val tips by remember {
              derivedStateOf {
                  "游戲倒計(jì)時:${time}s"
              }
          }
      
          LaunchedEffect(key1 = Unit) {
              repeat(10) {
                  delay(1000)
                  time--
              }
          }
          Text(
              text = if (time == 0) {
                  "最終選擇的英雄是:$hero"
              } else {
                  tips
              }
          )
      }
      

      現(xiàn)在效果好多了。這里我們不再需要 rememberUpdatedState 了。首先定義了時間,時一個 Int 類型的 State,然后借助 derivedStateOf 定義 tip ,時一個 String 類型的 State。
      derivedStateOf 的作用是從一個或者多個 State 派生出另一個 State。如果某個狀態(tài)是從其他狀態(tài)對象計(jì)算或派生得出的,則可以使用 derivedStateOf。使用此函數(shù)可確保僅當(dāng)計(jì)算中使用的狀態(tài)之一發(fā)生變化時才會進(jìn)行計(jì)算。
      derivedStateOf 的使用不難,但是和 remember 的配合使用可以有很多玩法來適應(yīng)不同的場景,主要的關(guān)注點(diǎn)還是在觸發(fā)重組的條件上,這個要綜合實(shí)際的場景和性能來覺得是用 key 來觸發(fā)重組還是改變引用的狀態(tài)來觸發(fā)重組。

      2.7 snapshotFlow

      前面使用 rememberUpdatedState 可以在 LaunchedEffect 中始終獲取到外部狀態(tài)的最新的值。但是無法感知到狀態(tài)的變化,也就是說外部狀態(tài)變化了,LaunchedEffect 中的代碼無法第一時間被通知到。用 snapshotFlow 則可以解決這個場景。
      snapshotFlow 用于將一個 State<T> 轉(zhuǎn)換成一個協(xié)程中的 Flow。 當(dāng) snpashotFlow 塊中讀取到的 State 對象之一發(fā)生變化時,如果新值與之前發(fā)出的值不相等,F(xiàn)low 會向收集器發(fā)出最新的值(此行為類似于 Flow.distinctUntilChaned)。
      看具體使用:

      @Composable
      fun FinalChoose(hero: String) {
          var time by remember {
              mutableIntStateOf(10)
          }
      
          var tips by remember {
              mutableStateOf("游戲倒計(jì)時:10s")
          }
      
          LaunchedEffect(key1 = Unit) {
              launch {
                  repeat(10) {
                      delay(1000)
                      time--
                  }
              }
              launch {
                  snapshotFlow { time }.collect {
                          tips = "游戲倒計(jì)時:${it}s"
                      }
              }
          }
      
          Text(
              text = if (time == 0) {
                  "最終選擇的英雄是:$hero"
              } else {
                  tips
              }
          )
      }
      

      運(yùn)行結(jié)果和上一次一樣,這里我們不再使用 derivedStateOf, 而是啟動了兩個協(xié)程,一個協(xié)程用于倒計(jì)時技術(shù),另一個協(xié)程則將 time 這個 State 轉(zhuǎn)換成 Flow, 然后進(jìn)行收集,并更新 tips。

      2.8 produceState

      produceState 用于將任意外部數(shù)據(jù)源轉(zhuǎn)換為 State。
      比如上面的例子中,我們將倒計(jì)時時間定義在 ViewModel 中,并且倒計(jì)時的邏輯在 ViewModel 中實(shí)現(xiàn),在 UI 中就可以借助 produceState 來實(shí)現(xiàn)。

      @Composable
      fun FinalChoose(hero: String) {
          val time = viewModel.time
      
          val tips by produceState<String>(initialValue = "游戲倒計(jì)時:10s") {
              value = "游戲倒計(jì)時:${time}s"
      
              awaitDispose {
                  // 做一些收尾的工作
              }
          }
          Text(
              text = if (time == 0) {
                  "最終選擇的英雄是:$hero"
              } else {
                  tips
              }
          )
      }
      

      我們看一下 produceState 的源碼實(shí)現(xiàn):

      @Composable
      fun <T> produceState(
          initialValue: T,
          vararg keys: Any?,
          producer: suspend ProduceStateScope<T>.() -> Unit
      ): State<T> {
          val result = remember { mutableStateOf(initialValue) }
          @Suppress("CHANGING_ARGUMENTS_EXECUTION_ORDER_FOR_NAMED_VARARGS")
          LaunchedEffect(keys = keys) {
              ProduceStateScopeImpl(result, coroutineContext).producer()
          }
          return result
      }
      

      很好理解,就是定義了一個狀態(tài) State, 然后啟動了一個協(xié)程,在協(xié)程中去更新 State 的值。參數(shù) key 發(fā)生變化時,協(xié)程會取消,然后重新啟動,生成新的 State。
      同時注意到,在 produceState 中可以使用 awaitDispose{ } 方法做一些收尾工作。這是不是很容易聯(lián)想到 callbackFlow 的使用場景。沒錯,基于回調(diào)的接口實(shí)現(xiàn),利用 callbackFlow 很容易轉(zhuǎn)換為協(xié)程的 Flow, 而 produceState 即可將其轉(zhuǎn)換為 Compose 中的 State。比如 BroadcastReceiver、ContentProvider、網(wǎng)絡(luò)請求等等。

      val currentPerson by produceState<Person?>(null, viewModel) {
          val disposable = viewModel.registerPersonObserver { person ->
              value = person
          }
      
          awaitDispose {
              disposable.dispose()
          }
      }
      

      再看一個網(wǎng)絡(luò)請求的例子:

      @Composable
      fun GetApi(url: String, repository: Repository): Recomposer.State<Result<Data>> {
          return produceState(initialValue = Result.Loading, url, repository) {
              val data = repository.load(url)
              value = if (result == null) {
                  Result.Error
              } else {
                  Result.Success(data)
              }
          }
      }
      

      三、總結(jié)

      本文主要介紹了 Composable 的聲明周期,以及常用的副作用函數(shù)。
      在重組過程中,應(yīng)該極力避免副作用的發(fā)生。根據(jù)場景,使用合適的副作用函數(shù)。

      寫在最后

      個人認(rèn)為 Compose 中最重要的知識域有兩個——狀態(tài)和重組、Modifier 修飾符。經(jīng)過前面這些文章的講解,狀態(tài)和重組基本上主要的知識點(diǎn)都講到了,知識有一定的前后連貫性。而 Modifier 修飾符龐大的類別體系中,將不再具有這樣的關(guān)聯(lián),可以挨個獨(dú)立學(xué)習(xí)。接下來的文章,我將不依次介紹 Modifier 的類別。而是介紹 Android 開發(fā)中的應(yīng)用領(lǐng)域在 Compose 中的處理方式,比如自定義 Layout, 動畫,觸摸反饋等等,然后在這些知識點(diǎn)中,講解涉及到的 Modifier。歡迎大家繼續(xù)關(guān)注!

      posted @ 2024-04-04 00:08  SharpCJ  閱讀(2706)  評論(1)    收藏  舉報
      主站蜘蛛池模板: 99热成人精品热久久66| 亚洲精品无码日韩国产不卡av | 国产午夜亚洲精品福利| 精品国产污污免费网站| 无码专区人妻系列日韩精品| 精品超清无码视频在线观看| 波多野结衣的av一区二区三区 | 亚洲中文字幕日产无码成人片| 亚洲欧洲日韩国内高清| 自拍偷拍一区二区三区四| 亚洲高清最新AV网站| 日韩丝袜欧美人妻制服| 日韩va中文字幕无码电影| 国产二区三区视频在线| 性男女做视频观看网站| 热久久美女精品天天吊色| 亚洲中文精品久久久久久不卡 | 亚洲精品av一二三区无码| 亚洲人成亚洲人成在线观看| 亚洲av乱码久久亚洲精品 | 欧美老熟妇乱子伦牲交视频| 欧美精品国产综合久久| 欧美老少配性行为| 99精品人妻少妇一区| 99热精品国产三级在线观看| 亚洲精品日本一区二区| 人人爽亚洲aⅴ人人爽av人人片| 欧美人妻在线一区二区| 国产亚洲欧洲av综合一区二区三区| 亚洲av网一区天堂福利| 国产精品一二三中文字幕| 国产精品一区在线免费看| 久久久久99精品成人片牛牛影视 | 男女激情一区二区三区| 麻豆一区二区三区精品视频| 日本一区二区三区在线播放| 亚洲美女厕所偷拍美女尿尿 | 中文字幕日韩精品东京热| 天美传媒mv免费观看完整 | 樱花草视频www日本韩国| 精品久久人人做爽综合|