Jetpack Compose(3) —— 狀態管理

来源:https://www.cnblogs.com/joy99/p/18069453
-Advertisement-
Play Games

本文主要講解了 Compose 中狀態的概念。最後做個小結, - Compose UI 依賴狀態變化,觸發重組,驅動界面更新。 - 使用 remember 和 rememberSaveable 進行狀態持久化。remember 保證在 recompose 過程中狀態穩定,rememberSaveab... ...


上一篇文章拿 TextField 組件舉例時,提到了 State,即狀態。本篇文章,即講解 State 的相關改概念。

一、什麼是狀態

與其它聲明式 UI 框架一樣,Compose 的職責非常單純,僅作為對數據狀態的反應。如果數據狀態沒有改變,則 UI 永遠不會自行改變。在 Compose 中,每一個組件都是一個被 @Composable 修飾的函數,其狀態就是函數的參數,當參數不變,則函數的輸出就不會變,唯一的參數決定唯一輸出。反言之,如果要讓界面發生變化,則需要改變界面的狀態,然後 Composable 響應這種變化。
下麵還是拿個例子來說,做一個簡單的計數器,有一個顯示計數的控制項,一個增加的按鈕,每點擊一次,則技術計數器加 1 ,一個減少的按鈕,每點擊一次,計時器減 1。
假如我們用此前的 View 視圖體系,來寫這個方法。代碼大概像下麵這樣:

class MainActivity : AppCompatActivity() {
    // ...
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        binding.incrementBtn.setOnClickListener {
            binding.tvCounter.text = "${Integer.valueOf(binding.tvCounter.text.toString()) + 1 }"
        }

        binding.decrementBtn.setOnClickListener {
            binding.tvCounter.text = "${Integer.valueOf(binding.tvCounter.text.toString()) - 1 }"
        }
    }
}

顯然上面這個代碼,計數邏輯和 UI 的耦合度就很高。稍微優化一下:

class MainActivity : AppCompatActivity() {
    // ...
    private var counter: Int = 0

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        binding.incrementBtn.setOnClickListener {
            counter++
            updateCounter()
        }

        binding.decrementBtn.setOnClickListener {
            counter--
            updateCounter()
        }
    }

    private fun updateCounter() {
        binding.tvCounter.text = "$counter"
    }
}

這個代碼的改動主要在於,新增了 counter 用於計數,本質上屬於一種 “狀態上提”, 原本 TextView 內部的狀態 “mText”, 上提到了 Activity 中,這樣,即使更換了計數器的 UI, 計數邏輯依然可以復用。

但是當前的代碼,仍然有一些問題,比如計數邏輯在 Activity 中,無法到其它頁面進行復用,進一步使用 MVVM 結構進行改造。引入 ViewModel, 將狀態從 Activity 中上提到 ViewModel 中。

class CounterViewModel: ViewModel() {
    private var _counter: MutableStateFlow<Int> = MutableStateFlow(0)
    val counter: StateFlow<Int> get() = _counter

    fun incrementCounter() {
        _counter.value++
    }

    fun decrementCounter() {
        _counter.value--
    }
}

class MainActivity : AppCompatActivity() {
    // ...
    private val viewModel: CounterViewModel by viewModels()

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        // ...
        binding.incrementBtn.setOnClickListener {
            viewModel.incrementCounter()
        }

        binding.decrementBtn.setOnClickListener {
            viewModel.decrementCounter()
        }

        lifecycleScope.launch {
            repeatOnLifecycle(Lifecycle.State.STARTED) {
                viewModel.counter.collect {
                    binding.tvCounter.text = $it
                }
            }
        }
    }
}

有 Jetpack 庫使用經驗的應該非常熟悉上面的代碼,將狀態上提到 ViewModel 中,使用 StateFlow 或者 LiveData 包裝起來,在 Ativity 中監聽狀態的變化,從而自動刷新 UI。

下麵,我們在 Compose 中實現上述計數器:

@Composable
fun CounterPage() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        var counter = 0
        Text(text = "$counter")
        Button(onClick = { counter++ }) {
            Text(text = "increment")
        }
        Button(onClick = { counter-- }) {
            Text(text = "decrement")
        }
    }
}

