<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>小涟漪</title><link>https://www.xiaolianyi.cn/</link><description>Recent content on 小涟漪</description><generator>Hugo -- gohugo.io</generator><language>zh-CN</language><lastBuildDate>Wed, 04 Nov 2026 12:00:00 +0800</lastBuildDate><atom:link href="https://www.xiaolianyi.cn/index.xml" rel="self" type="application/rss+xml"/><item><title>KMP 入门：把同一段逻辑跑在 Android 和 iOS 上</title><link>https://www.xiaolianyi.cn/p/kmp-basics/</link><pubDate>Wed, 04 Nov 2026 12:00:00 +0800</pubDate><guid>https://www.xiaolianyi.cn/p/kmp-basics/</guid><description>&lt;img src="https://www.xiaolianyi.cn/p/kmp-basics/kmp-cover.svg" alt="Featured image of post KMP 入门：把同一段逻辑跑在 Android 和 iOS 上" /&gt;&lt;p&gt;假设要做一个读书 App：Android 和 iOS 的界面可以各自写，但「读了 6 页，一共 10 页，进度是 60%」这条规则可以共用。Kotlin Multiplatform（KMP）让我们把这样的代码写进同一个 Kotlin 模块，再编译给两个平台使用。&lt;/p&gt;&#10;&lt;h2 id="代码该放在哪儿"&gt;代码该放在哪儿？&#10;&lt;/h2&gt;&lt;p&gt;在一个同时面向 Android 和 iOS 的共享模块里，常见的目录是：&lt;/p&gt;&#10;&lt;table&gt;&#10;&#9;&lt;thead&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;目录&lt;/th&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;th&gt;放什么&lt;/th&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/thead&gt;&#10;&#9;&lt;tbody&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;commonMain&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;两端共用的数据模型、业务规则和计算&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;androidMain&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;需要调用 Android API 的代码&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;iosMain&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;多个 iOS 目标共用的平台代码&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&#9;&#9;&lt;tr&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;&lt;code&gt;commonTest&lt;/code&gt;&lt;/td&gt;&#10;&#9;&#9;&#9;&#9;&#9;&lt;td&gt;可以在各目标上运行的共享测试&lt;/td&gt;&#10;&#9;&#9;&#9;&lt;/tr&gt;&#10;&#9;&lt;/tbody&gt;&#10;&lt;/table&gt;&#10;&lt;p&gt;可以先找一段没有系统依赖、输入和输出都很明确的逻辑。阅读进度就很合适。&lt;/p&gt;&#10;&lt;h2 id="动手写一个共享函数"&gt;动手写一个共享函数&#10;&lt;/h2&gt;&lt;p&gt;把下面的函数放到 &lt;code&gt;shared/src/commonMain/kotlin/blog/kmp/ReadingProgress.kt&lt;/code&gt;：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;package&lt;/span&gt; blog.kmp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;readingProgress&lt;/span&gt;(readPages: Int, totalPages: Int): Int {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;if&lt;/span&gt; (totalPages &lt;span style="color:#f92672"&gt;&amp;lt;=&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;) &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;val&lt;/span&gt; pages = readPages.coerceIn(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, totalPages)&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;return&lt;/span&gt; (pages.toLong() * &lt;span style="color:#ae81ff"&gt;100&lt;/span&gt; / totalPages).toInt()&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;&lt;code&gt;readingProgress(6, 10)&lt;/code&gt; 返回 &lt;code&gt;60&lt;/code&gt;。如果误传了负数或超过总页数，结果也会保持在 0% 到 100% 之间。这里取整数百分比，先转成 &lt;code&gt;Long&lt;/code&gt; 可以避免乘以 100 时发生 &lt;code&gt;Int&lt;/code&gt; 溢出。&lt;/p&gt;&#10;&lt;p&gt;接着在 &lt;code&gt;commonTest&lt;/code&gt; 用 &lt;code&gt;kotlin.test&lt;/code&gt; 写一组例子：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;package&lt;/span&gt; blog.kmp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; kotlin.test.Test&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;import&lt;/span&gt; kotlin.test.assertEquals&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;class&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;ReadingProgressTest&lt;/span&gt; {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#a6e22e"&gt;@Test&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; &lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;handlesNormalAndOutOfRangeValues&lt;/span&gt;() {&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assertEquals(&lt;span style="color:#ae81ff"&gt;60&lt;/span&gt;, readingProgress(&lt;span style="color:#ae81ff"&gt;6&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assertEquals(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, readingProgress(-&lt;span style="color:#ae81ff"&gt;2&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assertEquals(&lt;span style="color:#ae81ff"&gt;100&lt;/span&gt;, readingProgress(&lt;span style="color:#ae81ff"&gt;12&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;10&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; assertEquals(&lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;, readingProgress(&lt;span style="color:#ae81ff"&gt;3&lt;/span&gt;, &lt;span style="color:#ae81ff"&gt;0&lt;/span&gt;))&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt; }&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;}&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;如果模板还没配置测试依赖，可以在共享模块的 &lt;code&gt;kotlin { sourceSets { ... } }&lt;/code&gt; 里加上 &lt;code&gt;commonTest.dependencies { implementation(kotlin(&amp;quot;test&amp;quot;)) }&lt;/code&gt;，然后在 IDE 里用项目已有的目标运行测试。&lt;/p&gt;&#10;&lt;h2 id="两端做法不同时怎么办"&gt;两端做法不同时怎么办？&#10;&lt;/h2&gt;&lt;p&gt;比如调试页想在共享代码里显示当前平台，可以用 &lt;code&gt;expect&lt;/code&gt; 声明需要的函数，再用 &lt;code&gt;actual&lt;/code&gt; 为每个平台提供实现。把下面三段分别放在对应目录，文件里的包名保持一致：&lt;/p&gt;&#10;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// commonMain/kotlin/blog/kmp/Platform.kt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;package&lt;/span&gt; blog.kmp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;expect&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;platformName&lt;/span&gt;(): String&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// androidMain/kotlin/blog/kmp/Platform.kt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;package&lt;/span&gt; blog.kmp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;actual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;platformName&lt;/span&gt;(): String = &lt;span style="color:#e6db74"&gt;&amp;#34;Android&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;div class="highlight"&gt;&lt;pre tabindex="0" style="color:#f8f8f2;background-color:#272822;-moz-tab-size:4;-o-tab-size:4;tab-size:4;-webkit-text-size-adjust:none;"&gt;&lt;code class="language-kotlin" data-lang="kotlin"&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#75715e"&gt;// iosMain/kotlin/blog/kmp/Platform.kt&#10;&lt;/span&gt;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;package&lt;/span&gt; blog.kmp&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;span style="display:flex;"&gt;&lt;span&gt;&lt;span style="color:#66d9ef"&gt;actual&lt;/span&gt; &lt;span style="color:#66d9ef"&gt;fun&lt;/span&gt; &lt;span style="color:#a6e22e"&gt;platformName&lt;/span&gt;(): String = &lt;span style="color:#e6db74"&gt;&amp;#34;iOS&amp;#34;&lt;/span&gt;&#10;&lt;/span&gt;&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;&lt;/div&gt;&lt;p&gt;下一步可以给读书 App 加一条「读完才标记完成」的规则，把它写在 &lt;code&gt;commonMain&lt;/code&gt;，再用两端相同的测试数据跑一遍。从小函数开始，比一上来搬整个应用更容易看清共享代码的边界。&lt;/p&gt;&#10;&lt;p&gt;延伸阅读：&lt;a class="link" href="https://kotlinlang.org/docs/multiplatform/multiplatform-discover-project.html" target="_blank" rel="noopener"&#10; &gt;KMP 项目结构&lt;/a&gt;、&lt;a class="link" href="https://kotlinlang.org/docs/multiplatform/multiplatform-add-dependencies.html#test-libraries" target="_blank" rel="noopener"&#10; &gt;共享测试依赖&lt;/a&gt;、&lt;a class="link" href="https://kotlinlang.org/docs/multiplatform/multiplatform-expect-actual.html" target="_blank" rel="noopener"&#10; &gt;&lt;code&gt;expect&lt;/code&gt; 与 &lt;code&gt;actual&lt;/code&gt;&lt;/a&gt;。&lt;/p&gt;&#10;</description></item><item><title>博客开张</title><link>https://www.xiaolianyi.cn/p/hello/</link><pubDate>Wed, 04 Nov 2026 00:00:00 +0800</pubDate><guid>https://www.xiaolianyi.cn/p/hello/</guid><description>&lt;img src="https://www.xiaolianyi.cn/p/hello/ripple-cover.svg" alt="Featured image of post 博客开张" /&gt;&lt;p&gt;欢迎来到小涟漪。这里会陆续更新随笔、技术笔记和日常记录。&lt;/p&gt;&#10;</description></item></channel></rss>