<?xml version="1.0" encoding="utf-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" version="2.0"><channel><title>Resmic's | BLOG</title><link>https://blog.resmic.cn/</link><description>个人博客</description><item><title>PHP 8.3 新特性实战指南：类型化常量、json_validate 与平滑升级</title><link>https://blog.resmic.cn/post/246.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260426080618177716197821939.jpg&quot; alt=&quot;封面&quot; style=&quot;width:100%;&quot;&gt;&lt;/p&gt;

&lt;h2&gt;一、为什么升级到 PHP 8.3？&lt;/h2&gt;
&lt;p&gt;PHP 8.3 于 2023 年 11 月正式发布，是 PHP 8.x 系列的最新稳定版本。相比 PHP 8.2，它在类型系统、性能优化和开发体验上都有显著提升。如果你还在使用 PHP 7.x 或 PHP 8.0/8.1，现在是迁移到 8.3 的绝佳时机。&lt;/p&gt;
&lt;p&gt;以下是升级 PHP 8.3 的主要理由：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;类型化类常量，让代码更严格、更可维护&lt;/li&gt;
  &lt;li&gt;内置 &lt;code&gt;json_validate()&lt;/code&gt;，无需再自己封装验证逻辑&lt;/li&gt;
  &lt;li&gt;新的 &lt;code&gt;#[Override]&lt;/code&gt; 属性，防止方法覆盖错误&lt;/li&gt;
  &lt;li&gt;动态类常量获取，支持更灵活的常量访问方式&lt;/li&gt;
  &lt;li&gt;性能比 PHP 7.4 提升约 40%，比 PHP 8.0 提升约 15%&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;二、类型化类常量（Typed Class Constants）&lt;/h2&gt;
&lt;p&gt;PHP 8.3 最重要的新特性之一是支持为类常量指定类型。在此之前，类常量没有类型约束，容易在子类中被错误覆盖。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
// PHP 8.3 之前：常量无类型限制
class Config {
    const VERSION = '1.0.0'; // 可以被子类改成任意类型
}

// PHP 8.3：类型化常量
class Config {
    const string VERSION = '1.0.0';
    const int MAX_RETRY = 3;
    const float TIMEOUT = 30.5;
    const array ALLOWED_METHODS = ['GET', 'POST', 'PUT', 'DELETE'];
}

// 子类试图用错误类型覆盖将报错
class DevConfig extends Config {
    const string VERSION = '1.0.0-dev'; // ✅ 正确
    // const int VERSION = 1; // ❌ 报错：类型不兼容
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;类型化常量支持所有标量类型（int、float、string、bool）、array、以及类/接口类型，还支持 nullable 语法（如 &lt;code&gt;?string&lt;/code&gt;）。&lt;/p&gt;

&lt;h2&gt;三、json_validate() 函数&lt;/h2&gt;
&lt;p&gt;在 PHP 8.3 之前，验证一个字符串是否为合法 JSON 通常需要这样做：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
// 旧方式：解码后检查错误（性能差，会分配内存）
function isValidJson(string $json): bool {
    json_decode($json);
    return json_last_error() === JSON_ERROR_NONE;
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;PHP 8.3 引入了原生的 &lt;code&gt;json_validate()&lt;/code&gt;，它只做语法校验，不分配解码内存，性能更好：&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
// PHP 8.3 新方式：高效验证
$data = '{&quot;name&quot;: &quot;Alice&quot;, &quot;age&quot;: 30}';

if (json_validate($data)) {
    echo &quot;合法的 JSON&quot;;
    $obj = json_decode($data); // 只在需要时再解码
}

// 验证嵌套深度
if (json_validate($data, depth: 5)) {
    // 限制最大嵌套层数为 5
}

// 使用标志位
if (json_validate($data, flags: JSON_INVALID_UTF8_IGNORE)) {
    // 忽略无效 UTF-8 字符
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;在需要大量验证 JSON 字符串合法性的场景（如 API 网关、消息队列消费者），这个函数可以显著降低内存开销。&lt;/p&gt;

&lt;h2&gt;四、#[Override] 属性&lt;/h2&gt;
&lt;p&gt;&lt;code&gt;#[Override]&lt;/code&gt; 是 PHP 8.3 引入的新属性，用于标记一个方法是对父类方法的覆盖。如果父类中不存在同名方法，PHP 将抛出编译错误，有效防止因拼写错误导致的&quot;静默失效&quot;问题。&lt;/p&gt;
&lt;pre&gt;&lt;code class=&quot;language-php&quot;&gt;&amp;lt;?php
class Logger {
    public function log(string $message): void {
        echo $message . PHP_EOL;
    }
}

class FileLogger extends Logger {
    #[Override]
    public function log(string $message): void {
        // 确保这里真的覆盖了父类方法
        file_put_contents('/var/log/app.log', $message . PHP_EOL, FILE_APPEND);
    }
    
    // #[Override]
    // public function logg(string $message): void { // ❌ 报错：父类无此方法
    //     ...
    // }
}
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;这在大型项目和团队协作中特别有价值，可以在 code review 阶段之前就发现覆盖错误。&lt;/p&gt;

&lt;h2&gt;五、实战：平滑升级现有 PHP 项目&lt;/h2&gt;
&lt;p&gt;升级到 PHP 8.3 前，建议按以下步骤操作：&lt;/p&gt;
&lt;ul&gt;
  &lt;li&gt;&lt;strong&gt;第一步：&lt;/strong&gt;在本地或测试环境安装 PHP 8.3，使用 &lt;code&gt;php -v&lt;/code&gt; 确认版本&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;第二步：&lt;/strong&gt;运行 &lt;code&gt;composer update&lt;/code&gt;，检查依赖包兼容性，关注废弃（deprecated）警告&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;第三步：&lt;/strong&gt;使用 &lt;a href=&quot;https://github.com/rectorphp/rector&quot;&gt;Rector&lt;/a&gt; 自动化重构工具，自动升级代码语法&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;第四步：&lt;/strong&gt;运行完整测试套件，修复失败的测试用例&lt;/li&gt;
  &lt;li&gt;&lt;strong&gt;第五步：&lt;/strong&gt;分阶段灰度上线，监控错误日志&lt;/li&gt;
&lt;/ul&gt;
&lt;pre&gt;&lt;code class=&quot;language-bash&quot;&gt;# 使用 Rector 自动升级代码
composer require rector/rector --dev

# 创建 rector.php 配置
cat &amp;gt; rector.php &amp;lt;&amp;lt; 'RECTOR_EOF'
&amp;lt;?php
use Rector\Config\RectorConfig;
use Rector\Set\ValueObject\LevelSetList;

return RectorConfig::configure()
    -&amp;gt;withPaths([__DIR__ . '/src', __DIR__ . '/tests'])
    -&amp;gt;withSets([LevelSetList::UP_TO_PHP_83]);
RECTOR_EOF

# 预览变更（不实际修改）
./vendor/bin/rector process --dry-run

# 应用变更
./vendor/bin/rector process
&lt;/code&gt;&lt;/pre&gt;
&lt;p&gt;完成升级后，你的代码将充分利用 PHP 8.3 的所有新特性，同时享受更好的运行时性能。&lt;/p&gt;
</description><pubDate>Sun, 26 Apr 2026 08:10:13 +0800</pubDate></item><item><title>PHP 8.3 新特性全解析：类型系统增强与性能优化实战指南</title><link>https://blog.resmic.cn/post/245.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260425080704177707562426951.jpg&quot; alt=&quot;封面&quot; style=&quot;width:100%;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;一、PHP 8.3 概览：为什么值得升级？&lt;/h2&gt;&lt;p&gt;PHP 8.3 于 2023 年 11 月正式发布，是 PHP 8.x 系列的最新稳定版本。相比 8.2，它在类型安全、只读属性、随机数 API 以及错误处理等方面都有显著改进。&lt;/p&gt;&lt;p&gt;对于追求代码质量与可维护性的现代 PHP 开发者来说，升级到 8.3 能带来以下核心收益：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;更严格的类型系统，减少运行时错误&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;只读属性深度克隆支持，适合复杂值对象场景&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;新的 &lt;code&gt;json_validate()&lt;/code&gt; 函数，提升 JSON 处理效率&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;随机数扩展 (&lt;code&gt;Random\Randomizer&lt;/code&gt;) 功能增强&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;动态类常量获取更简洁&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;性能小幅提升，JIT 编译更稳定&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;本文将结合真实代码示例，逐一深入解析这些变化。&lt;/p&gt;&lt;h2&gt;二、只读属性改进：深度克隆支持&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了只读属性 (&lt;code&gt;readonly&lt;/code&gt;)，8.2 支持只读类，而 8.3 的重要改进是允许在克隆时重新初始化只读属性，彻底解决了不可变值对象难以拷贝修改的痛点。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

class&amp;nbsp;UserProfile
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;int&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$email,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;withEmail(string&amp;nbsp;$newEmail):&amp;nbsp;static
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$clone&amp;nbsp;=&amp;nbsp;clone&amp;nbsp;$this;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;PHP&amp;nbsp;8.3:&amp;nbsp;克隆后可重新赋值&amp;nbsp;readonly&amp;nbsp;属性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$clone-&amp;gt;email&amp;nbsp;=&amp;nbsp;$newEmail;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$clone;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

$user&amp;nbsp;=&amp;nbsp;new&amp;nbsp;UserProfile(1,&amp;nbsp;&amp;#39;Alice&amp;#39;,&amp;nbsp;&amp;#39;alice@example.com&amp;#39;);
$updated&amp;nbsp;=&amp;nbsp;$user-&amp;gt;withEmail(&amp;#39;alice@new.com&amp;#39;);

echo&amp;nbsp;$user-&amp;gt;email;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;alice@example.com（原对象不变）
echo&amp;nbsp;$updated-&amp;gt;email;&amp;nbsp;//&amp;nbsp;alice@new.com&lt;/pre&gt;&lt;p&gt;这一特性让&amp;quot;不可变对象 + 修改返回新实例&amp;quot;的 DDD 值对象模式在 PHP 中变得真正实用。&lt;/p&gt;&lt;h2&gt;三、类型系统增强：动态类常量与枚举改进&lt;/h2&gt;&lt;p&gt;PHP 8.3 允许使用动态表达式访问类常量和枚举成员，语法更灵活，减少了过去必须用 &lt;code&gt;constant()&lt;/code&gt; 函数的尴尬写法。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

class&amp;nbsp;Status
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;ACTIVE&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;active&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;BLOCKED&amp;nbsp;=&amp;nbsp;&amp;#39;blocked&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;PENDING&amp;nbsp;=&amp;nbsp;&amp;#39;pending&amp;#39;;
}

//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;新写法：动态常量访问
$key&amp;nbsp;=&amp;nbsp;&amp;#39;ACTIVE&amp;#39;;
echo&amp;nbsp;Status::{$key};&amp;nbsp;//&amp;nbsp;active

//&amp;nbsp;枚举同样支持
enum&amp;nbsp;Color:&amp;nbsp;string
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Red&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;red&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Green&amp;nbsp;=&amp;nbsp;&amp;#39;green&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;case&amp;nbsp;Blue&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;blue&amp;#39;;
}

$colorName&amp;nbsp;=&amp;nbsp;&amp;#39;Red&amp;#39;;
$color&amp;nbsp;=&amp;nbsp;Color::{$colorName};
echo&amp;nbsp;$color-&amp;gt;value;&amp;nbsp;//&amp;nbsp;red&lt;/pre&gt;&lt;p&gt;此外，PHP 8.3 为枚举增加了 &lt;code&gt;getConstant()&lt;/code&gt; 方法，与类常量保持接口一致，方便反射场景使用。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

$reflection&amp;nbsp;=&amp;nbsp;new&amp;nbsp;ReflectionEnum(Color::class);
$case&amp;nbsp;=&amp;nbsp;$reflection-&amp;gt;getCase(&amp;#39;Green&amp;#39;);
echo&amp;nbsp;$case-&amp;gt;getValue()-&amp;gt;value;&amp;nbsp;//&amp;nbsp;green&lt;/pre&gt;&lt;h2&gt;四、新增 json_validate() 函数&lt;/h2&gt;&lt;p&gt;在 PHP 8.3 之前，验证一个字符串是否为合法 JSON 只能先 &lt;code&gt;json_decode()&lt;/code&gt; 再检查 &lt;code&gt;json_last_error()&lt;/code&gt;，这不仅繁琐，而且会创建不必要的内存对象。PHP 8.3 引入了专用的 &lt;code&gt;json_validate()&lt;/code&gt;，仅做语法验证，不解析内容。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;旧写法（PHP&amp;nbsp;8.2&amp;nbsp;及以前）
function&amp;nbsp;isValidJsonOld(string&amp;nbsp;$json):&amp;nbsp;bool
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_decode($json);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_last_error()&amp;nbsp;===&amp;nbsp;JSON_ERROR_NONE;
}

//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;新写法
$validJson&amp;nbsp;&amp;nbsp;&amp;nbsp;=&amp;nbsp;&amp;#39;{&amp;quot;name&amp;quot;:&amp;quot;Alice&amp;quot;,&amp;quot;age&amp;quot;:30}&amp;#39;;
$invalidJson&amp;nbsp;=&amp;nbsp;&amp;#39;{name:&amp;nbsp;Alice}&amp;#39;;

var_dump(json_validate($validJson));&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;bool(true)
var_dump(json_validate($invalidJson));&amp;nbsp;//&amp;nbsp;bool(false)

//&amp;nbsp;支持指定最大嵌套深度
$deepJson&amp;nbsp;=&amp;nbsp;str_repeat(&amp;#39;{&amp;quot;a&amp;quot;:&amp;#39;,&amp;nbsp;100)&amp;nbsp;.&amp;nbsp;&amp;#39;1&amp;#39;&amp;nbsp;.&amp;nbsp;str_repeat(&amp;#39;}&amp;#39;,&amp;nbsp;100);
var_dump(json_validate($deepJson,&amp;nbsp;depth:&amp;nbsp;50));&amp;nbsp;//&amp;nbsp;bool(false)&lt;/pre&gt;&lt;p&gt;在高并发接口场景中，使用 &lt;code&gt;json_validate()&lt;/code&gt; 对大量请求做前置过滤，相比 &lt;code&gt;json_decode()&lt;/code&gt; 内存占用显著降低。&lt;/p&gt;&lt;h2&gt;五、随机数 API 增强：Randomizer 新方法&lt;/h2&gt;&lt;p&gt;PHP 8.2 引入了 &lt;code&gt;Random\Randomizer&lt;/code&gt; 类，8.3 在此基础上新增了 &lt;code&gt;getBytesFromString()&lt;/code&gt; 和 &lt;code&gt;getFloat()&lt;/code&gt;/&lt;code&gt;nextFloat()&lt;/code&gt; 方法，让安全随机数生成更加灵活。&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

use&amp;nbsp;Random\Randomizer;
use&amp;nbsp;Random\Engine\Secure;

$randomizer&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Randomizer(new&amp;nbsp;Secure());

//&amp;nbsp;从指定字符集中随机抽取&amp;nbsp;N&amp;nbsp;个字符（PHP&amp;nbsp;8.3&amp;nbsp;新增）
$charset&amp;nbsp;=&amp;nbsp;&amp;#39;ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789&amp;#39;;
$token&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;getBytesFromString($charset,&amp;nbsp;32);
echo&amp;nbsp;$token;&amp;nbsp;//&amp;nbsp;例如：aZ3kQ7mBx...（32位安全随机字符串）

//&amp;nbsp;生成指定范围内的浮点数（PHP&amp;nbsp;8.3&amp;nbsp;新增）
$price&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;getFloat(1.0,&amp;nbsp;100.0);
echo&amp;nbsp;round($price,&amp;nbsp;2);&amp;nbsp;//&amp;nbsp;例如：47.83

//&amp;nbsp;nextFloat()：生成&amp;nbsp;[0,&amp;nbsp;1)&amp;nbsp;区间的浮点数
$ratio&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;nextFloat();
echo&amp;nbsp;$ratio;&amp;nbsp;//&amp;nbsp;例如：0.7341...&lt;/pre&gt;&lt;p&gt;这些方法底层使用密码学安全的随机源，适合生成邀请码、临时密码、A/B 测试分流比例等场景，比 &lt;code&gt;rand()&lt;/code&gt;/&lt;code&gt;mt_rand()&lt;/code&gt; 更安全可靠。&lt;/p&gt;&lt;h2&gt;六、类型声明改进：never 返回类型与交集类型&lt;/h2&gt;&lt;p&gt;PHP 8.3 完善了 &lt;code&gt;never&lt;/code&gt; 返回类型的使用范围，并对交集类型 (&lt;code&gt;A&amp;amp;B&lt;/code&gt;) 的兼容性做了增强。以下是几个实用示例：&lt;/p&gt;&lt;pre&gt;&amp;lt;?php

//&amp;nbsp;never&amp;nbsp;返回类型：表示该函数永远不会正常返回
function&amp;nbsp;throwNotFound(string&amp;nbsp;$message):&amp;nbsp;never
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;RuntimeException($message);
}

//&amp;nbsp;交集类型：要求参数同时实现多个接口
interface&amp;nbsp;Countable2
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;count():&amp;nbsp;int;
}

interface&amp;nbsp;Stringable2
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__toString():&amp;nbsp;string;
}

function&amp;nbsp;process(Countable2&amp;amp;Stringable2&amp;nbsp;$value):&amp;nbsp;string
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;&amp;quot;Count:&amp;nbsp;{$value-&amp;gt;count()},&amp;nbsp;String:&amp;nbsp;{$value}&amp;quot;;
}