我們寫出上面的代碼,運行。

結果發現,無論怎麼點擊,Text 顯示的值總是 0 ,我們的計數邏輯沒有生效。為了說明這個問題,現在增加一點日誌:

@Composable
fun CounterPage() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        var counter = 0
        Log.d("sharpcj", "counter text --> $counter")
        Text(text = "$counter")
        Button(onClick = {
            Log.d("sharpcj", "increment button click ")
            counter++
        }) {
            Text(text = "increment")
        }
        Button(onClick = {
            Log.d("sharpcj", "decrement button click ")
            counter--
        }) {
            Text(text = "decrement")
        }
    }
}

再次運行,點擊按鈕,看到日誌如下:

2024-03-12 21:39:27.530 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:39:30.859 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 21:39:31.309 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 21:39:31.468 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 21:39:31.762 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 21:39:31.927 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 21:39:32.661 21949-21949 sharpcj                 com.sharpcj.hellocompose             D  decrement button click 

我們重新捋一捋,Compose 的組件實際上就是一個個函數,Compose 刷新 UI 的邏輯是,狀態發生變化,觸發了重組,函數被重新調用,然後由於參數發生了變化,函數輸出改變了,最終渲染出的的畫面才會發生變化。
再看上面的代碼,我們期望是定義 counter 作為了 Text 組件的狀態,點擊 Button,改變 counter, 到這裡都沒有問題,那麼問題處在了哪裡呢?問題主要是 counter 發生了變化,沒有觸發重組,即函數沒有被重新調用,日誌也證明瞭這一點。
回看我們上面傳統 View 視圖的寫法,此前,我們改變了狀態,需要主動調用 updateCounter 方法去刷新 UI, 後面經過改造,我們把狀態提升到 ViewModel 中,不論是使用 StateFlow 還是使用 LiveData 包裝後,我們都需要在 Activity 中監聽狀態的變化,才能對狀態的變化做出響應。針對上面的例子,我們現在清楚了,計數器不生效原因在於 counter 改變後,Compose 沒有感知到,沒有觸發重組。下麵需要開始學習 Compose 中的狀態了。

二、Compsoe 中的狀態 State

2.1 State

如同傳統試圖中,需要使用 StateFlow 或者 LiveData 將狀態變數包裝成一個可觀察類型的對象。Compose 中也提供了可觀察的狀態類型,可變狀態類型 MutableState 和 不可變狀態類型 State。我們需要使用 State/MutableState 將狀態變數包裝起來,這樣即可觸發重組。更為方便的是,聲明式 UI 框架中,不需要我們顯示註冊監聽狀態變化,框架自動實現了這一訂閱關係。我們來改寫上面的代碼:

@Composable
fun CounterPage() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        val counter: MutableState<Int> = mutableStateOf(0)
        Log.d("sharpcj", "counter text --> ${counter.value}")
        Text(text = "${counter.value}")
        Button(onClick = {
            Log.d("sharpcj", "increment button click ")
            counter.value++
        }) {
            Text(text = "increment")
        }
        Button(onClick = {
            Log.d("sharpcj", "decrement button click ")
            counter.value--
        }) {
            Text(text = "decrement")
        }
    }
}

我們使用了 mutableStateOf() 方法初始化了一個 MutableState 類型的狀態變數,並傳入預設值 0 ,使用的時候,需要調用 counter.value
再次運行,結果發現,點擊按鈕,計數器值還是沒有變化,日誌如下:

2024-03-12 21:57:24.773  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:57:31.428  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 21:57:31.437  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:57:31.825  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 21:57:31.834  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:57:33.047  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 21:57:33.055  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:57:33.216  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 21:57:33.224  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:57:33.634  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 21:57:33.643  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 21:57:33.792  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 21:57:33.801  6791-6791  sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0

