1. 程式人生 > 其它 >MVIKotlin學習筆記(5):時間旅行

MVIKotlin學習筆記(5):時間旅行

時間旅行

時間旅行是一個強大的除錯工具,它允許你記錄所有來自活躍的Stores的事件和狀態。當事件被記錄後你可以瀏覽、重演和除錯它。它的核心功能是多平臺,被所有支援目標實現。然而,一些特定的功能只能在特定的平臺上使用。

時間旅行是一種除錯工具,它可能會影響效能。理想情況下它不應該在生產環境中使用。

啟動並使用時間旅行有三個主要步驟:

  1. 向所有Store的工廠提供一個time-travel-aware,這是StoreFactory的變體。
  2. 在app上執行時間旅行服務端。
  3. 使用客戶端連線服務端並使用它。

提供一個StoreFactory用於時間旅行

TimeTravelStoreFactory

用於建立Store的例項,它能夠記錄和重演事件。在除錯構建中,StoreFactory的變體能夠通過DI傳遞給所有Store工廠,而不是DefaultStoreFactory

本節介紹的功能可用於所有受支援的目標。

假設有以下Store工廠:

internal class CalculatorStoreFactory(private val storeFactory: StoreFactory) {

    fun create(): CalculatorStore =
        object : CalculatorStore, Store<Intent, State, Nothing> by storeFactory.create(
            name = "CounterStore",
            // ...
        ) {
        }

    // 其他程式碼
}

它接受一個StoreFactory並用它來建立CalculatorStore的例項。現在可以傳遞任何StoreFactory。如果你想啟用時間旅行,只要傳遞TimeTravelStoreFactory的例項即可:

val storeFactory = TimeTravelStoreFactory()

CalculatorStoreFactory(storeFactory).create()

也可以組合它和LoggingStoreFactory

val storeFactory = LoggingStoreFactory(TimeTravelStoreFactory())

CalculatorStoreFactory(storeFactory).create()

通常需要在主程式的某處定義一個全域性的StoreFactory,並將它傳遞給所有依賴項。

執行時間旅行服務端

每個啟用了時間旅行的app都有一個TimeTravelController的全域性例項。每個Store自動連線到這個控制器。這個控制器接受來自外部的各種指令、來自以註冊的Stores的記錄事件、替換狀態和重新觸發事件用於除錯。

為了允許遠端控制,app應該執行一個時間旅行服務端。服務端綁定了TimeTravelController和外部世界。服務端的實現在不同的平臺是不同的。目前,以下的目標受支援:

  • 基於JVM的目標:androidjvm
  • Darwin/Apple目標:ios,tvos,watchosmacos
  • JavaScript(js),只支援谷歌瀏覽器

為其他平臺實現服務端應該沒有技術限制。作者歡迎提issue。

所有的服務端實現(除了JavaScript)是基於TCP的。預設埠是6379,埠可以在初始化期間顯式更改。

通訊協議是開放的,但被認為是內部的。不同版本之間沒有相容性保證。

在安卓app上執行

首先,在app模組中匯入時間旅行依賴項,替換<version>為最後一個發行版本

implementation("com.arkivanov.mvikotlin:mvikotlin-timetravel:<version>")

Application類的onCreate()中開始時間旅行

class App : Application() {
    private val timeTravelServer = TimeTravelServer()

    override fun onCreate() {
        super.onCreate()
        timeTravelServer.start()
    }
}

由於時間服務服務端確實使用了裝置上的網際網路與開發機器進行通訊,因此即使應用程式不使用網際網路,您也需要在AndroidManifest.xml中宣告使用網際網路許可權。

在JVM app上執行

首先,在app模組中匯入時間旅行依賴項,替換<version>為最後一個發行版本

implementation("com.arkivanov.mvikotlin:mvikotlin-timetravel:<version>")

