Skip to content

為何選擇 KSP

編譯器插件是強大的後設程式設計工具,能大幅增強您編寫程式碼的方式。 編譯器插件直接將編譯器作為函式庫呼叫,以分析和編輯輸入程式。這些插件也能為各種用途生成輸出。例如,它們可以生成樣板程式碼,甚至為特別標記的程式元素(如 Parcelable)生成完整實作。插件還有許多其他用途,甚至可以用來實作和微調語言中未直接提供的功能。

雖然編譯器插件很強大,但這種能力也是有代價的。要編寫即使是最簡單的插件,您需要具備一些編譯器背景知識,以及對特定編譯器的實作細節有一定程度的熟悉。另一個實際問題是,插件通常與特定編譯器版本緊密綁定,這表示您每次想要支援較新版本的編譯器時,可能都需要更新您的插件。

KSP 讓建立輕量級編譯器插件更容易

KSP 旨在隱藏編譯器變更,最大程度地減少使用它的處理器維護工作。KSP 的設計不綁定 JVM,以便未來能更容易地適應其他平台。KSP 也旨在最大程度地減少建構時間。對於某些處理器,例如 Glide,與 kapt 相比,KSP 可將完整編譯時間縮短多達 25%。

KSP 本身是作為編譯器插件實作的。在 Google 的 Maven 儲存庫中有預建套件,您可以直接下載和使用,而無需自行建構專案。

與 kotlinc 編譯器插件的比較

kotlinc 編譯器插件幾乎可以存取編譯器中的所有內容,因此具有最大的能力和靈活性。另一方面,由於這些插件可能依賴編譯器中的任何內容,因此它們對編譯器變更很敏感,需要頻繁維護。這些插件還需要深入理解 kotlinc 的實作,因此學習曲線可能很陡峭。

KSP 旨在透過定義良好的 API 隱藏大部分編譯器變更,儘管編譯器甚至 Kotlin 語言中的重大變更仍可能需要暴露給 API 使用者。

KSP 嘗試透過提供一個以能力換取簡潔的 API 來滿足常見的用例。它的能力是普通 kotlinc 插件的一個嚴格子集。例如,雖然 kotlinc 可以檢查表達式和陳述式,甚至可以修改程式碼,但 KSP 不能。

雖然編寫 kotlinc 插件可能很有趣,但它也可能花費很多時間。如果您沒有條件學習 kotlinc 的實作,並且不需要修改原始碼或讀取表達式,KSP 可能會是一個很好的選擇。

與反射的比較

KSP 的 API 看起來與 kotlin.reflect 類似。它們之間的主要差異是 KSP 中的類型引用需要明確解析。這也是介面未共享的原因之一。

與 kapt 的比較

kapt 是一個卓越的解決方案,它讓大量的 Java 註解處理器能夠開箱即用地適用於 Kotlin 程式。KSP 相較於 kapt 的主要優勢是改善的建構效能、不綁定 JVM、更符合慣用語的 Kotlin API,以及理解僅 Kotlin 符號的能力。

為了執行未修改的 Java 註解處理器,kapt 將 Kotlin 程式碼編譯成 Java 存根,這些存根保留了 Java 註解處理器所關心的資訊。為了建立這些存根,kapt 需要解析 Kotlin 程式中的所有符號。存根生成大約佔完整 kotlinc 分析的 1/3,並且與 kotlinc 程式碼生成屬於相同數量級。對於許多註解處理器來說,這比處理器本身花費的時間要長得多。例如,Glide 檢查數量非常有限的帶有預定義註解的類別,其程式碼生成相當快。幾乎所有的建構開銷都存在於存根生成階段。切換到 KSP 將立即減少編譯器中 25% 的時間。

為了效能評估,我們在 KSP 中實作了一個 Glide簡化版本,使其為 Tachiyomi 專案生成程式碼。儘管該專案在我們的測試設備上的總 Kotlin 編譯時間為 21.55 秒,但 kapt 花費了 8.67 秒來生成程式碼,而我們的 KSP 實作花費了 1.15 秒來生成程式碼。

與 kapt 不同,KSP 中的處理器不會從 Java 的視角查看輸入程式。該 API 對於 Kotlin 來說更自然,尤其是對於頂層函式等 Kotlin 特有功能。因為 KSP 不像 kapt 那樣委託給 javac,它不假設 JVM 特有的行為,並且可能與其他平台一起使用。

限制

儘管 KSP 試圖成為大多數常見用例的簡單解決方案,但與其他插件解決方案相比,它做出了一些權衡。以下不是 KSP 的目標:

  • 檢查原始碼的表達式級別資訊。
  • 修改原始碼。
  • 與 Java 註解處理 API 的 100% 相容性。