和上一次不一樣了,這次發現,點擊按鈕之後, Text(text = "${counter.value}") 有重新執行,即發生了重組,但是執行的時候,參數沒有改變,依然是 0,其實這裡涉及到一個重組作用域的概念,就是重組是有一個範圍的,關於重組作用範圍,稍後再講。這裡需要知道,發生了重組,Text(text = "${counter.value}") 有重新執行,那麼 val counter: MutableState<Int> = mutableStateOf(0) 也有重新執行,相當於重組時,counter 被重新初始化了,並賦予了預設值 0 。所以點擊按鈕發生了重組,但是計數器的值沒有發生改變。要解決這個問題,則需要使用到 Compose 中的一個重要函數 remember

2.2 remember

我們先看看 remember 函數的源碼:

/**
 * Remember the value produced by [calculation]. [calculation] will only be evaluated during the composition.
 * Recomposition will always return the value produced by composition.
 */
@Composable
inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
    currentComposer.cache(false, calculation)

remember 方法的作用是,對其包裹起來的變數值進行緩存,後續發生重組過程中,不會重新初始化,而是直接從緩存中取。具體使用如下:

@Composable
fun CounterPage() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        val counter: MutableState<Int> = remember { mutableStateOf(0) }
        Log.d("sharpcj", "counter text --> ${counter.value}")
        Text(text = "${counter.value}")
        Button(onClick = {
            Log.d("sharpcj", "increment button click ")
            counter.value++
        }) {
            Text(text = "increment")
        }
        Button(onClick = {
            Log.d("sharpcj", "decrement button click ")
            counter.value--
        }) {
            Text(text = "decrement")
        }
    }
}

再次運行,這次終於正常了。

看日誌也正確了。每次點擊都出發了重組,並且 counter 的值也沒有重新初始化。

2024-03-12 22:18:53.744 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 0
2024-03-12 22:19:10.397 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 22:19:10.421 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 1
2024-03-12 22:19:10.967 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 22:19:10.981 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 2
2024-03-12 22:19:11.181 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 22:19:11.195 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 3
2024-03-12 22:19:11.649 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 22:19:11.663 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 4
2024-03-12 22:19:11.806 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 22:19:11.821 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 5
2024-03-12 22:19:12.364 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 22:19:12.377 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 4
2024-03-12 22:19:12.640 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 22:19:12.657 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 3
2024-03-12 22:19:13.204 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  increment button click 
2024-03-12 22:19:13.220 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 4
2024-03-12 22:19:13.747 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  decrement button click 
2024-03-12 22:19:13.761 19790-19790 sharpcj                 com.sharpcj.hellocompose             D  counter text --> 3

上面的代碼中,我們創建 State 的方法如下:

val counter: MutableState<Int> = remember { mutableStateOf(0) }

使用時,通過 counter.value 來使用,這樣的代碼看起來就很繁瑣,我們可以進一步精簡寫法。
首先, Kotlin 支持類型推導,所以可以寫成下麵這樣:

val counter = remember { mutableStateOf(0) }

另外,藉助於 Kotlin 委托語法,Compose 實現了委托方式賦值,使用 by 關鍵字即可,用法如下:

var counter by remember { mutableStateOf(0) }

並導入如下方法:

import androidx.compose.runtime.getValue
import androidx.compose.runtime.setValue

在使用時,直接使用 counter++counter--

需要註意的一點是,沒有使用委托方式創建的對象,類型是 MutableState 類型,我們用 val 聲明,使用委托方式創建對象,對象類型是 MutableState 包裝的對象類型,這裡由於賦初始值為 0 ,根據類型推導,counter 就是 Int 型,由於要修改 counter 的值,所以須使用 var 將其聲明為一個可變類型對象。

2.3 rememberSaveable

使用 remember 雖然解決了重組過程中,狀態被重新初始化的問題,但是當 Activity 銷毀重建時,狀態值依然會重新初始化,比如橫豎屏旋轉,UiMode 切換等場景。在傳統試圖體系中,也存在這樣的問題,對此的解決方案有很多,比如重寫 Activity 的回調方法,在合適的時機,對數據進行保存和恢復,又或者使用 ViewModel 存放數據,這些方法對於 Compose 當然也有效,但是考慮到在使用 Compose 時,應該弱化 Activity 生命周期的概念,所以前者不適合在 Compose 中使用,而使用 ViewModel 依然是一種優秀的選擇,後文再介紹。但是把所有的數據都放到 ViewModel 中,是否是最好的呢,這個要根據具體場景,進行甄別。舉個例子,
針對這種場景,Compose 提供了 rememberSaveable 這個方法來解決這種場景的問題。