在應用的main函式中建立一個TimeTravelServer的例項並提供runOnMainThread引數,這可以是SwingUtilities.invokeLater {}或其他被使用的coroutines/Reaktive。

fun main() {
    TimeTravelServer(runOnMainThread = { SwingUtilities.invokeLater(it) })
        .start()
}

在Drawin/Apple app上執行

為了在Drawin/Apple裝置上設定TimeTravelServermvikotlin-timetravel依賴項必須在build.gradle.kts的共享模組中匯入。與此同時,mvikotlin-timetravel模組必須作為api依賴項新增。這可以在commonMain源集或只在Dawwin源集中完成。

kotlin {
    ios {
        binaries {
            framework {
                export("com.arkivanov.mvikotlin:mvikotlin-timetravel:<version>")
            }
        }
    }

    sourceSets {
        named("commonMain") {
            dependencies {
                api("com.arkivanov.mvikotlin:mvikotlin-timetravel:<version>")
            }
        }
    }
}

然後在AppDelegate中啟動TimeTravelServer的例項。

@UIApplicationMain
class AppDelegate: UIResponder, UIApplicationDelegate {

    private let s = TimeTravelServer()

    func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
        // 在應用啟動後重寫定製點。
        s.start()
        
        return true
    }

}

在谷歌瀏覽器(JavaScript)上執行

首先,在app模組中匯入時間旅行依賴項,替換<version>為最後一個發行版本

implementation("com.arkivanov.mvikotlin:mvikotlin-timetravel:<version>")

在應用的main函式中啟動TimeTravelServer

fun main() {
    TimeTravelServer().start()

    // 重置程式碼
}

使用時間旅行客戶端

時間旅行客戶端與服務端進行通訊,並提供UI用於控制功能和顯示資料。目前提供了三種客戶端變體。

  1. Intellij IDEA外掛 - 目前僅適用於安卓應用
  2. 獨立java應用 - 適用於安卓,JVM和Drawin/Apple應用
  3. 谷歌瀏覽器外掛 - 適用於JavaScript網頁應用

使用Intellij IDEA外掛

Intellij IDEA外掛可以在IDE中直接使用。目前只能連線安卓應用。

如何安裝

可以從Intellij IDEA Marketplace找到該外掛,可以在Intellij IDEA和Android Studio中直接安裝。在Settings -> Plugins -> Marketplace標籤的搜尋欄中查詢“MVIKotlin”。

如何使用

該外掛使用ADB轉發TCP埠6379。

首先確保TimeTravelServer在應用中執行,然後執行安卓應用並開啟IDE中的時間旅行外掛,可以單擊“連線”並開始記錄狀態更改。外掛首次執行時會詢問adb路徑。

在桌面上使用獨立客戶端應用程式

桌面客戶端應用程式提供了與IntelliJ IDEA外掛類似的功能。但它也可以連線到JVM和Darwin/Apple應用程式。

如何安裝

桌面時間旅行客戶端應用程式尚未釋出,因此您需要從原始碼構建並執行它。可以執行以下命令(至少需要JDK11):

./gradlew :mvikotlin-timetravel-client:app-desktop:run

如何使用

客戶端通過TCP連線服務端。

連線到已經運行了TimeTravelServer的安卓應用的簡單方法是開啟設定並選擇 “Connect via ADB”。然後點選 “Connect” 按鈕,客戶端將提示您使用 adb 可執行路徑,然後應建立連線。客戶端使用ADB轉發TCP埠。

連線到非安卓應用(或連線到安卓應用但不使用ADB),開啟設定並取消選擇 “Connect via ADB”,輸入裝置的主機地址。如果是本地執行的裝置,主機地址通常是localhost。對於遠端裝置,主機地址應該明確指定。請參閱裝置的設定以找到它的TCP地址。在任何情況下,裝置的埠應該是可連線的(例如擁有許可權、埠在白名單中)。

構建發行版