//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;新增：#[\Override]&amp;nbsp;注解
class&amp;nbsp;Base
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;calculate():&amp;nbsp;int
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;42;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

class&amp;nbsp;Child&amp;nbsp;extends&amp;nbsp;Base
{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#[\Override]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;calculate():&amp;nbsp;int&amp;nbsp;//&amp;nbsp;若父类不存在此方法，编译期报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;parent::calculate()&amp;nbsp;*&amp;nbsp;2;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

$c&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Child();
echo&amp;nbsp;$c-&amp;gt;calculate();&amp;nbsp;//&amp;nbsp;84&lt;/pre&gt;&lt;p&gt;&lt;code&gt;#[\Override]&lt;/code&gt; 注解是 PHP 8.3 引入的实用安全特性，能在编译期捕获&amp;quot;子类方法名拼写错误导致未真正覆盖父类方法&amp;quot;的低级 bug。&lt;/p&gt;&lt;h2&gt;七、实战迁移建议与注意事项&lt;/h2&gt;&lt;p&gt;升级到 PHP 8.3 前，需要关注以下不兼容变化：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;废弃的动态属性：&lt;/strong&gt; 继承自 stdClass 以外的类上动态创建未声明属性，8.3 中会产生 E_DEPRECATED 警告，9.0 将报错&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;正则表达式错误处理：&lt;/strong&gt; &lt;code&gt;preg_*&lt;/code&gt; 函数在某些错误情况下的返回值语义有细微变化，建议增加错误码检查&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;FFI 变更：&lt;/strong&gt; 如果项目使用 FFI 扩展，需注意接口变动&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Composer 依赖：&lt;/strong&gt; 升级前运行 &lt;code&gt;composer outdated&lt;/code&gt; 确认所有依赖都支持 PHP 8.3&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;#&amp;nbsp;检查项目是否兼容&amp;nbsp;PHP&amp;nbsp;8.3
composer&amp;nbsp;require&amp;nbsp;--dev&amp;nbsp;phpstan/phpstan
./vendor/bin/phpstan&amp;nbsp;analyse&amp;nbsp;--level=8&amp;nbsp;src/

#&amp;nbsp;使用&amp;nbsp;PHP&amp;nbsp;Compatibility&amp;nbsp;检查弃用&amp;nbsp;API
composer&amp;nbsp;require&amp;nbsp;--dev&amp;nbsp;phpcompatibility/php-compatibility
./vendor/bin/phpcs&amp;nbsp;--standard=PHPCompatibility&amp;nbsp;--runtime-set&amp;nbsp;testVersion&amp;nbsp;8.3&amp;nbsp;src/&lt;/pre&gt;&lt;p&gt;总体而言，PHP 8.3 是一个值得尽快跟进的版本。改进点集中且实用，迁移成本可控，在 Laravel 10/11、Symfony 6/7 等主流框架上均已获得完整支持。&lt;/p&gt;</description><pubDate>Sat, 25 Apr 2026 08:07:29 +0800</pubDate></item><item><title>PHP 8.3 新特性全解析：类型化常量、json_validate与随机性API实战</title><link>https://blog.resmic.cn/post/244.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260424080618177698917869937.jpg&quot; alt=&quot;封面&quot; style=&quot;width:100%;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;PHP 8.3 概览：为何值得升级&lt;/h2&gt;&lt;p&gt;PHP 8.3 于 2023 年 11 月正式发布，作为 PHP 8.x 系列的最新版本，它在性能、类型安全和开发体验上都有显著改进。对于仍在使用 PHP 7.x 或 PHP 8.0/8.1 的开发者而言，升级到 8.3 不仅可以享受更强的语言特性，还能获得更好的性能表现。&lt;/p&gt;&lt;p&gt;本文将逐一介绍 PHP 8.3 的核心新特性，并通过实际代码示例帮助你快速理解和应用这些特性。&lt;/p&gt;&lt;h2&gt;类型化类常量（Typed Class Constants）&lt;/h2&gt;&lt;p&gt;PHP 8.3 最受关注的特性之一是支持对类常量声明类型，这极大地提升了代码的可读性和类型安全性。&lt;/p&gt;&lt;p&gt;在 PHP 8.2 及之前，类常量没有类型声明，容易被子类错误覆盖：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;PHP&amp;nbsp;8.2&amp;nbsp;之前：没有类型约束
class&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;1.0.0&amp;#39;;&amp;nbsp;&amp;nbsp;//&amp;nbsp;可以被子类覆盖为任意类型
}

class&amp;nbsp;BadConfig&amp;nbsp;extends&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;123;&amp;nbsp;&amp;nbsp;//&amp;nbsp;错误但不报错
}&lt;/pre&gt;&lt;p&gt;PHP 8.3 引入类型化类常量后：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;PHP&amp;nbsp;8.3：强类型常量
class&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;1.0.0&amp;#39;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;int&amp;nbsp;MAX_RETRY&amp;nbsp;=&amp;nbsp;3;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;array&amp;nbsp;SUPPORTED_FORMATS&amp;nbsp;=&amp;nbsp;[&amp;#39;json&amp;#39;,&amp;nbsp;&amp;#39;xml&amp;#39;,&amp;nbsp;&amp;#39;csv&amp;#39;];
}

class&amp;nbsp;StrictConfig&amp;nbsp;extends&amp;nbsp;Config&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;string&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;&amp;#39;2.0.0&amp;#39;;&amp;nbsp;&amp;nbsp;//&amp;nbsp;正确
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;const&amp;nbsp;int&amp;nbsp;VERSION&amp;nbsp;=&amp;nbsp;2;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;Fatal&amp;nbsp;Error！类型不匹配
}&lt;/pre&gt;&lt;p&gt;支持的类型包括：&lt;code&gt;bool&lt;/code&gt;、&lt;code&gt;int&lt;/code&gt;、&lt;code&gt;float&lt;/code&gt;、&lt;code&gt;string&lt;/code&gt;、&lt;code&gt;array&lt;/code&gt;、&lt;code&gt;null&lt;/code&gt;、&lt;code&gt;mixed&lt;/code&gt; 以及枚举类型等，极大地减少了因常量类型误用导致的 Bug。&lt;/p&gt;&lt;h2&gt;json_validate() — 轻量级 JSON 验证&lt;/h2&gt;&lt;p&gt;在 PHP 8.3 之前，验证一个字符串是否为合法 JSON，通常需要先 &lt;code&gt;json_decode()&lt;/code&gt; 再检查 &lt;code&gt;json_last_error()&lt;/code&gt;，这会消耗额外的内存来解析整个 JSON 树。&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;PHP&amp;nbsp;8.2&amp;nbsp;之前的做法（低效）
function&amp;nbsp;isValidJson(string&amp;nbsp;$json):&amp;nbsp;bool&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;json_decode($json);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;json_last_error()&amp;nbsp;===&amp;nbsp;JSON_ERROR_NONE;
}

$bigJson&amp;nbsp;=&amp;nbsp;file_get_contents(&amp;#39;large_data.json&amp;#39;);&amp;nbsp;//&amp;nbsp;可能几十MB
isValidJson($bigJson);&amp;nbsp;//&amp;nbsp;会把整个JSON解析到内存中&lt;/pre&gt;&lt;p&gt;PHP 8.3 新增的 &lt;code&gt;json_validate()&lt;/code&gt; 只做语法检查，不构建数据结构，内存占用极低：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;PHP&amp;nbsp;8.3：高效验证
$json&amp;nbsp;=&amp;nbsp;&amp;#39;{&amp;quot;name&amp;quot;:&amp;nbsp;&amp;quot;Alice&amp;quot;,&amp;nbsp;&amp;quot;age&amp;quot;:&amp;nbsp;30}&amp;#39;;

if&amp;nbsp;(json_validate($json))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;echo&amp;nbsp;&amp;quot;有效的&amp;nbsp;JSON！&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$data&amp;nbsp;=&amp;nbsp;json_decode($json,&amp;nbsp;true);&amp;nbsp;//&amp;nbsp;只在需要时才解析
}

//&amp;nbsp;支持嵌套深度限制
json_validate($json,&amp;nbsp;depth:&amp;nbsp;5);