var counter by rememberSaveable { mutableStateOf(0) }

用法與 remember 方法用法類似,區別在於,rememberSaveable 在橫豎屏旋轉,UiMode 切換等場景中,能夠對其包裹的數據進行緩存。那是否說明 rememberSaveable 可以在所有的場景替換 remember , remember 方法就沒用了? rememberSaveable 方法比 remember 方法功能更強勁,代價就是性能要差一些,具體使用根據實際場景來選擇。

到這裡,狀態相關的知識點,應該就很清楚了,再回頭看上一篇文章中的 TextField 組件,應該能明白為什麼那樣寫了。

三、 Stateless 和 Stateful

聲明式 UI 的組件一般都可以分為 Stateless 組件和 Stateful 組件。
所謂 stateless 是指這個組件除了依賴參數以外,不依賴其它任何狀態。比如 Text 組件,

Text("Hello, Compose")

相對的,某個組件除了參數以外,還持有或者訪問了外部的狀態,稱為 stateful 組件。比如上一篇文章中提到的 TextField 組件,

var text by remember { mutableStateOf("文本框初始值") }
TextField(value = text, onValueChange = {
    text = it
})

Stateless 是不依賴於外部狀態,僅依賴傳入進來的參數,它是一個“純函數”,即唯一輸入,對應唯一輸出。也就是參數不變,UI 就不會變化,它的重組只能是來自上層的調用,因此 Compose 編譯器對其進行了優化,當 Stateless 的參數沒有變化時,它就不會參與重組,重組的範圍局限於 Stateless 外部。另外 Stateless 不耦合任何業務,功能更純粹,所以復用性更好,也更容易測試。
基於此,我們應該儘可能地將 stateful 組件改造成 stateless 組件,這個過程稱之為狀態上提。

3.1 狀態上提

狀態上提,通常的做法就是將內部狀態移除,以參數的形式傳入。以及需要回調給調用方的事件,也以參數形式傳入。
還是以上面計數器的代碼為例,為了簡潔,去掉前面添加的 log, 代碼如下:

@Composable
fun CounterPage() {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        var counter by remember{ mutableStateOf(0) }
        Text(text = "$counter")
        Button(onClick = {
            counter++
        }) {
            Text(text = "increment")
        }
        Button(onClick = {
            counter--
        }) {
            Text(text = "decrement")
        }
    }
}

這裡計數器主要是依賴了內部狀態 counter, 同時兩個按鈕的點擊事件,會改變 counter。狀態上提之後,該方法如下:

@Composable
fun CounterPage(counter: Int, onIncrement: () -> Unit, onDecrement: () -> Unit) {
    Column(horizontalAlignment = Alignment.CenterHorizontally) {
        Text(text = "$counter")
        Button(onClick = {
            onIncrement()
        }) {
            Text(text = "increment")
        }
        Button(onClick = {
            onDecrement()
        }) {
            Text(text = "decrement")
        }
    }
}

這樣,Counter 組件,就變成了 stateless 組件,不再與業務耦合,職責更加單一,可復用性和可測試性都更強了。此外,狀態上提,有助於單一數據源模型的打造。

四、狀態管理

我們再來看一下在 Compose 中應該如何管理狀態。

4.1 使用 stateful 管理狀態

簡單的的 UI 狀態,且與業務無關的狀態,適合在 Compose 中直接管理。
比如我有一個菜單列表,點一開關,展開一個菜單,再點一下,收起菜單,列表的狀態,僅由點擊開關這一單一事件決定。並且,列表的狀態與任何外部業務無關。那麼這種就適合在 Compose 內部進行管理。

4.2 使用 StateHolder 管理狀態

當業務有一定的複雜度之後,我們可以將業務邏輯相關的狀態統一封裝到一個 StateHoler 進行管理。剝離 Ui 邏輯,讓 Composable 專註 UI 佈局。