時間旅行客戶端是使用Compose for Desktop實現的,因此組裝一個發行版是可行的。請參閱 documentation page

使用谷歌瀏覽器外掛(實驗性)

谷歌瀏覽器擴充套件提供了與其他時間旅行客戶端類似的功能,但專門為Web應用程式設計。

谷歌瀏覽器擴充套件程式目前是實驗性的。最終它將被提升為穩定或刪除。

如何安裝

可以從Chrome Web Store安裝外掛。

如何使用

這個外掛增加了開發者工具面板,它的外觀和工作原理與其他時間旅行客戶類似。確保在Web應用程式中已經啟動TimeTravelServer。當web頁面被載入時,右鍵並選擇 “Inspect” 目錄項。導航到“MVIKotlin”工具面板並單擊“Connect”按鈕。該外掛會在web頁面中注入特殊指令碼,它在TimeTravelServer和外掛中代理訊息。

記錄事件

每當時間旅行客戶端連線到應用時,它可以開始記錄事件。按下“Start recording”按鈕開始記錄。所有記錄的事件將會出現在左側的列表中。按下“Stop recording”停止記錄。

檢查事件

當記錄結束後,應用會進入檢查狀態。在這個狀態下所有的Stores會從輸入和輸出斷開連線,所有的事件會累積並推遲到檢查結束後執行。

每一個記錄事件都能被檢查。在列表中選擇一個事件,細節會出現在右側。事件細節的精確表示依賴於時間旅行伺服器的實現,在不同的平臺上有所不同。

安卓和JVM目標的時間旅行伺服器使用反射以精確解析物件屬性。

Darwin/Apple目標的時間旅行伺服器只使用了toString函式。建議定義states,intents,actions和messages為資料類。
JavaScript目標的時間旅行伺服器使用JSON.stringify函式。類似於Darwin/Apple,也建議使用資料類。

時間旅行

在檢查狀態期間,Store的狀態可以回滾和重演,會用到下面這些按鈕:

  • “Move to start” - 移動到第一個記錄狀態
  • “Step backward” - 移動到前一個記錄狀態
  • “Step forward” - 移動到後一個記錄狀態
  • “Move to end” - 移動到最後一個記錄狀態

UI總是會展示當前選擇的狀態。

除錯記錄事件

在檢查狀態時,每個記錄事件都可以被再一次觸發。一個典型的使用情況是記錄app一個不正確的行為,然後在程式碼中設定斷點並觸發一個記錄事件。要觸發一個事件,在列表中選擇它,然後單擊“Debug the selected event”按鈕。

如果觸發的事件是一個Intent或一個Action,每個事件的相應StoreExecutor的新除錯例項會被建立。Executor相應的除錯例項用相同的State,與記錄時的State相同。在除錯會話期間排程的所有Messages都通過 Reducer傳遞,並且Executor的除錯例項的State會相應更新。任何在事件除錯會話期間被髮布的Label會被忽略。

如果觸發的事件是一個MessageReducer只會帶著Message和對應的State被呼叫,它的結果會被忽略。

匯出和匯入事件

這個功能目前只支援JVM和安卓應用。這個功能只有在所有相關類(Intents, Actions, Messages, States, 和Labels)必須實現Serializable介面。MVIKotlin模組提供了方便的 JvmSerializable介面,它可以在common源集中使用。

如果要匯出記錄事件,按下“Export events”按鈕。選擇一個資料夾並鍵入檔名。所有的事件會被序列化並儲存在檔案中。

如果要匯入之間匯出的事件,按下“Import events”按鈕並選擇一個檔案。所有的事件會從檔案被反序列化並應用於當前Stores

在匯入檔案時應用程式的程式碼應該和匯出時相同。反序列化的類要符合序列化時的類。否則它的行為是不確定的。

結束檢查

如果要結束檢查,按下“Cancel”按鈕,之後在檢查期間等待佇列中的IntentsLabels都會自動處理。