//&amp;nbsp;实际性能对比（大文件场景）
$largeJson&amp;nbsp;=&amp;nbsp;str_repeat(&amp;#39;{&amp;quot;key&amp;quot;:&amp;quot;value&amp;quot;},&amp;#39;,&amp;nbsp;100000);
$largeJson&amp;nbsp;=&amp;nbsp;&amp;#39;[&amp;#39;&amp;nbsp;.&amp;nbsp;rtrim($largeJson,&amp;nbsp;&amp;#39;,&amp;#39;)&amp;nbsp;.&amp;nbsp;&amp;#39;]&amp;#39;;

//&amp;nbsp;json_validate()&amp;nbsp;比&amp;nbsp;json_decode()&amp;nbsp;快约&amp;nbsp;30-50%，内存节省&amp;nbsp;80%&lt;/pre&gt;&lt;p&gt;在处理 Webhook 回调、API 数据校验等高频场景下，这一优化能带来明显的性能提升。&lt;/p&gt;&lt;h2&gt;新的随机性 API（Randomizer 增强）&lt;/h2&gt;&lt;p&gt;PHP 8.2 引入了 &lt;code&gt;Random\Randomizer&lt;/code&gt; 类，8.3 对其进行了重要补充，新增了 &lt;code&gt;getBytesFromString()&lt;/code&gt; 和 &lt;code&gt;getFloat()&lt;/code&gt;、&lt;code&gt;nextFloat()&lt;/code&gt; 方法。&lt;/p&gt;&lt;pre&gt;use&amp;nbsp;Random\Randomizer;
use&amp;nbsp;Random\Engine\Secure;

$randomizer&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Randomizer(new&amp;nbsp;Secure());

//&amp;nbsp;getBytesFromString：从指定字符集随机取字节
//&amp;nbsp;非常适合生成验证码、随机密码
$charset&amp;nbsp;=&amp;nbsp;&amp;#39;0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz&amp;#39;;
$token&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;getBytesFromString($charset,&amp;nbsp;32);
echo&amp;nbsp;$token;&amp;nbsp;//&amp;nbsp;例如：&amp;quot;aZ3Km9xQpR7vBnLw2eTfUdYcHsOjGiVl&amp;quot;

//&amp;nbsp;getFloat：生成指定范围内的随机浮点数
$price&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;getFloat(10.0,&amp;nbsp;99.99);
echo&amp;nbsp;round($price,&amp;nbsp;2);&amp;nbsp;//&amp;nbsp;例如：47.83

//&amp;nbsp;nextFloat：生成&amp;nbsp;[0,&amp;nbsp;1)&amp;nbsp;范围的随机浮点数
$probability&amp;nbsp;=&amp;nbsp;$randomizer-&amp;gt;nextFloat();
echo&amp;nbsp;$probability;&amp;nbsp;//&amp;nbsp;例如：0.7342891

//&amp;nbsp;对比旧方式：mt_rand()&amp;nbsp;存在分布不均匀问题
//&amp;nbsp;新&amp;nbsp;API&amp;nbsp;基于&amp;nbsp;CSPRNG，密码学安全且分布均匀&lt;/pre&gt;&lt;h2&gt;只读属性（Readonly）的改进&lt;/h2&gt;&lt;p&gt;PHP 8.1 引入了 &lt;code&gt;readonly&lt;/code&gt; 属性，8.2 支持 &lt;code&gt;readonly class&lt;/code&gt;，而 8.3 进一步允许在克隆时修改只读属性（通过 &lt;code&gt;__clone&lt;/code&gt; 魔术方法），解决了不可变对象难以克隆修改的痛点。&lt;/p&gt;&lt;pre&gt;class&amp;nbsp;User&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;__construct(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;int&amp;nbsp;$id,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;readonly&amp;nbsp;string&amp;nbsp;$email,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;PHP&amp;nbsp;8.3：克隆时可在&amp;nbsp;__clone&amp;nbsp;中修改&amp;nbsp;readonly&amp;nbsp;属性
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;public&amp;nbsp;function&amp;nbsp;withEmail(string&amp;nbsp;$email):&amp;nbsp;static&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$clone&amp;nbsp;=&amp;nbsp;clone&amp;nbsp;$this;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$clone-&amp;gt;email&amp;nbsp;=&amp;nbsp;$email;&amp;nbsp;//&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;允许！之前会报错
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;$clone;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

$user&amp;nbsp;=&amp;nbsp;new&amp;nbsp;User(1,&amp;nbsp;&amp;#39;Alice&amp;#39;,&amp;nbsp;&amp;#39;alice@example.com&amp;#39;);
$updated&amp;nbsp;=&amp;nbsp;$user-&amp;gt;withEmail(&amp;#39;newalice@example.com&amp;#39;);

echo&amp;nbsp;$user-&amp;gt;email;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;alice@example.com（原对象不变）
echo&amp;nbsp;$updated-&amp;gt;email;&amp;nbsp;//&amp;nbsp;newalice@example.com

//&amp;nbsp;这种模式在&amp;nbsp;DDD（领域驱动设计）中非常有用
//&amp;nbsp;可以安全地创建不可变值对象的&amp;quot;修改副本&amp;quot;&lt;/pre&gt;&lt;p&gt;这一改进让 PHP 在构建不可变数据结构时更加优雅，与现代架构设计模式（如 CQRS、Event Sourcing）的契合度更高。&lt;/p&gt;&lt;h2&gt;升级建议与兼容性注意事项&lt;/h2&gt;&lt;p&gt;如果你准备将项目升级到 PHP 8.3，以下几点需要特别注意：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;移除的特性&lt;/strong&gt;：部分 PHP 8.0 中已弃用的函数在 8.3 中正式移除，升级前请运行 &lt;code&gt;composer require --dev phpstan/phpstan&lt;/code&gt; 进行静态分析。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;扩展兼容性&lt;/strong&gt;：检查项目依赖的 PHP 扩展是否已支持 8.3，尤其是 &lt;code&gt;xdebug&lt;/code&gt;、&lt;code&gt;imagick&lt;/code&gt; 等。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;框架支持&lt;/strong&gt;：Laravel 10+ 和 Symfony 6.3+ 均已官方支持 PHP 8.3，可放心升级。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;测试覆盖&lt;/strong&gt;：升级前确保有充分的单元测试覆盖，推荐使用 PHPUnit 10.x。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;分阶段迁移&lt;/strong&gt;：生产环境建议先在预发布环境验证 1-2 周，确认无异常再全量切换。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;#&amp;nbsp;快速检查代码兼容性
composer&amp;nbsp;require&amp;nbsp;--dev&amp;nbsp;phpstan/phpstan&amp;nbsp;rector/rector

#&amp;nbsp;用&amp;nbsp;Rector&amp;nbsp;自动迁移代码到&amp;nbsp;PHP&amp;nbsp;8.3&amp;nbsp;风格
vendor/bin/rector&amp;nbsp;process&amp;nbsp;src&amp;nbsp;--config&amp;nbsp;rector.php

#&amp;nbsp;rector.php&amp;nbsp;配置示例
use&amp;nbsp;Rector\Set\ValueObject\LevelSetList;
return&amp;nbsp;static&amp;nbsp;function&amp;nbsp;(\Rector\Config\RectorConfig&amp;nbsp;$config):&amp;nbsp;void&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;$config-&amp;gt;sets([LevelSetList::UP_TO_PHP_83]);
};&lt;/pre&gt;&lt;p&gt;PHP 8.3 是一个值得升级的版本，类型化常量、&lt;code&gt;json_validate()&lt;/code&gt; 和增强的随机性 API 都是日常开发中的实用利器。建议在新项目中直接使用 PHP 8.3，老项目也可以制定升级计划，逐步享受这些改进带来的红利。&lt;/p&gt;</description><pubDate>Fri, 24 Apr 2026 08:09:50 +0800</pubDate></item><item><title>Jetpack Compose 性能优化实战：7大策略打造流畅Android应用</title><link>https://blog.resmic.cn/post/243.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260423080521177690272178708.jpg&quot; alt=&quot;封面&quot; style=&quot;width:100%;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;前言：为什么 Compose 性能优化如此重要&lt;/h2&gt;&lt;p&gt;Jetpack Compose 自 1.0 稳定版发布以来，已迅速成为 Android UI 开发的首选框架。与传统 View 系统相比，Compose 提供了声明式 UI、更少的样板代码和更强的可组合性。然而，声明式 UI 的本质决定了它依赖&lt;strong&gt;重组（Recomposition）&lt;/strong&gt;机制来刷新界面，若使用不当，频繁的重组会导致严重的性能问题，出现卡顿、丢帧等用户体验下降的情况。&lt;/p&gt;&lt;p&gt;根据 Google 官方的 Android Vitals 数据，超过 60% 的用户会在遭遇严重卡顿后卸载应用。因此，掌握 Compose 性能优化技术是每个 Android 开发者的必修课。本文将从以下几个核心维度展开讲解，并提供可直接落地的代码示例。&lt;/p&gt;&lt;h2&gt;一、深入理解重组机制：避免不必要的重组&lt;/h2&gt;&lt;p&gt;重组是 Compose 更新 UI 的方式——当状态发生变化时，Compose 会重新执行受影响的可组合函数。理解重组的触发条件是优化的第一步。&lt;/p&gt;&lt;h3&gt;1.1 稳定性（Stability）与智能重组&lt;/h3&gt;&lt;p&gt;Compose 编译器会分析每个可组合函数的参数类型是否&amp;quot;稳定&amp;quot;。如果参数是稳定的，Compose 在重组时会跳过未发生变化的组合函数，这称为&lt;strong&gt;跳过重组（Skippable Recomposition）&lt;/strong&gt;。&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;稳定类型&lt;/strong&gt;：基本类型（Int、String、Boolean 等）、被 &lt;code&gt;@Stable&lt;/code&gt; 或 &lt;code&gt;@Immutable&lt;/code&gt; 标注的类、Kotlin 不可变数据类&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;不稳定类型&lt;/strong&gt;：包含 var 属性的类、List/Map/Set（需改用 ImmutableList）、Java 类（Compose 无法分析其可变性）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;不稳定&amp;nbsp;-&amp;nbsp;每次父组合重组都会触发此函数重组
data&amp;nbsp;class&amp;nbsp;User(var&amp;nbsp;name:&amp;nbsp;String,&amp;nbsp;var&amp;nbsp;age:&amp;nbsp;Int)

@Composable
fun&amp;nbsp;UserCard(user:&amp;nbsp;User)&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}

//&amp;nbsp;✅&amp;nbsp;稳定&amp;nbsp;-&amp;nbsp;仅在&amp;nbsp;user&amp;nbsp;实际变化时重组
@Immutable
data&amp;nbsp;class&amp;nbsp;User(val&amp;nbsp;name:&amp;nbsp;String,&amp;nbsp;val&amp;nbsp;age:&amp;nbsp;Int)

@Composable
fun&amp;nbsp;UserCard(user:&amp;nbsp;User)&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}&lt;/pre&gt;&lt;h3&gt;1.2 使用 Compose 编译器报告分析重组&lt;/h3&gt;&lt;p&gt;在 &lt;code&gt;build.gradle.kts&lt;/code&gt; 中添加以下配置，生成可组合函数的稳定性报告：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;app/build.gradle.kts
composeOptions&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kotlinCompilerExtensionVersion&amp;nbsp;=&amp;nbsp;&amp;quot;1.5.x&amp;quot;
}
tasks.withType&amp;lt;KotlinCompile&amp;gt;().configureEach&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compilerOptions&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;freeCompilerArgs.addAll(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;-P&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;plugin:androidx.compose.compiler.plugins.kotlin:reportsDestination=${project.buildDir.absolutePath}/compose_reports&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;编译后在 &lt;code&gt;build/compose_reports/&lt;/code&gt; 目录下查看 &lt;code&gt;*-composables.txt&lt;/code&gt; 文件，标注了每个函数是否为 skippable。&lt;/p&gt;&lt;h2&gt;二、状态管理最佳实践：精准控制状态作用域&lt;/h2&gt;&lt;p&gt;状态的位置决定了重组的范围。错误的状态提升会导致大量不相关的组合函数被迫重组。&lt;/p&gt;&lt;h3&gt;2.1 状态下移（State Hoisting Down）&lt;/h3&gt;&lt;p&gt;将状态保持在尽可能低的层级，只有真正需要该状态的组合函数才持有它：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;状态定义在顶层，导致整个屏幕重组
@Composable
fun&amp;nbsp;HomeScreen()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;searchQuery&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(&amp;quot;&amp;quot;)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...&amp;nbsp;大量其他&amp;nbsp;UI
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchBar(query&amp;nbsp;=&amp;nbsp;searchQuery,&amp;nbsp;onQueryChange&amp;nbsp;=&amp;nbsp;{&amp;nbsp;searchQuery&amp;nbsp;=&amp;nbsp;it&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ProductList()&amp;nbsp;//&amp;nbsp;与&amp;nbsp;searchQuery&amp;nbsp;无关，但被迫重组
}

//&amp;nbsp;✅&amp;nbsp;状态下移到&amp;nbsp;SearchBar&amp;nbsp;内部
@Composable
fun&amp;nbsp;HomeScreen()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;...&amp;nbsp;大量其他&amp;nbsp;UI
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SearchBar()&amp;nbsp;//&amp;nbsp;自管理状态，不影响外部
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ProductList()
}&lt;/pre&gt;&lt;h3&gt;2.2 使用 derivedStateOf 减少不必要的重组&lt;/h3&gt;&lt;p&gt;当一个状态是从另一个状态派生出来，且变化频率不同时，使用 &lt;code&gt;derivedStateOf&lt;/code&gt;：&lt;/p&gt;&lt;pre&gt;val&amp;nbsp;listState&amp;nbsp;=&amp;nbsp;rememberLazyListState()

//&amp;nbsp;❌&amp;nbsp;每次滚动都会触发重组（即使按钮可见性没变）
val&amp;nbsp;showButton&amp;nbsp;=&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;&amp;gt;&amp;nbsp;0

//&amp;nbsp;✅&amp;nbsp;只有当&amp;quot;是否显示&amp;quot;真正改变时才重组
val&amp;nbsp;showButton&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;derivedStateOf&amp;nbsp;{&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;2.3 Lambda 引用稳定化&lt;/h3&gt;&lt;p&gt;传入可组合函数的 lambda 如果每次都是新对象，会导致重组。使用 &lt;code&gt;rememberUpdatedState&lt;/code&gt; 或将 lambda 提升为稳定引用：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;每次重组都创建新&amp;nbsp;lambda
@Composable
fun&amp;nbsp;ParentScreen(viewModel:&amp;nbsp;MyViewModel)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ItemList(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onItemClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;id&amp;nbsp;-&amp;gt;&amp;nbsp;viewModel.onItemSelected(id)&amp;nbsp;}&amp;nbsp;//&amp;nbsp;每次都是新对象
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
}

//&amp;nbsp;✅&amp;nbsp;使用&amp;nbsp;rememberUpdatedState&amp;nbsp;或&amp;nbsp;@Stable&amp;nbsp;的&amp;nbsp;ViewModel&amp;nbsp;方法引用
@Composable
fun&amp;nbsp;ParentScreen(viewModel:&amp;nbsp;MyViewModel)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;onItemClick&amp;nbsp;=&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;viewModel::onItemSelected&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ItemList(onItemClick&amp;nbsp;=&amp;nbsp;onItemClick)
}&lt;/pre&gt;&lt;h2&gt;三、LazyList 性能优化：让列表飞起来&lt;/h2&gt;&lt;p&gt;&lt;code&gt;LazyColumn&lt;/code&gt; 和 &lt;code&gt;LazyRow&lt;/code&gt; 是 Compose 中最常用也最容易出现性能问题的组件。&lt;/p&gt;&lt;h3&gt;3.1 必须提供 key 参数&lt;/h3&gt;&lt;p&gt;为 &lt;code&gt;LazyList&lt;/code&gt; 的每个 item 提供稳定且唯一的 key，避免列表数据变化时的全量重绘：&lt;/p&gt;&lt;pre&gt;LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items&amp;nbsp;=&amp;nbsp;products,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;product&amp;nbsp;-&amp;gt;&amp;nbsp;product.id&amp;nbsp;}&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;稳定&amp;nbsp;key
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;product&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ProductItem(product&amp;nbsp;=&amp;nbsp;product)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;3.2 使用 contentType 优化 item 复用&lt;/h3&gt;&lt;p&gt;当列表包含多种 item 类型时，指定 &lt;code&gt;contentType&lt;/code&gt; 让 Compose 更高效地复用组合：&lt;/p&gt;&lt;pre&gt;LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items&amp;nbsp;=&amp;nbsp;feedItems,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;it.id&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentType&amp;nbsp;=&amp;nbsp;{&amp;nbsp;item&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;when&amp;nbsp;(item)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;FeedItem.Banner&amp;nbsp;-&amp;gt;&amp;nbsp;&amp;quot;banner&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;FeedItem.Product&amp;nbsp;-&amp;gt;&amp;nbsp;&amp;quot;product&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;FeedItem.Ad&amp;nbsp;-&amp;gt;&amp;nbsp;&amp;quot;ad&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;item&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;when&amp;nbsp;(item)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;FeedItem.Banner&amp;nbsp;-&amp;gt;&amp;nbsp;BannerItem(item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;FeedItem.Product&amp;nbsp;-&amp;gt;&amp;nbsp;ProductItem(item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;FeedItem.Ad&amp;nbsp;-&amp;gt;&amp;nbsp;AdItem(item)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;3.3 避免在 item 内部执行耗时操作&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;图片加载使用 Coil 或 Glide for Compose，异步加载避免阻塞主线程&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;复杂计算使用 &lt;code&gt;remember&lt;/code&gt; 缓存结果&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;使用 &lt;code&gt;Modifier.animateItem()&lt;/code&gt;（Compose 1.7+）替代旧版 &lt;code&gt;animateItemPlacement&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;ProductItem(product:&amp;nbsp;Product)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;缓存格式化结果，避免每次重组都计算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;formattedPrice&amp;nbsp;=&amp;nbsp;remember(product.price)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NumberFormat.getCurrencyInstance().format(product.price)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AsyncImage(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model&amp;nbsp;=&amp;nbsp;product.imageUrl,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;product.name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.fillMaxWidth()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.height(200.dp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(text&amp;nbsp;=&amp;nbsp;formattedPrice)
}&lt;/pre&gt;&lt;h2&gt;四、动画性能优化：流畅不卡顿&lt;/h2&gt;&lt;p&gt;Compose 动画在处理不当时极易造成过度重组，尤其是涉及大量 UI 元素同时动画的场景。&lt;/p&gt;&lt;h3&gt;4.1 优先使用 Modifier 动画而非 State 动画&lt;/h3&gt;&lt;p&gt;基于 &lt;code&gt;Modifier&lt;/code&gt; 的动画（如 &lt;code&gt;graphicsLayer&lt;/code&gt;）运行在渲染层，不触发重组；而基于 State 的动画每帧都会触发重组：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;基于&amp;nbsp;State&amp;nbsp;的动画&amp;nbsp;-&amp;nbsp;每帧触发重组
var&amp;nbsp;scale&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(1f)&amp;nbsp;}
val&amp;nbsp;animatedScale&amp;nbsp;by&amp;nbsp;animateFloatAsState(scale)
Box(Modifier.scale(animatedScale))&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}

//&amp;nbsp;✅&amp;nbsp;使用&amp;nbsp;graphicsLayer&amp;nbsp;-&amp;nbsp;运行在渲染层，零重组
val&amp;nbsp;infiniteTransition&amp;nbsp;=&amp;nbsp;rememberInfiniteTransition()
val&amp;nbsp;scale&amp;nbsp;by&amp;nbsp;infiniteTransition.animateFloat(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;initialValue&amp;nbsp;=&amp;nbsp;1f,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;targetValue&amp;nbsp;=&amp;nbsp;1.2f,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;animationSpec&amp;nbsp;=&amp;nbsp;infiniteRepeatable(tween(1000))
)
Box(Modifier.graphicsLayer&amp;nbsp;{&amp;nbsp;scaleX&amp;nbsp;=&amp;nbsp;scale;&amp;nbsp;scaleY&amp;nbsp;=&amp;nbsp;scale&amp;nbsp;})&amp;nbsp;{&amp;nbsp;...&amp;nbsp;}&lt;/pre&gt;&lt;h3&gt;4.2 合理使用 AnimatedVisibility 和 AnimatedContent&lt;/h3&gt;&lt;pre&gt;//&amp;nbsp;使用&amp;nbsp;AnimatedVisibility&amp;nbsp;时指定&amp;nbsp;enter/exit&amp;nbsp;动画避免默认的昂贵动画
AnimatedVisibility(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;visible&amp;nbsp;=&amp;nbsp;isVisible,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enter&amp;nbsp;=&amp;nbsp;fadeIn(animationSpec&amp;nbsp;=&amp;nbsp;tween(200)),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;exit&amp;nbsp;=&amp;nbsp;fadeOut(animationSpec&amp;nbsp;=&amp;nbsp;tween(200))
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;HeavyContent()
}&lt;/pre&gt;&lt;h2&gt;五、图片与资源加载优化&lt;/h2&gt;&lt;p&gt;图片加载是 Android 应用中最常见的性能瓶颈之一，在 Compose 中更需要特别注意。&lt;/p&gt;&lt;h3&gt;5.1 Coil 3 最佳配置&lt;/h3&gt;&lt;pre&gt;//&amp;nbsp;Application&amp;nbsp;中配置全局&amp;nbsp;ImageLoader
val&amp;nbsp;imageLoader&amp;nbsp;=&amp;nbsp;ImageLoader.Builder(context)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.memoryCache&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MemoryCache.Builder(context)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.maxSizePercent(0.25)&amp;nbsp;//&amp;nbsp;使用&amp;nbsp;25%&amp;nbsp;内存
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.diskCache&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;DiskCache.Builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.directory(context.cacheDir.resolve(&amp;quot;image_cache&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.maxSizePercent(0.02)&amp;nbsp;//&amp;nbsp;2%&amp;nbsp;磁盘空间
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.respectCacheHeaders(false)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()&lt;/pre&gt;&lt;h3&gt;5.2 使用 SubcomposeLayout 延迟测量&lt;/h3&gt;&lt;p&gt;对于需要根据子组件尺寸来布局的复杂场景，使用 &lt;code&gt;SubcomposeLayout&lt;/code&gt; 可以避免多次测量：&lt;/p&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;AdaptiveLayout(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mainContent:&amp;nbsp;@Composable&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;overlay:&amp;nbsp;@Composable&amp;nbsp;(IntSize)&amp;nbsp;-&amp;gt;&amp;nbsp;Unit
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SubcomposeLayout&amp;nbsp;{&amp;nbsp;constraints&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;mainPlaceable&amp;nbsp;=&amp;nbsp;subcompose(&amp;quot;main&amp;quot;,&amp;nbsp;mainContent)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.first().measure(constraints)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;overlayPlaceable&amp;nbsp;=&amp;nbsp;subcompose(&amp;quot;overlay&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;overlay(IntSize(mainPlaceable.width,&amp;nbsp;mainPlaceable.height))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}.first().measure(constraints)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;layout(mainPlaceable.width,&amp;nbsp;mainPlaceable.height)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;mainPlaceable.place(0,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;overlayPlaceable.place(0,&amp;nbsp;0)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;六、性能监控：用工具发现问题&lt;/h2&gt;&lt;p&gt;优化要有数据支撑，以下工具帮助你精准定位性能瓶颈。&lt;/p&gt;&lt;h3&gt;6.1 Android Studio 重组高亮&lt;/h3&gt;&lt;p&gt;在 Android Studio 中启用 &lt;strong&gt;Layout Inspector → Recomposition Counts&lt;/strong&gt;，实时查看每个可组合函数的重组次数，快速找到热点函数。&lt;/p&gt;&lt;h3&gt;6.2 使用 Macrobenchmark 测量启动和滚动&lt;/h3&gt;&lt;pre&gt;@RunWith(AndroidJUnit4::class)
class&amp;nbsp;ScrollBenchmark&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@get:Rule
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;benchmarkRule&amp;nbsp;=&amp;nbsp;MacrobenchmarkRule()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Test
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;scrollLatency()&amp;nbsp;=&amp;nbsp;benchmarkRule.measureRepeated(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;packageName&amp;nbsp;=&amp;nbsp;&amp;quot;com.example.app&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;metrics&amp;nbsp;=&amp;nbsp;listOf(FrameTimingMetric()),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;iterations&amp;nbsp;=&amp;nbsp;5,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setupBlock&amp;nbsp;=&amp;nbsp;{&amp;nbsp;startActivityAndWait()&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;recycler&amp;nbsp;=&amp;nbsp;device.findObject(By.res(&amp;quot;product_list&amp;quot;))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;recycler.setGestureMargin(device.displayWidth&amp;nbsp;/&amp;nbsp;5)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;repeat(3)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;recycler.fling(Direction.DOWN)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;device.waitForIdle()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h3&gt;6.3 Baseline Profiles 提升启动性能&lt;/h3&gt;&lt;p&gt;为关键代码路径生成 Baseline Profile，让 ART 在安装时预编译热路径，显著提升冷启动速度（通常可提升 20-40%）：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;在&amp;nbsp;macrobenchmark&amp;nbsp;模块中
@ExperimentalBaselineProfilesApi
class&amp;nbsp;BaselineProfileGenerator&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@get:Rule
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;baselineRule&amp;nbsp;=&amp;nbsp;BaselineProfileRule()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Test
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;startup()&amp;nbsp;=&amp;nbsp;baselineRule.collectBaselineProfile(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;packageName&amp;nbsp;=&amp;nbsp;&amp;quot;com.example.app&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;startActivityAndWait()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;模拟用户关键路径操作
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;七、总结与优化清单&lt;/h2&gt;&lt;p&gt;Compose 性能优化是一个系统工程，需要从架构设计到代码细节全面考量。以下是一份实用的优化 Checklist：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;✅ 使用 &lt;code&gt;@Immutable&lt;/code&gt; / &lt;code&gt;@Stable&lt;/code&gt; 标注数据类，确保参数稳定性&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 用 Compose 编译器报告检查所有可组合函数是否为 skippable&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 将状态保持在最小作用域，避免顶层状态导致全量重组&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 使用 &lt;code&gt;derivedStateOf&lt;/code&gt; 处理派生状态&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ &lt;code&gt;LazyList&lt;/code&gt; 始终提供稳定的 &lt;code&gt;key&lt;/code&gt; 和 &lt;code&gt;contentType&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 优先使用 &lt;code&gt;graphicsLayer&lt;/code&gt; 执行动画，避免每帧重组&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 配置 Coil 内存/磁盘缓存，避免重复网络请求&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 使用 Macrobenchmark + FrameTimingMetric 量化滚动性能&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;✅ 为生产 App 添加 Baseline Profiles 提升冷启动速度&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;性能优化没有银弹，关键是建立&amp;quot;测量 → 定位 → 优化 → 验证&amp;quot;的闭环。希望本文的实战技巧能帮助你的 Compose 应用达到 60fps 流畅体验！&lt;/p&gt;</description><pubDate>Thu, 23 Apr 2026 08:06:11 +0800</pubDate></item><item><title>大模型RAG检索增强生成实战：从原理到落地的完整开发指南</title><link>https://blog.resmic.cn/post/242.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260422101655177682421548562.jpg&quot; alt=&quot;封面&quot; style=&quot;width:100%;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;一、什么是 RAG？为什么它如此重要&lt;/h2&gt;&lt;p&gt;RAG（Retrieval-Augmented Generation，检索增强生成）是一种将信息检索与大语言模型生成能力相结合的技术架构。传统大模型存在两个核心痛点：&lt;strong&gt;知识截止日期&lt;/strong&gt;（训练数据有时效性）和&lt;strong&gt;幻觉问题&lt;/strong&gt;（模型会编造不存在的事实）。RAG 通过在生成前先从外部知识库检索相关内容，将其作为上下文传入模型，从根本上缓解了这两个问题。&lt;/p&gt;&lt;p&gt;RAG 的典型应用场景包括：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;企业内部知识问答系统（对接公司文档、Wiki、FAQ）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;智能客服（实时检索产品手册、政策文件）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;代码助手（检索私有代码库和文档）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;医疗/法律等专业领域问答（检索权威文献库）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;二、RAG 核心架构与工作流程&lt;/h2&gt;&lt;p&gt;一个完整的 RAG 系统由以下几个核心组件构成：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;文档加载器（Document Loader）&lt;/strong&gt;：支持 PDF、Word、Markdown、网页等多种格式&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;文本分块器（Text Splitter）&lt;/strong&gt;：将长文档切分为合适的片段（Chunk）&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Embedding 模型&lt;/strong&gt;：将文本转换为高维向量表示&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;向量数据库（Vector Store）&lt;/strong&gt;：存储和检索向量，常用 Chroma、Milvus、Pinecone&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;检索器（Retriever）&lt;/strong&gt;：根据用户查询找出最相关的文档片段&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;生成器（Generator）&lt;/strong&gt;：将检索结果与问题拼接成 Prompt 调用 LLM 生成答案&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;工作流程如下：用户提问 → 查询向量化 → 向量数据库相似度检索 → 拼接 Prompt → LLM 生成答案 → 返回结果。&lt;/p&gt;&lt;h2&gt;三、关键技术选型与优化策略&lt;/h2&gt;&lt;p&gt;RAG 系统的效果很大程度上取决于以下几个关键决策：&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3.1 文档分块策略&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;分块大小直接影响检索精度与召回率。经验值：chunk_size=512~1024 tokens，chunk_overlap=50~100 tokens。对于结构化文档，建议按段落或标题层级分块；对于代码文档，按函数或类分块效果更好。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;3.2 Embedding 模型选择&lt;/strong&gt;&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;中文场景优先考虑：BAAI/bge-large-zh-v1.5、text2vec-large-chinese&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;多语言场景：multilingual-e5-large&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;OpenAI API：text-embedding-3-large（效果最佳但有成本）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;&lt;strong&gt;3.3 检索优化&lt;/strong&gt;&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;混合检索&lt;/strong&gt;：结合向量检索（语义）和 BM25 检索（关键词），用 RRF 融合排序&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;重排序（Reranker）&lt;/strong&gt;：用 cross-encoder 模型对 Top-K 结果重新打分&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;查询扩展&lt;/strong&gt;：对用户查询进行改写或生成多个子查询&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;四、Python 实战：用 LangChain 构建 RAG 系统&lt;/h2&gt;&lt;p&gt;下面是一个基于 LangChain + Chroma + OpenAI 的完整 RAG 示例：&lt;/p&gt;&lt;pre&gt;from&amp;nbsp;langchain.document_loaders&amp;nbsp;import&amp;nbsp;PyPDFLoader,&amp;nbsp;DirectoryLoader
from&amp;nbsp;langchain.text_splitter&amp;nbsp;import&amp;nbsp;RecursiveCharacterTextSplitter
from&amp;nbsp;langchain.embeddings&amp;nbsp;import&amp;nbsp;HuggingFaceEmbeddings
from&amp;nbsp;langchain.vectorstores&amp;nbsp;import&amp;nbsp;Chroma
from&amp;nbsp;langchain.chains&amp;nbsp;import&amp;nbsp;RetrievalQA
from&amp;nbsp;langchain.llms&amp;nbsp;import&amp;nbsp;OpenAI

#&amp;nbsp;1.&amp;nbsp;加载文档
loader&amp;nbsp;=&amp;nbsp;DirectoryLoader(&amp;#39;./docs&amp;#39;,&amp;nbsp;glob=&amp;#39;**/*.pdf&amp;#39;,&amp;nbsp;loader_cls=PyPDFLoader)
documents&amp;nbsp;=&amp;nbsp;loader.load()
print(f&amp;quot;加载了&amp;nbsp;{len(documents)}&amp;nbsp;个文档页面&amp;quot;)

#&amp;nbsp;2.&amp;nbsp;文本分块
text_splitter&amp;nbsp;=&amp;nbsp;RecursiveCharacterTextSplitter(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk_size=512,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chunk_overlap=64,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;separators=[&amp;quot;\n\n&amp;quot;,&amp;nbsp;&amp;quot;\n&amp;quot;,&amp;nbsp;&amp;quot;。&amp;quot;,&amp;nbsp;&amp;quot;！&amp;quot;,&amp;nbsp;&amp;quot;？&amp;quot;,&amp;nbsp;&amp;quot;&amp;nbsp;&amp;quot;]
)
chunks&amp;nbsp;=&amp;nbsp;text_splitter.split_documents(documents)
print(f&amp;quot;切分为&amp;nbsp;{len(chunks)}&amp;nbsp;个文本块&amp;quot;)

#&amp;nbsp;3.&amp;nbsp;创建&amp;nbsp;Embedding&amp;nbsp;模型（使用本地模型，无需&amp;nbsp;API&amp;nbsp;key）
embeddings&amp;nbsp;=&amp;nbsp;HuggingFaceEmbeddings(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model_name=&amp;quot;BAAI/bge-large-zh-v1.5&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model_kwargs={&amp;quot;device&amp;quot;:&amp;nbsp;&amp;quot;cuda&amp;quot;},&amp;nbsp;&amp;nbsp;#&amp;nbsp;有&amp;nbsp;GPU&amp;nbsp;时使用
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;encode_kwargs={&amp;quot;normalize_embeddings&amp;quot;:&amp;nbsp;True}
)

#&amp;nbsp;4.&amp;nbsp;构建向量数据库
vectorstore&amp;nbsp;=&amp;nbsp;Chroma.from_documents(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;documents=chunks,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;embedding=embeddings,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;persist_directory=&amp;quot;./chroma_db&amp;quot;
)
vectorstore.persist()
print(&amp;quot;向量数据库构建完成&amp;quot;)

#&amp;nbsp;5.&amp;nbsp;构建检索链
retriever&amp;nbsp;=&amp;nbsp;vectorstore.as_retriever(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;search_type=&amp;quot;mmr&amp;quot;,&amp;nbsp;&amp;nbsp;#&amp;nbsp;最大边际相关性，减少冗余
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;search_kwargs={&amp;quot;k&amp;quot;:&amp;nbsp;5,&amp;nbsp;&amp;quot;fetch_k&amp;quot;:&amp;nbsp;20}
)

#&amp;nbsp;6.&amp;nbsp;构建&amp;nbsp;QA&amp;nbsp;链
qa_chain&amp;nbsp;=&amp;nbsp;RetrievalQA.from_chain_type(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;llm=OpenAI(temperature=0),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;chain_type=&amp;quot;stuff&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;retriever=retriever,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return_source_documents=True,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;verbose=True
)

#&amp;nbsp;7.&amp;nbsp;问答
result&amp;nbsp;=&amp;nbsp;qa_chain({&amp;quot;query&amp;quot;:&amp;nbsp;&amp;quot;请解释产品的退款政策&amp;quot;})
print(&amp;quot;答案:&amp;quot;,&amp;nbsp;result[&amp;quot;result&amp;quot;])
print(&amp;quot;来源文档:&amp;quot;,&amp;nbsp;[doc.metadata&amp;nbsp;for&amp;nbsp;doc&amp;nbsp;in&amp;nbsp;result[&amp;quot;source_documents&amp;quot;]])&lt;/pre&gt;&lt;p&gt;上述代码涵盖了完整的 RAG 流水线。在生产环境中，建议将向量数据库替换为 Milvus 或 Weaviate 以支持更大规模数据。&lt;/p&gt;&lt;h2&gt;五、常见问题与解决方案&lt;/h2&gt;&lt;p&gt;在实际落地过程中，RAG 系统经常遇到以下问题：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;检索不准确&lt;/strong&gt;：优先检查 Embedding 模型是否与文档语言匹配，考虑加入混合检索和 Reranker&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;答案幻觉仍然存在&lt;/strong&gt;：在 Prompt 中明确要求模型只基于检索内容回答，无信息时说&amp;quot;不知道&amp;quot;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;响应速度慢&lt;/strong&gt;：向量检索建议使用 HNSW 索引；Embedding 计算可异步或缓存&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;上下文窗口超限&lt;/strong&gt;：控制检索数量（Top-K=3~5），或使用 Map-Reduce / Refine chain&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;文档更新同步&lt;/strong&gt;：建议设计增量更新机制，使用文档 hash 判断是否需要重新 Embedding&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;RAG 并非银弹，它更适合&amp;quot;知识密集型&amp;quot;场景。对于逻辑推理、数学计算等任务，Fine-tuning 或 Tool Use 可能是更好的选择。随着 LLM 上下文窗口不断增大（如 Gemini 1M token），未来 RAG 的架构形态也会持续演进，但其核心价值——让模型获取实时、可靠、私有知识——将长期存在。&lt;/p&gt;</description><pubDate>Wed, 22 Apr 2026 10:17:15 +0800</pubDate></item><item><title>Android Jetpack Compose 实战：从零构建一个完整的 ToDo 应用</title><link>https://blog.resmic.cn/post/241.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260421080629177672998978534.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Jetpack Compose 已经从“尝鲜”阶段进入了主流 Android 开发的视野。越来越多的团队开始在新项目中使用 Compose，但很多开发者在真正上手时才发现：看懂文档是一回事，写出能跑的完整应用又是另一回事。&lt;/p&gt;&lt;p&gt;本文用一个真实的 ToDo 应用作为载体，从项目搭建到最终运行，完整走一遍 Compose 开发流程，包括状态管理、数据持久化、MVVM 架构、UI 交互等核心知识点，并附上踩坑记录。&lt;/p&gt;&lt;h2&gt;一、项目结构与依赖配置&lt;/h2&gt;&lt;p&gt;先看整体项目结构，我们按照标准 MVVM 分层：&lt;/p&gt;&lt;pre&gt;app/
├──&amp;nbsp;data/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;Task.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;TaskDao.kt
│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;TaskDatabase.kt
├──&amp;nbsp;repository/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;TaskRepository.kt
├──&amp;nbsp;viewmodel/
│&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;TaskViewModel.kt
└──&amp;nbsp;ui/
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;MainActivity.kt
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;├──&amp;nbsp;TaskListScreen.kt
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;└──&amp;nbsp;AddTaskScreen.kt&lt;/pre&gt;&lt;p&gt;在 build.gradle.kts 中添加依赖：&lt;/p&gt;&lt;pre&gt;android&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;compileSdk&amp;nbsp;=&amp;nbsp;34
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;composeOptions&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kotlinCompilerExtensionVersion&amp;nbsp;=&amp;nbsp;&amp;quot;1.5.8&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;buildFeatures&amp;nbsp;{&amp;nbsp;compose&amp;nbsp;=&amp;nbsp;true&amp;nbsp;}
}
dependencies&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;composeBom&amp;nbsp;=&amp;nbsp;platform(&amp;quot;androidx.compose:compose-bom:2024.02.00&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation(composeBom)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation(&amp;quot;androidx.compose.ui:ui&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation(&amp;quot;androidx.compose.material3:material3&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;roomVersion&amp;nbsp;=&amp;nbsp;&amp;quot;2.6.1&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation(&amp;quot;androidx.room:room-runtime:$roomVersion&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation(&amp;quot;androidx.room:room-ktx:$roomVersion&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ksp(&amp;quot;androidx.room:room-compiler:$roomVersion&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation(&amp;quot;androidx.navigation:navigation-compose:2.7.6&amp;quot;)
}&lt;/pre&gt;&lt;p&gt;踩坑提示：Compose 编译器版本必须与 Kotlin 版本严格对应。&lt;/p&gt;&lt;h2&gt;二、数据层：Room 数据库实现&lt;/h2&gt;&lt;pre&gt;@Entity(tableName&amp;nbsp;=&amp;nbsp;&amp;quot;tasks&amp;quot;)
data&amp;nbsp;class&amp;nbsp;Task(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@PrimaryKey(autoGenerate&amp;nbsp;=&amp;nbsp;true)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;id:&amp;nbsp;Int&amp;nbsp;=&amp;nbsp;0,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;title:&amp;nbsp;String,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;description:&amp;nbsp;String&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;isCompleted:&amp;nbsp;Boolean&amp;nbsp;=&amp;nbsp;false,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;createdAt:&amp;nbsp;Long&amp;nbsp;=&amp;nbsp;System.currentTimeMillis()
)

@Dao
interface&amp;nbsp;TaskDao&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Query(&amp;quot;SELECT&amp;nbsp;*&amp;nbsp;FROM&amp;nbsp;tasks&amp;nbsp;ORDER&amp;nbsp;BY&amp;nbsp;createdAt&amp;nbsp;DESC&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;getAllTasks():&amp;nbsp;Flow&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Insert(onConflict&amp;nbsp;=&amp;nbsp;OnConflictStrategy.REPLACE)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;insertTask(task:&amp;nbsp;Task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Update
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;updateTask(task:&amp;nbsp;Task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Delete
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;deleteTask(task:&amp;nbsp;Task)
}&lt;/pre&gt;&lt;h2&gt;三、Repository 与 ViewModel&lt;/h2&gt;&lt;pre&gt;class&amp;nbsp;TaskRepository(private&amp;nbsp;val&amp;nbsp;taskDao:&amp;nbsp;TaskDao)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;allTasks:&amp;nbsp;Flow&amp;gt;&amp;nbsp;=&amp;nbsp;taskDao.getAllTasks()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;insertTask(task:&amp;nbsp;Task)&amp;nbsp;=&amp;nbsp;taskDao.insertTask(task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;updateTask(task:&amp;nbsp;Task)&amp;nbsp;=&amp;nbsp;taskDao.updateTask(task)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;deleteTask(task:&amp;nbsp;Task)&amp;nbsp;=&amp;nbsp;taskDao.deleteTask(task)
}

class&amp;nbsp;TaskViewModel(application:&amp;nbsp;Application)&amp;nbsp;:&amp;nbsp;AndroidViewModel(application)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;repository:&amp;nbsp;TaskRepository
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;allTasks:&amp;nbsp;StateFlow&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;newTaskTitle&amp;nbsp;by&amp;nbsp;mutableStateOf(&amp;quot;&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;set
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;init&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;db&amp;nbsp;=&amp;nbsp;TaskDatabase.getDatabase(application)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;repository&amp;nbsp;=&amp;nbsp;TaskRepository(db.taskDao())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;allTasks&amp;nbsp;=&amp;nbsp;repository.allTasks
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.stateIn(viewModelScope,&amp;nbsp;SharingStarted.WhileSubscribed(5000),&amp;nbsp;emptyList())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;addTask()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(newTaskTitle.isBlank())&amp;nbsp;return
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModelScope.launch&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;repository.insertTask(Task(title&amp;nbsp;=&amp;nbsp;newTaskTitle.trim()))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;newTaskTitle&amp;nbsp;=&amp;nbsp;&amp;quot;&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;toggleTaskCompletion(task:&amp;nbsp;Task)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModelScope.launch&amp;nbsp;{&amp;nbsp;repository.updateTask(task.copy(isCompleted&amp;nbsp;=&amp;nbsp;!task.isCompleted))&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;deleteTask(task:&amp;nbsp;Task)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModelScope.launch&amp;nbsp;{&amp;nbsp;repository.deleteTask(task)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;踩坑提示：在 Compose 里推荐用 StateFlow + collectAsStateWithLifecycle()，能感知生命周期，避免在后台仍然收集数据浪费资源。&lt;/p&gt;&lt;h2&gt;四、UI 层：任务列表页面&lt;/h2&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;TaskListScreen(viewModel:&amp;nbsp;TaskViewModel&amp;nbsp;=&amp;nbsp;viewModel(),&amp;nbsp;onNavigateToAdd:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;tasks&amp;nbsp;by&amp;nbsp;viewModel.allTasks.collectAsStateWithLifecycle()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Scaffold(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;topBar&amp;nbsp;=&amp;nbsp;{&amp;nbsp;TopAppBar(title&amp;nbsp;=&amp;nbsp;{&amp;nbsp;Text(&amp;quot;我的待办&amp;quot;)&amp;nbsp;})&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;floatingActionButton&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FloatingActionButton(onClick&amp;nbsp;=&amp;nbsp;onNavigateToAdd)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icon(Icons.Default.Add,&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;&amp;quot;添加任务&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;paddingValues&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(tasks.isEmpty())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Box(modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxSize().padding(paddingValues),&amp;nbsp;contentAlignment&amp;nbsp;=&amp;nbsp;Alignment.Center)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&amp;quot;暂无任务，点击右下角添加&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LazyColumn(modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(paddingValues),&amp;nbsp;contentPadding&amp;nbsp;=&amp;nbsp;PaddingValues(16.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(tasks,&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;it.id&amp;nbsp;})&amp;nbsp;{&amp;nbsp;task&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TaskItem(task&amp;nbsp;=&amp;nbsp;task,&amp;nbsp;onToggle&amp;nbsp;=&amp;nbsp;{&amp;nbsp;viewModel.toggleTaskCompletion(task)&amp;nbsp;},&amp;nbsp;onDelete&amp;nbsp;=&amp;nbsp;{&amp;nbsp;viewModel.deleteTask(task)&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

@Composable
fun&amp;nbsp;TaskItem(task:&amp;nbsp;Task,&amp;nbsp;onToggle:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit,&amp;nbsp;onDelete:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Card(modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxWidth().padding(vertical&amp;nbsp;=&amp;nbsp;4.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Row(modifier&amp;nbsp;=&amp;nbsp;Modifier.padding(12.dp),&amp;nbsp;verticalAlignment&amp;nbsp;=&amp;nbsp;Alignment.CenterVertically)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Checkbox(checked&amp;nbsp;=&amp;nbsp;task.isCompleted,&amp;nbsp;onCheckedChange&amp;nbsp;=&amp;nbsp;{&amp;nbsp;onToggle()&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(task.title,&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.weight(1f),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;textDecoration&amp;nbsp;=&amp;nbsp;if&amp;nbsp;(task.isCompleted)&amp;nbsp;TextDecoration.LineThrough&amp;nbsp;else&amp;nbsp;TextDecoration.None)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;IconButton(onClick&amp;nbsp;=&amp;nbsp;onDelete)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icon(Icons.Default.Delete,&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;&amp;quot;删除&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;五、添加任务页面与导航配置&lt;/h2&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;AddTaskScreen(viewModel:&amp;nbsp;TaskViewModel&amp;nbsp;=&amp;nbsp;viewModel(),&amp;nbsp;onTaskAdded:&amp;nbsp;()&amp;nbsp;-&amp;gt;&amp;nbsp;Unit)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Scaffold(topBar&amp;nbsp;=&amp;nbsp;{&amp;nbsp;TopAppBar(title&amp;nbsp;=&amp;nbsp;{&amp;nbsp;Text(&amp;quot;添加任务&amp;quot;)&amp;nbsp;})&amp;nbsp;})&amp;nbsp;{&amp;nbsp;paddingValues&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column(modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxSize().padding(paddingValues).padding(16.dp))&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;OutlinedTextField(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;value&amp;nbsp;=&amp;nbsp;viewModel.newTaskTitle,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;onValueChange&amp;nbsp;=&amp;nbsp;{&amp;nbsp;viewModel.newTaskTitle&amp;nbsp;=&amp;nbsp;it&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;label&amp;nbsp;=&amp;nbsp;{&amp;nbsp;Text(&amp;quot;任务名称&amp;quot;)&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxWidth()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Button(onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;viewModel.addTask();&amp;nbsp;onTaskAdded()&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.fillMaxWidth().padding(top&amp;nbsp;=&amp;nbsp;16.dp),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;enabled&amp;nbsp;=&amp;nbsp;viewModel.newTaskTitle.isNotBlank())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(&amp;quot;保存任务&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

class&amp;nbsp;MainActivity&amp;nbsp;:&amp;nbsp;ComponentActivity()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;override&amp;nbsp;fun&amp;nbsp;onCreate(savedInstanceState:&amp;nbsp;Bundle?)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;super.onCreate(savedInstanceState)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;setContent&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;TodoAppTheme&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;navController&amp;nbsp;=&amp;nbsp;rememberNavController()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;NavHost(navController,&amp;nbsp;startDestination&amp;nbsp;=&amp;nbsp;&amp;quot;list&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;composable(&amp;quot;list&amp;quot;)&amp;nbsp;{&amp;nbsp;TaskListScreen(onNavigateToAdd&amp;nbsp;=&amp;nbsp;{&amp;nbsp;navController.navigate(&amp;quot;add&amp;quot;)&amp;nbsp;})&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;composable(&amp;quot;add&amp;quot;)&amp;nbsp;{&amp;nbsp;AddTaskScreen(onTaskAdded&amp;nbsp;=&amp;nbsp;{&amp;nbsp;navController.popBackStack()&amp;nbsp;})&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;六、常见踩坑总结&lt;/h2&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;remember vs rememberSaveable&lt;/strong&gt;：remember 在重组时保持状态，横竖屏切换后会丢失；需要跨配置变更保留状态用 rememberSaveable。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;LazyColumn key 参数&lt;/strong&gt;：一定要给 items 传 key 参数，否则列表更新时动画不正确。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Modifier 顺序很重要&lt;/strong&gt;：.padding().clickable() 和 .clickable().padding() 效果完全不同。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;AndroidViewModel 与 ViewModel&lt;/strong&gt;：需要访问 Context 时用 AndroidViewModel，否则普通 ViewModel 即可。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;七、扩展方向&lt;/h2&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;优先级与分类&lt;/strong&gt;：给 Task 增加 priority 和 category 字段，UI 上用颜色区分&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;截止日期提醒&lt;/strong&gt;：集成 WorkManager 设置本地通知&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Widget 支持&lt;/strong&gt;：使用 Glance API 在桌面展示待办列表&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;深色模式&lt;/strong&gt;：Material3 自动支持动态主题，只需在 Theme.kt 中开启 dynamicColor = true&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;如果在实际项目中遇到其他 Compose 相关问题，欢迎留言交流！&lt;/p&gt;</description><pubDate>Tue, 21 Apr 2026 08:13:52 +0800</pubDate></item><item><title>Android Hilt 依赖注入实战：从零搭建 Clean Architecture 注入体系</title><link>https://blog.resmic.cn/post/240.html</link><description>&lt;h2&gt;为什么要在 Android 项目中引入 Hilt？&lt;/h2&gt;&lt;p&gt;依赖注入（Dependency Injection，DI）是现代 Android 开发中几乎无法绕开的话题。手动管理对象依赖不仅繁琐，还容易在 Activity/Fragment 生命周期中引发内存泄漏。Google 官方推荐的 &lt;strong&gt;Hilt&lt;/strong&gt; 框架基于 Dagger2，通过编译期代码生成，在保持高性能的同时大幅降低了上手门槛。&lt;/p&gt;&lt;p&gt;本文以一个真实的“用户登录 + 数据加载”功能为线索，带你从零搞建一套符合 Clean Architecture 的 Hilt 注入体系，覆盖以下场景：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;ViewModel 中注入 Repository&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Repository 中注入 Retrofit 和 Room&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;多模块项目中的跨模块注入&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;单元测试中替换真实依赖&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;第一步：添加依赖与基础配置&lt;/h2&gt;&lt;p&gt;在项目根目录 &lt;code&gt;build.gradle&lt;/code&gt; 中添加 Hilt 插件：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;根目录&amp;nbsp;build.gradle
plugins&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;&amp;#39;com.google.dagger.hilt.android&amp;#39;&amp;nbsp;version&amp;nbsp;&amp;#39;2.51.1&amp;#39;&amp;nbsp;apply&amp;nbsp;false
}&lt;/pre&gt;&lt;p&gt;在 app 模块 &lt;code&gt;build.gradle&lt;/code&gt; 中：&lt;/p&gt;&lt;pre&gt;plugins&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;&amp;#39;com.google.dagger.hilt.android&amp;#39;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;id&amp;nbsp;&amp;#39;kotlin-kapt&amp;#39;
}

dependencies&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation&amp;nbsp;&amp;quot;com.google.dagger:hilt-android:2.51.1&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;kapt&amp;nbsp;&amp;quot;com.google.dagger:hilt-android-compiler:2.51.1&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;implementation&amp;nbsp;&amp;quot;androidx.hilt:hilt-navigation-compose:1.2.0&amp;quot;
}&lt;/pre&gt;&lt;p&gt;接着给 Application 类加上 &lt;code&gt;@HiltAndroidApp&lt;/code&gt; 注解，这是 Hilt 的入口，不能省略：&lt;/p&gt;&lt;pre&gt;@HiltAndroidApp
class&amp;nbsp;MyApp&amp;nbsp;:&amp;nbsp;Application()&lt;/pre&gt;&lt;p&gt;别忘了在 &lt;code&gt;AndroidManifest.xml&lt;/code&gt; 中指定 &lt;code&gt;android:name=&amp;quot;.MyApp&amp;quot;&lt;/code&gt;。&lt;/p&gt;&lt;h2&gt;第二步：构建 Network 和 Database 模块&lt;/h2&gt;&lt;p&gt;Hilt 通过 &lt;code&gt;@Module&lt;/code&gt; + &lt;code&gt;@InstallIn&lt;/code&gt; 来声明如何提供依赖。网络层和数据库层通常作用域为整个应用生命周期，使用 &lt;code&gt;SingletonComponent&lt;/code&gt;：&lt;/p&gt;&lt;pre&gt;@Module
@InstallIn(SingletonComponent::class)
object&amp;nbsp;NetworkModule&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideOkHttpClient():&amp;nbsp;OkHttpClient&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;OkHttpClient.Builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addInterceptor(HttpLoggingInterceptor().apply&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;level&amp;nbsp;=&amp;nbsp;HttpLoggingInterceptor.Level.BODY
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.connectTimeout(30,&amp;nbsp;TimeUnit.SECONDS)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideRetrofit(okHttpClient:&amp;nbsp;OkHttpClient):&amp;nbsp;Retrofit&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Retrofit.Builder()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.baseUrl(&amp;quot;https://api.example.com/&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.client(okHttpClient)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.addConverterFactory(GsonConverterFactory.create())
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;.build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideUserApi(retrofit:&amp;nbsp;Retrofit):&amp;nbsp;UserApi&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;retrofit.create(UserApi::class.java)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;数据库模块同理：&lt;/p&gt;&lt;pre&gt;@Module
@InstallIn(SingletonComponent::class)
object&amp;nbsp;DatabaseModule&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideDatabase(@ApplicationContext&amp;nbsp;context:&amp;nbsp;Context):&amp;nbsp;AppDatabase&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;Room.databaseBuilder(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;context,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AppDatabase::class.java,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;app_database&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;).build()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideUserDao(database:&amp;nbsp;AppDatabase):&amp;nbsp;UserDao&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;database.userDao()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;踩坑提示&lt;/strong&gt;：&lt;code&gt;UserDao&lt;/code&gt; 不加 &lt;code&gt;@Singleton&lt;/code&gt;，因为 Room 会自己管理 DAO 的生命周期，重复包装反而可能引发问题。&lt;/p&gt;&lt;h2&gt;第三步：注入 Repository 和 ViewModel&lt;/h2&gt;&lt;p&gt;Repository 层负责整合网络和本地数据源，使用构造函数注入：&lt;/p&gt;&lt;pre&gt;class&amp;nbsp;UserRepository&amp;nbsp;@Inject&amp;nbsp;constructor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;userApi:&amp;nbsp;UserApi,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;userDao:&amp;nbsp;UserDao
)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;suspend&amp;nbsp;fun&amp;nbsp;login(username:&amp;nbsp;String,&amp;nbsp;password:&amp;nbsp;String):&amp;nbsp;Result&amp;lt;User&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;user&amp;nbsp;=&amp;nbsp;userApi.login(LoginRequest(username,&amp;nbsp;password))
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;userDao.insertUser(user)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Result.success(user)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(e:&amp;nbsp;Exception)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Result.failure(e)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;getCachedUser():&amp;nbsp;Flow&amp;lt;User?&amp;gt;&amp;nbsp;=&amp;nbsp;userDao.getUser()
}&lt;/pre&gt;&lt;p&gt;ViewModel 中使用 &lt;code&gt;@HiltViewModel&lt;/code&gt; + &lt;code&gt;@Inject constructor&lt;/code&gt;，无需手动创建 ViewModelFactory：&lt;/p&gt;&lt;pre&gt;@HiltViewModel
class&amp;nbsp;LoginViewModel&amp;nbsp;@Inject&amp;nbsp;constructor(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;userRepository:&amp;nbsp;UserRepository
)&amp;nbsp;:&amp;nbsp;ViewModel()&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;_loginState&amp;nbsp;=&amp;nbsp;MutableStateFlow&amp;lt;LoginState&amp;gt;(LoginState.Idle)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;loginState:&amp;nbsp;StateFlow&amp;lt;LoginState&amp;gt;&amp;nbsp;=&amp;nbsp;_loginState.asStateFlow()

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;login(username:&amp;nbsp;String,&amp;nbsp;password:&amp;nbsp;String)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModelScope.launch&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_loginState.value&amp;nbsp;=&amp;nbsp;LoginState.Loading
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;result&amp;nbsp;=&amp;nbsp;userRepository.login(username,&amp;nbsp;password)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;_loginState.value&amp;nbsp;=&amp;nbsp;if&amp;nbsp;(result.isSuccess)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LoginState.Success(result.getOrNull()!!)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}&amp;nbsp;else&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;LoginState.Error(result.exceptionOrNull()?.message&amp;nbsp;?:&amp;nbsp;&amp;quot;登录失败&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;在 Activity/Fragment 中获取 ViewModel 只需：&lt;/p&gt;&lt;pre&gt;@AndroidEntryPoint
class&amp;nbsp;LoginActivity&amp;nbsp;:&amp;nbsp;AppCompatActivity()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;private&amp;nbsp;val&amp;nbsp;viewModel:&amp;nbsp;LoginViewModel&amp;nbsp;by&amp;nbsp;viewModels()
}&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;注意&lt;/strong&gt; Activity 必须加 &lt;code&gt;@AndroidEntryPoint&lt;/code&gt;，否则 Hilt 无法注入。&lt;/p&gt;&lt;h2&gt;第四步：单元测试中替换依赖&lt;/h2&gt;&lt;p&gt;Hilt 对测试的支持非常完善，通过 &lt;code&gt;@TestInstallIn&lt;/code&gt; 可以替换正式模块：&lt;/p&gt;&lt;pre&gt;@TestInstallIn(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;components&amp;nbsp;=&amp;nbsp;[SingletonComponent::class],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;replaces&amp;nbsp;=&amp;nbsp;[NetworkModule::class]
)
@Module
object&amp;nbsp;FakeNetworkModule&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Provides
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Singleton
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;provideUserApi():&amp;nbsp;UserApi&amp;nbsp;=&amp;nbsp;FakeUserApi()
}&lt;/pre&gt;&lt;p&gt;测试类：&lt;/p&gt;&lt;pre&gt;@HiltAndroidTest
class&amp;nbsp;LoginViewModelTest&amp;nbsp;{

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@get:Rule
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;hiltRule&amp;nbsp;=&amp;nbsp;HiltAndroidRule(this)

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Inject
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;lateinit&amp;nbsp;var&amp;nbsp;userRepository:&amp;nbsp;UserRepository

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Before
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;init()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;hiltRule.inject()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;@Test
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;fun&amp;nbsp;testLoginSuccess()&amp;nbsp;=&amp;nbsp;runTest&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;viewModel&amp;nbsp;=&amp;nbsp;LoginViewModel(userRepository)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;viewModel.login(&amp;quot;test_user&amp;quot;,&amp;nbsp;&amp;quot;password123&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;assertTrue(viewModel.loginState.value&amp;nbsp;is&amp;nbsp;LoginState.Success)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;常见问题与最佳实践&lt;/h2&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;作用域选择&lt;/strong&gt;：Activity 内的依赖用 &lt;code&gt;ActivityComponent&lt;/code&gt;，避免 &lt;code&gt;Singleton&lt;/code&gt; 持有 Activity 引用造成泄漏&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;@Qualifier 区分同类型&lt;/strong&gt;：若有多个 OkHttpClient，用 &lt;code&gt;@Named&lt;/code&gt; 或自定义 Qualifier 区分&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;懒加载&lt;/strong&gt;：用 &lt;code&gt;Lazy&amp;lt;T&amp;gt;&lt;/code&gt; 包装不需要立即初始化的依赖，减少启动耗时&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;多模块项目&lt;/strong&gt;：每个 feature 模块可以有自己的 &lt;code&gt;@InstallIn(ActivityComponent::class)&lt;/code&gt; 模块，Hilt 会自动合并&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;kapt 编译慢？&lt;/strong&gt;：升级到 KSP 可以显著提升编译速度，Hilt 已支持 KSP&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;掌握这套模式后，你会发现代码可测试性、可维护性都有质的提升。依赖注入不只是框架的使用，更是一种面向接口编程的思维方式。&lt;/p&gt;</description><pubDate>Mon, 20 Apr 2026 10:39:38 +0800</pubDate></item><item><title>MCP 工具开发实战：10步给 AI 接入任意外部 API</title><link>https://blog.resmic.cn/post/239.html</link><description>&lt;h2&gt;什么是 MCP？&lt;/h2&gt;&lt;p&gt;2024 年底，Anthropic 开源了 &lt;strong&gt;Model Context Protocol（MCP）&lt;/strong&gt;——一个让 AI 大模型能够直接调用外部工具、读取数据源的开放协议。它解决了一个核心痛点：&lt;strong&gt;如何让 AI 和你的真实世界连通？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;比较一下两种方式：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;之前：Function Calling&lt;/strong&gt; —— 每个模型各定一套，接口不通，维护成本高&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;现在：MCP&lt;/strong&gt; —— 标准协议，写一次 Server，所有支持 MCP 的 AI 都能用&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;MCP 的核心优势：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;跨模型通用：Clauде、GPT-4、Gemini 共用一套工具&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;进程隔离：Server 独立运行，崩溃不影响主程序&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;双向通信：Server 可以主动向 AI 推送消息&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;生态成熟：GitHub、Slack、PostgreSQL 都已有官方 MCP Server&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;本文目标：&lt;strong&gt;10 步完成一个真实可用的 MCP Server，接入天气 API，在 Claude Desktop 里直接用自然语言查询天气。&lt;/strong&gt;&lt;/p&gt;&lt;h2&gt;环境准备（Step 1-2）&lt;/h2&gt;&lt;h3&gt;Step 1：确认运行环境&lt;/h3&gt;&lt;pre&gt;#&amp;nbsp;需要&amp;nbsp;Node.js&amp;nbsp;18+&amp;nbsp;或&amp;nbsp;Python&amp;nbsp;3.10+
node&amp;nbsp;-v&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;v18.0.0&amp;nbsp;或以上
python3&amp;nbsp;--version&amp;nbsp;&amp;nbsp;#&amp;nbsp;3.10+&lt;/pre&gt;&lt;p&gt;本文选用 TypeScript + Node.js（官方 SDK 支持最完善）。&lt;/p&gt;&lt;h3&gt;Step 2：创建项目&lt;/h3&gt;&lt;pre&gt;mkdir&amp;nbsp;weather-mcp-server
cd&amp;nbsp;weather-mcp-server
npm&amp;nbsp;init&amp;nbsp;-y
npm&amp;nbsp;install&amp;nbsp;@modelcontextprotocol/sdk&amp;nbsp;axios
npm&amp;nbsp;install&amp;nbsp;-D&amp;nbsp;typescript&amp;nbsp;@types/node&amp;nbsp;ts-node
npx&amp;nbsp;tsc&amp;nbsp;--init&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;tsconfig.json&lt;/code&gt;：&lt;/p&gt;&lt;pre&gt;{
&amp;nbsp;&amp;nbsp;&amp;quot;compilerOptions&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;target&amp;quot;:&amp;nbsp;&amp;quot;ES2022&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;module&amp;quot;:&amp;nbsp;&amp;quot;node16&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;moduleResolution&amp;quot;:&amp;nbsp;&amp;quot;node16&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;outDir&amp;quot;:&amp;nbsp;&amp;quot;./dist&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;strict&amp;quot;:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;申请天气 API Key（Step 3）&lt;/h2&gt;&lt;h3&gt;Step 3：获取 OpenWeatherMap API Key&lt;/h3&gt;&lt;ol class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;登录 &lt;a href=&quot;https://openweathermap.org/api&quot; rel=&quot;noopener&quot;&gt;openweathermap.org&lt;/a&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;免费注册账号 → 进入 My API keys 页面&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;复制默认生成的 Key（或点 Generate New Key）&lt;/p&gt;&lt;/li&gt;&lt;/ol&gt;&lt;p&gt;免费额度：60 次/分钟，个人使用完全够用。&lt;/p&gt;&lt;h2&gt;编写核心代码（Step 4-6）&lt;/h2&gt;&lt;h3&gt;Step 4：创建 Server 入口文件&lt;/h3&gt;&lt;p&gt;新建 &lt;code&gt;src/index.ts&lt;/code&gt;：&lt;/p&gt;&lt;pre&gt;import&amp;nbsp;{&amp;nbsp;Server&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;quot;@modelcontextprotocol/sdk/server/index.js&amp;quot;;
import&amp;nbsp;{&amp;nbsp;StdioServerTransport&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;quot;@modelcontextprotocol/sdk/server/stdio.js&amp;quot;;
import&amp;nbsp;{
&amp;nbsp;&amp;nbsp;CallToolRequestSchema,
&amp;nbsp;&amp;nbsp;ListToolsRequestSchema,
}&amp;nbsp;from&amp;nbsp;&amp;quot;@modelcontextprotocol/sdk/types.js&amp;quot;;
import&amp;nbsp;axios&amp;nbsp;from&amp;nbsp;&amp;quot;axios&amp;quot;;

const&amp;nbsp;server&amp;nbsp;=&amp;nbsp;new&amp;nbsp;Server(
&amp;nbsp;&amp;nbsp;{&amp;nbsp;name:&amp;nbsp;&amp;quot;weather-mcp-server&amp;quot;,&amp;nbsp;version:&amp;nbsp;&amp;quot;1.0.0&amp;quot;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;{&amp;nbsp;capabilities:&amp;nbsp;{&amp;nbsp;tools:&amp;nbsp;{}&amp;nbsp;}&amp;nbsp;}
);&lt;/pre&gt;&lt;h3&gt;Step 5：注册工具列表&lt;/h3&gt;&lt;pre&gt;server.setRequestHandler(ListToolsRequestSchema,&amp;nbsp;async&amp;nbsp;()&amp;nbsp;=&amp;gt;&amp;nbsp;({
&amp;nbsp;&amp;nbsp;tools:&amp;nbsp;[
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;name:&amp;nbsp;&amp;quot;get_weather&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;关键：description&amp;nbsp;要写清楚触发条件，AI&amp;nbsp;才知道何时调用它
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description:
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;当用户询问某个城市的天气、气温、湿度、风力时调用。返回实时天气数据。&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;inputSchema:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;&amp;quot;object&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;properties:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;city:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;&amp;quot;string&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;description:&amp;nbsp;&amp;quot;城市名称，支持中文或英文，如：北京、Shanghai、广州&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;required:&amp;nbsp;[&amp;quot;city&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;],
}));&lt;/pre&gt;&lt;h3&gt;Step 6：实现工具逻辑&lt;/h3&gt;&lt;pre&gt;server.setRequestHandler(CallToolRequestSchema,&amp;nbsp;async&amp;nbsp;(request)&amp;nbsp;=&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;if&amp;nbsp;(request.params.name&amp;nbsp;!==&amp;nbsp;&amp;quot;get_weather&amp;quot;)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;Error(&amp;quot;未知工具&amp;quot;);
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;const&amp;nbsp;city&amp;nbsp;=&amp;nbsp;request.params.arguments?.city&amp;nbsp;as&amp;nbsp;string;
&amp;nbsp;&amp;nbsp;const&amp;nbsp;apiKey&amp;nbsp;=&amp;nbsp;process.env.WEATHER_API_KEY;

&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!apiKey)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;[{&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,&amp;nbsp;text:&amp;nbsp;&amp;quot;错误：WEATHER_API_KEY&amp;nbsp;未设置&amp;quot;&amp;nbsp;}],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isError:&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;}

&amp;nbsp;&amp;nbsp;try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;{&amp;nbsp;data&amp;nbsp;}&amp;nbsp;=&amp;nbsp;await&amp;nbsp;axios.get(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;https://api.openweathermap.org/data/2.5/weather&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;params:&amp;nbsp;{&amp;nbsp;q:&amp;nbsp;city,&amp;nbsp;appid:&amp;nbsp;apiKey,&amp;nbsp;units:&amp;nbsp;&amp;quot;metric&amp;quot;,&amp;nbsp;lang:&amp;nbsp;&amp;quot;zh_cn&amp;quot;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;timeout:&amp;nbsp;5000,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;result&amp;nbsp;=&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;城市:&amp;nbsp;data.name,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;天气:&amp;nbsp;data.weather[0].description,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;温度:&amp;nbsp;`${data.main.temp}°C`,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;湿度:&amp;nbsp;`${data.main.humidity}%`,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;风速:&amp;nbsp;`${data.wind.speed}&amp;nbsp;m/s`,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;气压:&amp;nbsp;`${data.main.pressure}&amp;nbsp;hPa`,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};

&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;[{&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,&amp;nbsp;text:&amp;nbsp;JSON.stringify(result,&amp;nbsp;null,&amp;nbsp;2)&amp;nbsp;}],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;}&amp;nbsp;catch&amp;nbsp;(err:&amp;nbsp;any)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;msg&amp;nbsp;=&amp;nbsp;err.response?.status&amp;nbsp;===&amp;nbsp;404
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;?&amp;nbsp;`找不到城市:&amp;nbsp;${city}`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;:&amp;nbsp;`查询失败:&amp;nbsp;${err.message}`;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{&amp;nbsp;content:&amp;nbsp;[{&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,&amp;nbsp;text:&amp;nbsp;msg&amp;nbsp;}],&amp;nbsp;isError:&amp;nbsp;true&amp;nbsp;};
&amp;nbsp;&amp;nbsp;}
});

async&amp;nbsp;function&amp;nbsp;main()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;const&amp;nbsp;transport&amp;nbsp;=&amp;nbsp;new&amp;nbsp;StdioServerTransport();
&amp;nbsp;&amp;nbsp;await&amp;nbsp;server.connect(transport);
&amp;nbsp;&amp;nbsp;console.error(&amp;quot;Weather&amp;nbsp;MCP&amp;nbsp;Server&amp;nbsp;已启动&amp;quot;);
}

main().catch(console.error);&lt;/pre&gt;&lt;h2&gt;编译与本地测试（Step 7）&lt;/h2&gt;&lt;h3&gt;Step 7：编译并运行&lt;/h3&gt;&lt;pre&gt;npx&amp;nbsp;tsc
WEATHER_API_KEY=your_api_key&amp;nbsp;node&amp;nbsp;dist/index.js&lt;/pre&gt;&lt;p&gt;启动成功应输出：&lt;code&gt;Weather MCP Server 已启动&lt;/code&gt;（输到 stderr）&lt;/p&gt;&lt;p&gt;验证工具注册（新开一个终端）：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;发送一个&amp;nbsp;ListTools&amp;nbsp;请求
echo&amp;nbsp;&amp;#39;{&amp;quot;jsonrpc&amp;quot;:&amp;quot;2.0&amp;quot;,&amp;quot;id&amp;quot;:1,&amp;quot;method&amp;quot;:&amp;quot;tools/list&amp;quot;}&amp;#39;&amp;nbsp;|&amp;nbsp;WEATHER_API_KEY=xxx&amp;nbsp;node&amp;nbsp;dist/index.js&lt;/pre&gt;&lt;h2&gt;接入 Claude Desktop（Step 8-9）&lt;/h2&gt;&lt;h3&gt;Step 8：找到配置文件&lt;/h3&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;macOS&lt;/strong&gt;：&lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Windows&lt;/strong&gt;：&lt;code&gt;%APPDATA%Claudeclaude_desktop_config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h3&gt;Step 9：添加 Server 配置&lt;/h3&gt;&lt;pre&gt;{
&amp;nbsp;&amp;nbsp;&amp;quot;mcpServers&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;weather&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;command&amp;quot;:&amp;nbsp;&amp;quot;node&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;args&amp;quot;:&amp;nbsp;[&amp;quot;/绝对路径/weather-mcp-server/dist/index.js&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;env&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;WEATHER_API_KEY&amp;quot;:&amp;nbsp;&amp;quot;你的_API_KEY&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;保存后 &lt;strong&gt;重启 Claude Desktop&lt;/strong&gt;，在输入框左下角能看到工具图标，点开可看到 &lt;code&gt;get_weather&lt;/code&gt; 已注册。&lt;/p&gt;&lt;p&gt;现在对 Claude 说“北京今天天气怎么样？”，它会自动调用你写的 MCP Server。&lt;/p&gt;&lt;h2&gt;验证与排错（Step 10）&lt;/h2&gt;&lt;h3&gt;Step 10：常见问题排查&lt;/h3&gt;&lt;p&gt;&lt;strong&gt;工具图标不出现&lt;/strong&gt;：配置文件路径错误，或 JSON 格式有问题。用 JSON 校验器检查。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;Server 启动报错但 Claude 无提示&lt;/strong&gt;：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;单独运行查报错
node&amp;nbsp;dist/index.js

#&amp;nbsp;查看&amp;nbsp;Claude&amp;nbsp;日志
#&amp;nbsp;macOS
tail&amp;nbsp;-f&amp;nbsp;~/Library/Logs/Claude/main.log&lt;/pre&gt;&lt;p&gt;&lt;strong&gt;AI 不调用工具&lt;/strong&gt;：这通常是 &lt;code&gt;description&lt;/code&gt; 写得不够清楚。要明确写出触发条件，比如“当用户询问某城市天气时调用”。&lt;/p&gt;&lt;p&gt;&lt;strong&gt;返回内容太长导致对话卡顿&lt;/strong&gt;：MCP 返回内容会进入上下文。只返回关键字段，不要把整个原始 API 响应招过来。&lt;/p&gt;&lt;h2&gt;进阶扩展&lt;/h2&gt;&lt;p&gt;掌握基础后，MCP 还支持更多能力：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Resources（资源）&lt;/strong&gt;：让 AI 读取文件、数据库，适合大量数据场景&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Prompts（模板）&lt;/strong&gt;：预置常用 Prompt，用户可直接调用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Sampling（采样）&lt;/strong&gt;：Server 主动让 AI 生成内容（高级用法）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;建议从一个你日常需要的 API 开始实践：公司内部系统、个人数据库、家庭自动化接口……把它接入 AI，你会发现工作效率就是不一样。&lt;/p&gt;</description><pubDate>Mon, 20 Apr 2026 10:00:02 +0800</pubDate></item><item><title>MCP 工具开发实战：从零给 AI 接入任意 API 的完整指南</title><link>https://blog.resmic.cn/post/237.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260418080756177647087680288.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;h2&gt;什么是 MCP？为什么它能让 AI 接入任意 API&lt;/h2&gt;&lt;p&gt;MCP（Model Context Protocol）是 Anthropic 于 2024 年底开源的一套标准化协议，专门用于解决一个老大难问题：&lt;strong&gt;如何让 AI 助手安全、可复用地调用外部工具与 API？&lt;/strong&gt;&lt;/p&gt;&lt;p&gt;在 MCP 出现之前，每个 AI 应用都要自己实现工具调用逻辑，格式五花八门，维护成本极高。MCP 相当于给 AI 工具调用定义了一套&amp;quot;USB 接口标准&amp;quot;——只要遵循协议，任何工具都能即插即用。&lt;/p&gt;&lt;p&gt;目前已有大量开源社区 MCP Server 覆盖常见场景：浏览器自动化、数据库查询、GitHub 操作、Slack 消息、Google Drive 等。本文将带你从零实现一个能查询天气的 MCP Server，并与 Claude Desktop 集成。&lt;/p&gt;&lt;h2&gt;环境准备与项目初始化&lt;/h2&gt;&lt;p&gt;本教程使用 TypeScript + Node.js 实现 MCP Server。首先确认环境：&lt;/p&gt;&lt;pre&gt;node&amp;nbsp;-v&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;需要&amp;nbsp;v18+
npm&amp;nbsp;-v&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;#&amp;nbsp;需要&amp;nbsp;v9+&lt;/pre&gt;&lt;p&gt;创建项目并安装依赖：&lt;/p&gt;&lt;pre&gt;mkdir&amp;nbsp;my-weather-mcp&amp;nbsp;&amp;amp;&amp;amp;&amp;nbsp;cd&amp;nbsp;my-weather-mcp
npm&amp;nbsp;init&amp;nbsp;-y
npm&amp;nbsp;install&amp;nbsp;@modelcontextprotocol/sdk&amp;nbsp;zod
npm&amp;nbsp;install&amp;nbsp;-D&amp;nbsp;typescript&amp;nbsp;@types/node&amp;nbsp;ts-node
npx&amp;nbsp;tsc&amp;nbsp;--init&lt;/pre&gt;&lt;p&gt;修改 &lt;code&gt;tsconfig.json&lt;/code&gt;，确保以下配置：&lt;/p&gt;&lt;pre&gt;{
&amp;nbsp;&amp;nbsp;&amp;quot;compilerOptions&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;target&amp;quot;:&amp;nbsp;&amp;quot;ES2020&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;module&amp;quot;:&amp;nbsp;&amp;quot;commonjs&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;outDir&amp;quot;:&amp;nbsp;&amp;quot;./dist&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;strict&amp;quot;:&amp;nbsp;true,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;esModuleInterop&amp;quot;:&amp;nbsp;true
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;h2&gt;实现核心：编写 MCP Server&lt;/h2&gt;&lt;p&gt;创建 &lt;code&gt;src/index.ts&lt;/code&gt;，这是整个 Server 的入口：&lt;/p&gt;&lt;pre&gt;import&amp;nbsp;{&amp;nbsp;McpServer&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;quot;@modelcontextprotocol/sdk/server/mcp.js&amp;quot;;
import&amp;nbsp;{&amp;nbsp;StdioServerTransport&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;quot;@modelcontextprotocol/sdk/server/stdio.js&amp;quot;;
import&amp;nbsp;{&amp;nbsp;z&amp;nbsp;}&amp;nbsp;from&amp;nbsp;&amp;quot;zod&amp;quot;;

const&amp;nbsp;server&amp;nbsp;=&amp;nbsp;new&amp;nbsp;McpServer({
&amp;nbsp;&amp;nbsp;name:&amp;nbsp;&amp;quot;weather-server&amp;quot;,
&amp;nbsp;&amp;nbsp;version:&amp;nbsp;&amp;quot;1.0.0&amp;quot;,
});

//&amp;nbsp;定义工具：查询天气
server.tool(
&amp;nbsp;&amp;nbsp;&amp;quot;get_weather&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;查询指定城市的当前天气&amp;quot;,
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;city:&amp;nbsp;z.string().describe(&amp;quot;城市名称，例如：Beijing、Shanghai&amp;quot;),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;units:&amp;nbsp;z.enum([&amp;quot;metric&amp;quot;,&amp;nbsp;&amp;quot;imperial&amp;quot;]).default(&amp;quot;metric&amp;quot;).describe(&amp;quot;温度单位：metric=摄氏度，imperial=华氏度&amp;quot;),
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;async&amp;nbsp;({&amp;nbsp;city,&amp;nbsp;units&amp;nbsp;})&amp;nbsp;=&amp;gt;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;调用&amp;nbsp;Open-Meteo&amp;nbsp;免费&amp;nbsp;API（无需&amp;nbsp;API&amp;nbsp;Key）
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;geocodeRes&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;`https://geocoding-api.open-meteo.com/v1/search?name=${encodeURIComponent(city)}&amp;amp;count=1&amp;amp;language=zh&amp;amp;format=json`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;geocodeData&amp;nbsp;=&amp;nbsp;await&amp;nbsp;geocodeRes.json()&amp;nbsp;as&amp;nbsp;any;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!geocodeData.results?.length)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{&amp;nbsp;content:&amp;nbsp;[{&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,&amp;nbsp;text:&amp;nbsp;`找不到城市：${city}`&amp;nbsp;}]&amp;nbsp;};
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;{&amp;nbsp;latitude,&amp;nbsp;longitude,&amp;nbsp;name,&amp;nbsp;country&amp;nbsp;}&amp;nbsp;=&amp;nbsp;geocodeData.results[0];
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;tempUnit&amp;nbsp;=&amp;nbsp;units&amp;nbsp;===&amp;nbsp;&amp;quot;metric&amp;quot;&amp;nbsp;?&amp;nbsp;&amp;quot;celsius&amp;quot;&amp;nbsp;:&amp;nbsp;&amp;quot;fahrenheit&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;weatherRes&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;`https://api.open-meteo.com/v1/forecast?latitude=${latitude}&amp;amp;longitude=${longitude}&amp;amp;current=temperature_2m,relative_humidity_2m,wind_speed_10m,weather_code&amp;amp;temperature_unit=${tempUnit}`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;);
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;weatherData&amp;nbsp;=&amp;nbsp;await&amp;nbsp;weatherRes.json()&amp;nbsp;as&amp;nbsp;any;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;current&amp;nbsp;=&amp;nbsp;weatherData.current;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;const&amp;nbsp;tempSymbol&amp;nbsp;=&amp;nbsp;units&amp;nbsp;===&amp;nbsp;&amp;quot;metric&amp;quot;&amp;nbsp;?&amp;nbsp;&amp;quot;°C&amp;quot;&amp;nbsp;:&amp;nbsp;&amp;quot;°F&amp;quot;;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;[{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text:&amp;nbsp;`${name},&amp;nbsp;${country}&amp;nbsp;当前天气：\n温度：${current.temperature_2m}${tempSymbol}\n湿度：${current.relative_humidity_2m}%\n风速：${current.wind_speed_10m}&amp;nbsp;km/h`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}]
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;};
&amp;nbsp;&amp;nbsp;}
);

//&amp;nbsp;定义资源：暴露支持的城市列表
server.resource(
&amp;nbsp;&amp;nbsp;&amp;quot;supported-cities&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;weather://supported-cities&amp;quot;,
&amp;nbsp;&amp;nbsp;async&amp;nbsp;()&amp;nbsp;=&amp;gt;&amp;nbsp;({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contents:&amp;nbsp;[{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;uri:&amp;nbsp;&amp;quot;weather://supported-cities&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text:&amp;nbsp;&amp;quot;支持全球任意城市，使用英文或拼音输入城市名即可。常用：Beijing,&amp;nbsp;Shanghai,&amp;nbsp;Shenzhen,&amp;nbsp;Guangzhou,&amp;nbsp;Chengdu&amp;quot;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}]
&amp;nbsp;&amp;nbsp;})
);

//&amp;nbsp;启动&amp;nbsp;Server（Stdio&amp;nbsp;模式，供&amp;nbsp;Claude&amp;nbsp;Desktop&amp;nbsp;调用）
async&amp;nbsp;function&amp;nbsp;main()&amp;nbsp;{
&amp;nbsp;&amp;nbsp;const&amp;nbsp;transport&amp;nbsp;=&amp;nbsp;new&amp;nbsp;StdioServerTransport();
&amp;nbsp;&amp;nbsp;await&amp;nbsp;server.connect(transport);
&amp;nbsp;&amp;nbsp;console.error(&amp;quot;Weather&amp;nbsp;MCP&amp;nbsp;Server&amp;nbsp;已启动&amp;quot;);
}

main().catch(console.error);&lt;/pre&gt;&lt;p&gt;注意几个关键点：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;McpServer&lt;/code&gt; 是 SDK 提供的高层封装，负责处理所有 MCP 协议细节&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;server.tool()&lt;/code&gt; 第三个参数用 &lt;code&gt;zod&lt;/code&gt; 定义参数 schema，AI 会根据这个生成调用参数&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;code&gt;StdioServerTransport&lt;/code&gt; 表示通过标准输入输出通信，是 Claude Desktop 默认的连接方式&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;我们使用了 Open-Meteo 的完全免费 API，无需注册，无需 API Key，适合学习使用&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;h2&gt;编译与本地测试&lt;/h2&gt;&lt;p&gt;编译 TypeScript：&lt;/p&gt;&lt;pre&gt;npx&amp;nbsp;tsc&lt;/pre&gt;&lt;p&gt;用 MCP Inspector 快速测试（官方调试工具）：&lt;/p&gt;&lt;pre&gt;npx&amp;nbsp;@modelcontextprotocol/inspector&amp;nbsp;node&amp;nbsp;dist/index.js&lt;/pre&gt;&lt;p&gt;浏览器打开 &lt;code&gt;http://localhost:5173&lt;/code&gt;，在 Inspector 界面中点击 &amp;quot;Connect&amp;quot; 连接到你的 Server，然后找到 &lt;code&gt;get_weather&lt;/code&gt; 工具，输入 &lt;code&gt;{&amp;quot;city&amp;quot;: &amp;quot;Shanghai&amp;quot;}&lt;/code&gt; 测试：&lt;/p&gt;&lt;pre&gt;#&amp;nbsp;预期输出（成功）：
Shanghai,&amp;nbsp;China&amp;nbsp;当前天气：
温度：22.5°C
湿度：68%
风速：12.3&amp;nbsp;km/h&lt;/pre&gt;&lt;p&gt;如果看到这样的输出，说明 MCP Server 工作正常！&lt;/p&gt;&lt;h2&gt;接入 Claude Desktop&lt;/h2&gt;&lt;p&gt;找到 Claude Desktop 的配置文件并编辑：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;macOS：&lt;code&gt;~/Library/Application Support/Claude/claude_desktop_config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;Windows：&lt;code&gt;%APPDATA%\Claude\claude_desktop_config.json&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;pre&gt;{
&amp;nbsp;&amp;nbsp;&amp;quot;mcpServers&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;weather&amp;quot;:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;command&amp;quot;:&amp;nbsp;&amp;quot;node&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;args&amp;quot;:&amp;nbsp;[&amp;quot;/绝对路径/my-weather-mcp/dist/index.js&amp;quot;],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;quot;env&amp;quot;:&amp;nbsp;{}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;重启 Claude Desktop，在对话框左下角会看到一个锤子图标（工具列表），点击可见 &lt;code&gt;get_weather&lt;/code&gt; 工具已加载。现在对 Claude 说&amp;quot;帮我查一下深圳今天的天气&amp;quot;，它会自动调用你的 MCP Server！&lt;/p&gt;&lt;h2&gt;进阶：添加 Prompt 模板和错误处理&lt;/h2&gt;&lt;p&gt;MCP 还支持 &lt;code&gt;server.prompt()&lt;/code&gt;，预定义对话模板：&lt;/p&gt;&lt;pre&gt;server.prompt(
&amp;nbsp;&amp;nbsp;&amp;quot;weather-report&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;quot;生成一份完整的天气播报&amp;quot;,
&amp;nbsp;&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;city:&amp;nbsp;z.string(),
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;date:&amp;nbsp;z.string().optional().describe(&amp;quot;日期，默认今天&amp;quot;),
&amp;nbsp;&amp;nbsp;},
&amp;nbsp;&amp;nbsp;({&amp;nbsp;city,&amp;nbsp;date&amp;nbsp;})&amp;nbsp;=&amp;gt;&amp;nbsp;({
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;messages:&amp;nbsp;[{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;role:&amp;nbsp;&amp;quot;user&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;text:&amp;nbsp;`请用专业播音员的口吻，为&amp;nbsp;${city}&amp;nbsp;生成&amp;nbsp;${date&amp;nbsp;??&amp;nbsp;&amp;quot;今天&amp;quot;}&amp;nbsp;的天气播报，包括穿衣建议和出行提示。`
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}]
&amp;nbsp;&amp;nbsp;})
);&lt;/pre&gt;&lt;p&gt;生产环境中，还需要做好错误处理和日志记录：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;所有对外请求都包裹&amp;nbsp;try/catch
try&amp;nbsp;{
&amp;nbsp;&amp;nbsp;const&amp;nbsp;res&amp;nbsp;=&amp;nbsp;await&amp;nbsp;fetch(url);
&amp;nbsp;&amp;nbsp;if&amp;nbsp;(!res.ok)&amp;nbsp;throw&amp;nbsp;new&amp;nbsp;Error(`HTTP&amp;nbsp;${res.status}:&amp;nbsp;${res.statusText}`);
&amp;nbsp;&amp;nbsp;//&amp;nbsp;...
}&amp;nbsp;catch&amp;nbsp;(err)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;return&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;content:&amp;nbsp;[{&amp;nbsp;type:&amp;nbsp;&amp;quot;text&amp;quot;,&amp;nbsp;text:&amp;nbsp;`查询失败：${(err&amp;nbsp;as&amp;nbsp;Error).message}`&amp;nbsp;}],
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;isError:&amp;nbsp;true&amp;nbsp;&amp;nbsp;//&amp;nbsp;标记为错误响应，让&amp;nbsp;AI&amp;nbsp;知道需要重试或提示用户
&amp;nbsp;&amp;nbsp;};
}&lt;/pre&gt;&lt;h2&gt;常见踩坑与解决方案&lt;/h2&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Claude Desktop 看不到工具&lt;/strong&gt;：检查配置文件路径是否用了绝对路径，相对路径无效；确认 JSON 格式正确，可用 &lt;code&gt;node -e &amp;quot;require(&amp;#39;./config.json&amp;#39;)&amp;quot;&lt;/code&gt; 验证&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;Server 启动就崩溃&lt;/strong&gt;：记住不要在 Server 中用 &lt;code&gt;console.log&lt;/code&gt;，Stdio 模式下标准输出是 MCP 协议通道，调试信息必须用 &lt;code&gt;console.error&lt;/code&gt;&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;工具参数识别不准&lt;/strong&gt;：优化 &lt;code&gt;zod&lt;/code&gt; 中的 &lt;code&gt;.describe()&lt;/code&gt; 文字，AI 靠这些描述理解参数含义；参数描述越详细，AI 传值越准确&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;异步工具超时&lt;/strong&gt;：Claude Desktop 默认超时较短，耗时操作建议加进度提示，或将大任务拆分为多个小工具串联调用&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;部署到服务器后无法连接&lt;/strong&gt;：Stdio 模式只支持本地进程通信；需要远程调用则切换为 &lt;code&gt;SSEServerTransport&lt;/code&gt;（HTTP + Server-Sent Events 模式）&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;</description><pubDate>Sat, 18 Apr 2026 08:08:13 +0800</pubDate></item><item><title>Jetpack Compose 性能优化实战：5个高频坑与解决方案</title><link>https://blog.resmic.cn/post/236.html</link><description>&lt;p&gt;&lt;img src=&quot;https://blog.resmic.cn/zb_users/upload/2026/04/20260416095103177630426321103.jpg&quot; alt=&quot;文章封面&quot; style=&quot;width:100%;border-radius:8px;margin-bottom:20px;&quot;/&gt;&lt;/p&gt;&lt;p&gt;Jetpack Compose 已经成为 Android 原生 UI 开发的主流方案，但很多开发者在实际项目落地时都踩过同一批坑：动画卡顿、状态管理混乱、性能优化无从下手。本文基于真实项目经验，整理出 Compose 开发中最高频的性能问题及解决方案，并附上可直接复用的代码片段。&lt;/p&gt;&lt;h2&gt;一、搞清楚 Recomposition 的触发机制&lt;/h2&gt;&lt;p&gt;Compose 性能问题十有八九出在不必要的重组（Recomposition）上。很多开发者误以为只要用了 &lt;code&gt;remember&lt;/code&gt; 就够了，实际上 Compose 的重组粒度取决于 Composable 函数读取了哪些 State。&lt;/p&gt;&lt;p&gt;最常见的反模式是把整个复杂对象作为 State 传入：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;错误：整个&amp;nbsp;UserInfo&amp;nbsp;变化都会触发&amp;nbsp;ProfileCard&amp;nbsp;重组
@Composable
fun&amp;nbsp;ProfileCard(userInfo:&amp;nbsp;UserInfo)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(userInfo.name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(userInfo.email)
}

//&amp;nbsp;✅&amp;nbsp;正确：只传需要的字段，减少重组范围
@Composable
fun&amp;nbsp;ProfileCard(name:&amp;nbsp;String,&amp;nbsp;email:&amp;nbsp;String)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(name)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(email)
}&lt;/pre&gt;&lt;p&gt;另一个高频问题是 Lambda 引用不稳定。每次父组件重组时，传入的 Lambda 都会产生新实例，导致子组件被强制重组：&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;每次重组都创建新&amp;nbsp;Lambda
LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(list)&amp;nbsp;{&amp;nbsp;item&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;ItemCard(item,&amp;nbsp;onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;viewModel.onItemClick(item)&amp;nbsp;})
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;✅&amp;nbsp;用&amp;nbsp;remember&amp;nbsp;缓存&amp;nbsp;Lambda（Kotlin&amp;nbsp;1.9+&amp;nbsp;可用&amp;nbsp;rememberUpdatedState）
val&amp;nbsp;onItemClick&amp;nbsp;=&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;{&amp;nbsp;item:&amp;nbsp;Item&amp;nbsp;-&amp;gt;&amp;nbsp;viewModel.onItemClick(item)&amp;nbsp;}&amp;nbsp;}&lt;/pre&gt;&lt;p&gt;调试工具推荐：在 Android Studio 开启 &lt;strong&gt;Layout Inspector → Recomposition Counts&lt;/strong&gt;，能直观看到每个节点的重组次数，快速定位热点。&lt;/p&gt;&lt;h2&gt;二、LazyColumn 的正确打开方式&lt;/h2&gt;&lt;p&gt;LazyColumn 是 Compose 里最容易出性能问题的组件，核心原则是：&lt;strong&gt;给每个 item 提供稳定唯一的 key&lt;/strong&gt;。&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;❌&amp;nbsp;没有&amp;nbsp;key，列表增删时会全量重组
LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(messageList)&amp;nbsp;{&amp;nbsp;msg&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageItem(msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;✅&amp;nbsp;提供&amp;nbsp;key，Compose&amp;nbsp;可以精准复用已有组件
LazyColumn&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;items&amp;nbsp;=&amp;nbsp;messageList,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;key&amp;nbsp;=&amp;nbsp;{&amp;nbsp;msg&amp;nbsp;-&amp;gt;&amp;nbsp;msg.id&amp;nbsp;}&amp;nbsp;&amp;nbsp;//&amp;nbsp;必须是唯一稳定值
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;)&amp;nbsp;{&amp;nbsp;msg&amp;nbsp;-&amp;gt;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;MessageItem(msg)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;另一个坑点：在 item 内部做复杂计算。LazyColumn 滚动时会频繁调用 item 的 Composable，应把耗时逻辑移到 ViewModel 或用 &lt;code&gt;remember(key)&lt;/code&gt; 缓存：&lt;/p&gt;&lt;pre&gt;@Composable
fun&amp;nbsp;MessageItem(msg:&amp;nbsp;Message)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;❌&amp;nbsp;每次重组都计算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;formattedTime&amp;nbsp;=&amp;nbsp;SimpleDateFormat(&amp;quot;HH:mm&amp;quot;).format(msg.timestamp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;//&amp;nbsp;✅&amp;nbsp;key&amp;nbsp;不变就不重算
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;formattedTime&amp;nbsp;=&amp;nbsp;remember(msg.timestamp)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;SimpleDateFormat(&amp;quot;HH:mm&amp;quot;,&amp;nbsp;Locale.getDefault()).format(msg.timestamp)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(formattedTime)
}&lt;/pre&gt;&lt;p&gt;对于图片加载，务必配合 &lt;code&gt;contentScale&lt;/code&gt; 和固定尺寸，避免 Coil/Glide 加载完成后触发重新布局：&lt;/p&gt;&lt;pre&gt;AsyncImage(
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;model&amp;nbsp;=&amp;nbsp;msg.avatarUrl,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;null,
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;modifier&amp;nbsp;=&amp;nbsp;Modifier.size(40.dp),&amp;nbsp;&amp;nbsp;//&amp;nbsp;固定尺寸！
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;contentScale&amp;nbsp;=&amp;nbsp;ContentScale.Crop
)&lt;/pre&gt;&lt;h2&gt;三、状态提升与 ViewModel 的配合&lt;/h2&gt;&lt;p&gt;Compose 推崇&amp;quot;状态提升（State Hoisting）&amp;quot;，但很多人做过头了——把所有状态都塞进 ViewModel，导致 UI 层完全依赖 ViewModel 才能预览，测试也变得复杂。&lt;/p&gt;&lt;p&gt;实用原则：&lt;strong&gt;局部 UI 状态留在 Composable，业务状态交给 ViewModel&lt;/strong&gt;。&lt;/p&gt;&lt;pre&gt;//&amp;nbsp;局部&amp;nbsp;UI&amp;nbsp;状态（展开/折叠）不需要进&amp;nbsp;ViewModel
@Composable
fun&amp;nbsp;ExpandableCard(title:&amp;nbsp;String,&amp;nbsp;content:&amp;nbsp;String)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;var&amp;nbsp;expanded&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{&amp;nbsp;mutableStateOf(false)&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Card(onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;expanded&amp;nbsp;=&amp;nbsp;!expanded&amp;nbsp;})&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Column&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(title)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;AnimatedVisibility(visible&amp;nbsp;=&amp;nbsp;expanded)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Text(content)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}

//&amp;nbsp;业务数据从&amp;nbsp;ViewModel&amp;nbsp;的&amp;nbsp;StateFlow&amp;nbsp;收集
@Composable
fun&amp;nbsp;ArticleScreen(viewModel:&amp;nbsp;ArticleViewModel&amp;nbsp;=&amp;nbsp;hiltViewModel())&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;val&amp;nbsp;uiState&amp;nbsp;by&amp;nbsp;viewModel.uiState.collectAsStateWithLifecycle()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;when&amp;nbsp;(uiState)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;Loading&amp;nbsp;-&amp;gt;&amp;nbsp;LoadingIndicator()
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;Success&amp;nbsp;-&amp;gt;&amp;nbsp;ArticleList((uiState&amp;nbsp;as&amp;nbsp;Success).articles)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;is&amp;nbsp;Error&amp;nbsp;-&amp;gt;&amp;nbsp;ErrorView((uiState&amp;nbsp;as&amp;nbsp;Error).message)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;注意：使用 &lt;code&gt;collectAsStateWithLifecycle()&lt;/code&gt; 而非 &lt;code&gt;collectAsState()&lt;/code&gt;，前者在 Activity 进入后台时会自动停止收集，节省资源。&lt;/p&gt;&lt;h2&gt;四、动画性能：用 derivedStateOf 避免过度重组&lt;/h2&gt;&lt;p&gt;滚动联动动画是 Compose 的高频需求，但如果直接监听 &lt;code&gt;lazyListState.firstVisibleItemScrollOffset&lt;/code&gt;，会在每一帧都触发重组：&lt;/p&gt;&lt;pre&gt;val&amp;nbsp;listState&amp;nbsp;=&amp;nbsp;rememberLazyListState()

//&amp;nbsp;❌&amp;nbsp;每像素都重组
val&amp;nbsp;showFab&amp;nbsp;=&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;&amp;gt;&amp;nbsp;0

//&amp;nbsp;✅&amp;nbsp;只有结果变化时才重组
val&amp;nbsp;showFab&amp;nbsp;by&amp;nbsp;remember&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;derivedStateOf&amp;nbsp;{&amp;nbsp;listState.firstVisibleItemIndex&amp;nbsp;&amp;gt;&amp;nbsp;0&amp;nbsp;}
}

AnimatedVisibility(visible&amp;nbsp;=&amp;nbsp;showFab)&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;FloatingActionButton(onClick&amp;nbsp;=&amp;nbsp;{&amp;nbsp;/*&amp;nbsp;scroll&amp;nbsp;to&amp;nbsp;top&amp;nbsp;*/&amp;nbsp;})&amp;nbsp;{
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;Icon(Icons.Default.ArrowUpward,&amp;nbsp;contentDescription&amp;nbsp;=&amp;nbsp;&amp;quot;回到顶部&amp;quot;)
&amp;nbsp;&amp;nbsp;&amp;nbsp;&amp;nbsp;}
}&lt;/pre&gt;&lt;p&gt;&lt;code&gt;derivedStateOf&lt;/code&gt; 的核心价值：将高频 State 变化转换为低频结果变化，是处理滚动、拖拽等连续手势的必备工具。&lt;/p&gt;&lt;h2&gt;五、实战踩坑记录&lt;/h2&gt;&lt;p&gt;以下是真实项目中遇到的几个典型问题：&lt;/p&gt;&lt;ul class=&quot; list-paddingleft-2&quot;&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;坑1：Dialog 内容闪烁&lt;/strong&gt;。根因是 Dialog 的内容 Composable 和触发状态在同一层，点击按钮时状态先变 false 再销毁，导致闪一帧空内容。解决方案：用 &lt;code&gt;AnimatedVisibility&lt;/code&gt; 替代直接条件渲染，或用 &lt;code&gt;DialogProperties(dismissOnBackPress = true)&lt;/code&gt; 配合独立的 showDialog 状态管理。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;坑2：TextField 输入卡顿&lt;/strong&gt;。在低端机上 TextField 每次输入都卡，排查发现是 onValueChange 里做了字符串格式化操作（手机号分段显示），格式化逻辑本身是 O(n) 但触发了整个父组件重组。解决：把格式化逻辑移入 &lt;code&gt;remember(value) { ... }&lt;/code&gt;，并把 TextField 封装成独立组件隔离重组影响。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;坑3：BottomSheet 内 LazyColumn 高度异常&lt;/strong&gt;。BottomSheet 默认有高度约束，内部 LazyColumn 需要加 &lt;code&gt;.weight(1f)&lt;/code&gt; 或指定 &lt;code&gt;fillMaxHeight(0.9f)&lt;/code&gt;，否则在某些机型上会只显示一行。&lt;/p&gt;&lt;/li&gt;&lt;li&gt;&lt;p&gt;&lt;strong&gt;坑4：Dark Mode 切换时动画丢失&lt;/strong&gt;。系统深色模式切换会重建 Activity（除非在 Manifest 配置了 &lt;code&gt;uiMode&lt;/code&gt;），导致 Compose 状态丢失。解决：在 AndroidManifest 的 Activity 节点加 &lt;code&gt;android:configChanges=&amp;quot;uiMode&amp;quot;&lt;/code&gt; 并在 ViewModel 里持久化关键状态。&lt;/p&gt;&lt;/li&gt;&lt;/ul&gt;&lt;p&gt;Jetpack Compose 的学习曲线主要在于理解其响应式模型和重组机制，一旦建立起正确的心智模型，开发效率会有质的提升。建议结合 Android Studio 的 Profiler 和 Layout Inspector 实际测量，而不是凭感觉优化。&lt;/p&gt;</description><pubDate>Thu, 16 Apr 2026 09:51:25 +0800</pubDate></item></channel></rss>