4.3 使用 ViewModel 管理狀態

從某種意義上講,ViewModel 也是一種特殊的 StateHolde。單因為它是保存在 ViewModelStore 中,所以有一下特點:

  • 存活範圍大,可以脫離 Composition 存在,被所有 Composable 共用。
  • 存活時間長,不會因為橫豎屏切換或者 UiMode 切換導致數據丟失。

因此,ViewModel 適合管理應用程式全局狀態,而且 ViewModel更傾向於管理哪些非 UI 的業務狀態。

以上管理方式可以同時使用,結合具體的業務靈活搭配。

4.4 LiveData、Rxjava、Flow 轉 State

在 MVVM 架構中,使用 ViewModel 來管理狀態,如果是新項目,把狀態直接定義 State 類型就可以了。

對於傳統試圖項目,一般使用 LiveData、Rxjava 或者 Flow 這類響應式數據框架。而在 Compose 中需要 State 觸發重組,刷新 UI,也有相應的方法,將上述響應式數據流轉換為 Compose 中的 State。當上有數據變化時,可以驅動 Composable 完成重組。具體方法如下:

拓展方法 依賴庫
LiveData.observeAsState() androidx.compose:runtime-livedata
Flow.collectAsState() 不依賴三方庫,Compose 自帶
Observable.subscribeAsState() androidx.compose:runtime-rxjava2 或者 androidx.compose:runtime-rxjava3

五、小結

本文主要講解了 Compose 中狀態的概念。最後做個小結,

  • Compose UI 依賴狀態變化,觸發重組,驅動界面更新。
  • 使用 remember 和 rememberSaveable 進行狀態持久化。remember 保證在 recompose 過程中狀態穩定,rememberSaveable 保證 Activity 自動銷毀重建過程中狀態穩定。
  • 狀態上提,儘可能將 Stateful 組件轉換為 Stateless 組件。
  • 視情況使用 Stateful、StateHoler、ViewModel 管理狀態。
  • 將 LiveData、RxJava、Flow 數據流轉換為 State。
作者:SharpCJ     作者博客:http://joy99.cnblogs.com/     本文版權歸作者和博客園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接。
您的分享是我們最大的動力!

-Advertisement-
Play Games
更多相關文章
  • 一:雙擊startup.bat但閃退 我們可以用記事本打開startup.bat 在末尾添加一個pause 這樣它就會新建一個視窗停在錯誤的地方 二:根據報錯信息改正 這一步如果有亂碼可以進入tomcat的conf目錄下logging.properties 找到 java.util.logging. ...
  • Prometheus是一個開源的監控和告警工具包,其常用的組件主要包括以下幾個部分: Prometheus Server 功能:Prometheus Server是Prometheus的核心組件,負責定時從被監控組件(如Kubernetes、Docker、主機等)中拉取(pull)數據,並將其存儲在 ...
  • 本文主要學習FreeRTOS任務管理的相關知識,包括FreeRTOS創建/刪除任務、任務狀態、任務優先順序、延時函數、空閑任務和任務調度方法等知識 ...
  • Windows從零安裝WordPress 在Linux中,可以用Linux運維工具配合docker很便捷的安裝並配置MySQL、nginx、php、WordPress,但是Windows伺服器中,我還沒有發現類似的面板,就嘗試學慣用最原始的方法進行安裝。 教程環境:Windows Server 20 ...
  • 概述:樂觀併發控制是處理數據訪問併發的一種策略,通過在更新前檢查版本號或時間戳,確保數據在事務間保持一致性。在MySQL示例中,通過比對版本號,如果發現其他事務已更新數據,則拒絕當前事務的修改,避免潛在的併發衝突。這種機制提高了數據一致性,典型應用包括樂觀鎖的實現。 數據訪問併發是指多個事務或用戶同 ...
  • RDS for MariaDB的實例狀態概覽功能,幫助客戶快速感知資料庫實例的整體健康度,並迅速定位異常,極大簡化了運維操作。 ...
  • 轉載至我的博客 https://www.infrastack.cn ,公眾號:架構成長指南 在併發一致性控制場景中,我們常常用for update悲觀鎖來進行一致性的保證,但是如果不瞭解它的機制,就進行使用,很容易出現事故,比如for update進行了鎖表導致其他請求只能等待,從而拖垮系統,因此了 ...
  • 本文詳細介紹了Libcomm通信庫及其原理,讓我們更好的理解GaussDB(DWS)集群通信中的具體邏輯,對於GaussDB(DWS)通信運維也具備一定的參考意義。 ...
一周排行
    -Advertisement-
    Play Games
  • 基於.NET Framework 4.8 開發的深度學習模型部署測試平臺,提供了YOLO框架的主流系列模型,包括YOLOv8~v9,以及其系列下的Det、Seg、Pose、Obb、Cls等應用場景,同時支持圖像與視頻檢測。模型部署引擎使用的是OpenVINO™、TensorRT、ONNX runti... ...
  • 十年沉澱,重啟開發之路 十年前,我沉浸在開發的海洋中,每日與代碼為伍,與演算法共舞。那時的我,滿懷激情,對技術的追求近乎狂熱。然而,隨著歲月的流逝,生活的忙碌逐漸占據了我的大部分時間,讓我無暇顧及技術的沉澱與積累。 十年間,我經歷了職業生涯的起伏和變遷。從初出茅廬的菜鳥到逐漸嶄露頭角的開發者,我見證了 ...
  • C# 是一種簡單、現代、面向對象和類型安全的編程語言。.NET 是由 Microsoft 創建的開發平臺,平臺包含了語言規範、工具、運行,支持開發各種應用,如Web、移動、桌面等。.NET框架有多個實現,如.NET Framework、.NET Core(及後續的.NET 5+版本),以及社區版本M... ...
  • 前言 本文介紹瞭如何使用三菱提供的MX Component插件實現對三菱PLC軟元件數據的讀寫,記錄了使用電腦模擬,模擬PLC,直至完成測試的詳細流程,並重點介紹了在這個過程中的易錯點,供參考。 用到的軟體: 1. PLC開發編程環境GX Works2,GX Works2下載鏈接 https:// ...
  • 前言 整理這個官方翻譯的系列,原因是網上大部分的 tomcat 版本比較舊,此版本為 v11 最新的版本。 開源項目 從零手寫實現 tomcat minicat 別稱【嗅虎】心有猛虎,輕嗅薔薇。 系列文章 web server apache tomcat11-01-官方文檔入門介紹 web serv ...
  • 1、jQuery介紹 jQuery是什麼 jQuery是一個快速、簡潔的JavaScript框架,是繼Prototype之後又一個優秀的JavaScript代碼庫(或JavaScript框架)。jQuery設計的宗旨是“write Less,Do More”,即倡導寫更少的代碼,做更多的事情。它封裝 ...
  • 前言 之前的文章把js引擎(aardio封裝庫) 微軟開源的js引擎(ChakraCore))寫好了,這篇文章整點js代碼來測一下bug。測試網站:https://fanyi.youdao.com/index.html#/ 逆向思路 逆向思路可以看有道翻譯js逆向(MD5加密,AES加密)附完整源碼 ...
  • 引言 現代的操作系統(Windows,Linux,Mac OS)等都可以同時打開多個軟體(任務),這些軟體在我們的感知上是同時運行的,例如我們可以一邊瀏覽網頁,一邊聽音樂。而CPU執行代碼同一時間只能執行一條,但即使我們的電腦是單核CPU也可以同時運行多個任務,如下圖所示,這是因為我們的 CPU 的 ...
  • 掌握使用Python進行文本英文統計的基本方法,並瞭解如何進一步優化和擴展這些方法,以應對更複雜的文本分析任務。 ...
  • 背景 Redis多數據源常見的場景: 分區數據處理:當數據量增長時,單個Redis實例可能無法處理所有的數據。通過使用多個Redis數據源,可以將數據分區存儲在不同的實例中,使得數據處理更加高效。 多租戶應用程式:對於多租戶應用程式,每個租戶可以擁有自己的Redis數據源,以確保數據隔離和安全性。 ...