MoreRSS

site iconDroidyue | 技术小黑屋修改

89年出生的保定人在北京,Android 工程师, Infoq译者。
请复制 RSS 到你的阅读器,或快速订阅到 :

Inoreader Feedly Follow Feedbin Local Reader

Droidyue | 技术小黑屋的 RSS 预览

一看就会,为 AI 编程 Agent 撸一个 MCP 服务

2025-10-15 14:30:00

通过 MCP (Model Context Protocol) 让 AI 助手直接调用 ADB 命令操作 Android 设备,实现日志查看、应用安装、性能分析等自动化操作。

MCP 协议说明

MCP 是 Anthropic 推出的开放协议,用于连接 AI 助手与外部工具。MCP Server 将特定工具包装成标准化接口,让 AI 能够理解和调用。

架构如下:

1
Claude Desktop/API → MCP Server → ADB Commands → Android Device

实现步骤

初始化项目

1
2
3
4
mkdir adb_mcp
cd adb_mcp
npm init -y
npm install @modelcontextprotocol/sdk

配置 package.json

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
{
  "name": "adb-mcp-server",
  "version": "1.0.0",
  "description": "MCP Server for Android Debug Bridge (ADB) operations",
  "main": "adb-mcp-server.js",
  "bin": {
    "adb-mcp-server": "./adb-mcp-server.js"
  },
  "scripts": {
    "start": "node adb-mcp-server.js"
  },
  "keywords": ["mcp", "adb", "android", "debug"],
  "author": "",
  "license": "MIT",
  "dependencies": {
    "@modelcontextprotocol/sdk": "^1.0.0"
  }
}

核心代码实现

创建 adb-mcp-server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
#!/usr/bin/env node
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { StdioServerTransport } = require('@modelcontextprotocol/sdk/server/stdio.js');
const { CallToolRequestSchema, ListToolsRequestSchema } = require('@modelcontextprotocol/sdk/types.js');
const { exec } = require('child_process');
const { promisify } = require('util');

const execPromise = promisify(exec);

// 创建 MCP Server 实例
const server = new Server(
  {
    name: 'adb-server',
    version: '1.0.0'
  },
  {
    capabilities: {
      tools: {}
    }
  }
);

// 定义工具列表
const tools = [
  {
    name: 'adb-logcat',
    description: 'Get Android device logs',
    inputSchema: {
      type: 'object',
      properties: {
        lines: {
          type: 'number',
          description: 'Number of log lines to retrieve',
          default: 100
        }
      }
    }
  },
  {
    name: 'adb-devices',
    description: 'List connected Android devices',
    inputSchema: {
      type: 'object',
      properties: {}
    }
  },
  {
    name: 'adb-install',
    description: 'Install APK on connected device',
    inputSchema: {
      type: 'object',
      properties: {
        apkPath: {
          type: 'string',
          description: 'Path to the APK file to install'
        }
      },
      required: ['apkPath']
    }
  },
  {
    name: 'adb-app-info',
    description: 'Get app package information',
    inputSchema: {
      type: 'object',
      properties: {
        packageName: {
          type: 'string',
          description: 'Android package name (e.g., com.example.app)'
        }
      },
      required: ['packageName']
    }
  },
  {
    name: 'adb-meminfo',
    description: 'Get app memory usage information',
    inputSchema: {
      type: 'object',
      properties: {
        packageName: {
          type: 'string',
          description: 'Android package name (e.g., com.example.app)'
        }
      },
      required: ['packageName']
    }
  },
  {
    name: 'adb-clear-data',
    description: 'Clear app data and cache',
    inputSchema: {
      type: 'object',
      properties: {
        packageName: {
          type: 'string',
          description: 'Android package name (e.g., com.example.app)'
        }
      },
      required: ['packageName']
    }
  }
];

// 处理工具列表请求
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools };
});

// 处理工具执行请求
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;

  try {
    let result;

    switch (name) {
      case 'adb-logcat': {
        const lines = args.lines || 100;
        const { stdout } = await execPromise(`adb logcat -d -t ${lines}`);
        result = stdout;
        break;
      }

      case 'adb-devices': {
        const { stdout } = await execPromise('adb devices -l');
        result = stdout;
        break;
      }

      case 'adb-install': {
        if (!args.apkPath) {
          throw new Error('apkPath is required');
        }
        const { stdout } = await execPromise(`adb install -r "${args.apkPath}"`);
        result = stdout;
        break;
      }

      case 'adb-app-info': {
        if (!args.packageName) {
          throw new Error('packageName is required');
        }
        const { stdout } = await execPromise(`adb shell dumpsys package ${args.packageName}`);
        result = stdout;
        break;
      }

      case 'adb-meminfo': {
        if (!args.packageName) {
          throw new Error('packageName is required');
        }
        const { stdout } = await execPromise(`adb shell dumpsys meminfo ${args.packageName}`);
        result = stdout;
        break;
      }

      case 'adb-clear-data': {
        if (!args.packageName) {
          throw new Error('packageName is required');
        }
        const { stdout } = await execPromise(`adb shell pm clear ${args.packageName}`);
        result = stdout;
        break;
      }

      default:
        throw new Error(`Unknown tool: ${name}`);
    }

    return {
      content: [
        {
          type: 'text',
          text: result
        }
      ]
    };
  } catch (error) {
    return {
      content: [
        {
          type: 'text',
          text: `Error executing ${name}: ${error.message}`
        }
      ],
      isError: true
    };
  }
});

// 启动服务器
async function main() {
  const transport = new StdioServerTransport();
  await server.connect(transport);
  console.error('ADB MCP Server running on stdio');
}

main().catch((error) => {
  console.error('Server error:', error);
  process.exit(1);
});

赋予执行权限:

1
chmod +x adb-mcp-server.js

关键实现说明

Server 初始化

1
2
3
4
const server = new Server(
  { name: 'adb-server', version: '1.0.0' },
  { capabilities: { tools: {} } }
);

声明服务器名称、版本和支持的能力类型。

工具定义

每个工具包含三个部分:

  • name: 唯一标识符
  • description: 功能描述,AI 根据此判断调用时机
  • inputSchema: JSON Schema 格式的参数定义

示例:

1
2
3
4
5
6
7
8
9
10
11
{
  name: 'adb-install',
  description: 'Install APK on connected device',
  inputSchema: {
    type: 'object',
    properties: {
      apkPath: { type: 'string', description: 'Path to the APK file' }
    },
    required: ['apkPath']
  }
}

请求处理

MCP 定义两种请求类型:

ListToolsRequest - 列出所有可用工具:

1
2
3
server.setRequestHandler(ListToolsRequestSchema, async () => {
  return { tools };
});

CallToolRequest - 执行具体工具:

1
2
3
4
server.setRequestHandler(CallToolRequestSchema, async (request) => {
  const { name, arguments: args } = request.params;
  // 根据 name 执行相应 ADB 命令
});

命令执行

使用 child_process 执行 ADB 命令:

1
const { stdout } = await execPromise(`adb devices -l`);

配置方式

Claude Desktop 配置

编辑 ~/Library/Application Support/Claude/claude_desktop_config.json

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "adb": {
      "command": "node",
      "args": ["/Users/你的用户名/Documents/self_host/adb_mcp/adb-mcp-server.js"]
    }
  }
}

配置完成后重启 Claude Desktop。

Claude Code CLI 配置

使用 Claude Code 命令行工具添加 MCP Server:

1
claude -p mcp add adb node /Users/xxxx/Documents/sxxx/adb_mcp/adb-mcp-server.js

参数说明:

  • adb: MCP Server 名称
  • node: 运行命令
  • 最后是 adb-mcp-server.js 的完整路径

Gemini CLI 配置

编辑 Gemini CLI 配置文件 ~/.gemini/mcp_config.json

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "adb": {
      "command": "node",
      "args": ["/Users/xxxx/Documents/sxxx/adb_mcp/adb-mcp-server.js"]
    }
  }
}

:Gemini CLI 的 MCP 配置可能因版本而异,建议以官方文档为准。

Copilot CLI 配置

编辑 Copilot CLI 配置文件 ~/.github-copilot/mcp_servers.json

1
2
3
4
5
6
{
  "adb": {
    "command": "node",
    "args": ["/Users/xxxx/Documents/sxxx/adb_mcp/adb-mcp-server.js"]
  }
}

:Copilot CLI 的 MCP 配置可能因版本而异,建议以官方文档为准。


使用示例

在 Claude 中直接使用自然语言:

1
查看连接的 Android 设备
1
获取最近 50  logcat 日志
1
查看 com.android.chrome 的内存使用情况
1
清除 com.example.app 的数据

Claude 会自动调用对应的 MCP 工具执行操作。


应用场景

日志分析

传统方式:

1
2
adb logcat -d > log.txt
# 手动搜索错误信息

使用 MCP:

1
获取最近的 logcat 日志,找出所有 ERROR 级别信息

Claude 自动执行并分析结果。

性能监控

传统方式:

1
2
adb shell dumpsys meminfo com.example.app
# 手动分析输出数据

使用 MCP:

1
查看 com.example.app 的内存使用,分析是否有内存泄漏

批量操作

1
列出所有连接的设备,在每个设备上安装 /path/to/app.apk

Claude 自动处理设备列表和批量安装。


扩展功能

添加截图

1
2
3
4
5
6
7
8
9
10
11
{
  name: 'adb-screenshot',
  description: 'Take a screenshot',
  inputSchema: {
    type: 'object',
    properties: {
      savePath: { type: 'string', description: 'Path to save screenshot' }
    },
    required: ['savePath']
  }
}

执行命令:

1
await execPromise(`adb exec-out screencap -p > ${args.savePath}`);

添加性能监控

1
2
3
4
5
{
  name: 'adb-top',
  description: 'Get CPU usage',
  inputSchema: { type: 'object', properties: {} }
}

添加文件传输

1
2
3
4
5
6
7
8
9
10
11
12
{
  name: 'adb-push',
  description: 'Push file to device',
  inputSchema: {
    type: 'object',
    properties: {
      localPath: { type: 'string' },
      remotePath: { type: 'string' }
    },
    required: ['localPath', 'remotePath']
  }
}

注意事项

权限检查

确保 ADB 已添加到系统 PATH:

1
2
which adb
adb version

设备授权

使用前确认设备已连接并授权:

1
adb devices

如果显示 unauthorized,需在设备上确认 USB 调试授权。

错误处理

生产环境应添加:

  • 详细日志记录
  • 设备断开连接处理
  • 命令超时机制

安全性

  • 避免在不信任的环境使用
  • 注意 APK 路径注入风险
  • 考虑添加命令白名单

参考资源



Vibe Coding 最佳实践:10 个让开发效率提升 10 倍的关键技巧

2025-10-15 14:30:00

本文整理 Vibe Coding(AI 辅助编程)的 10 个核心最佳实践,帮助提升开发效率和代码质量。

1. 根据任务选择模型

Claude Haiku 4.5:速度最快、成本最低($0.25/MTok 输入,$1.25/MTok 输出),适合简单快速任务(代码格式化、简单 bug 修复、基础代码补全、文档注释生成)。响应速度快,适合高频调用场景。

Claude Sonnet 4.5:日常开发首选,用于 70-80% 编程任务(代码生成、重构、测试),性价比最高($3/MTok 输入,$15/MTok 输出)。

Claude Opus 4.1:复杂推理任务(多步骤工作流、架构决策),价格是 Sonnet 5 倍,支持 7 小时以上自主编程。

OpenAI Codex / GPT-4:擅长代码补全和快速生成,GitHub Copilot 基于此技术。适合 IDE 内实时代码提示、函数级补全、单元测试生成。

决策框架

  • 简单快速任务 → Claude Haiku 4.5
  • 实时代码补全 → Codex
  • 日常代码生成、重构 → Claude Sonnet 4.5
  • 复杂架构设计、多文件重构 → Claude Opus 4.1

2. 使用四要素 Prompt 框架

核心框架

  • 上下文/角色:设定专业背景(”你是精通 Kotlin 协程的 Android 性能优化专家”)
  • 指令:清晰的单一任务命令(”重构此 ViewModel 以减少数据库查询次数”)
  • 内容:使用代码块标记实际代码
  • 格式:明确输出结构(”提供重构后的代码并添加注释解释变更”)

实用模式

  • 逐步说明:”创建 Kotlin 函数:1. 接收用户 ID 列表;2. 从 Room 获取数据;3. 过滤活跃用户;4. 返回 Flow 并处理异常”
  • 示例驱动:”参考以下 ViewModel 写法,转换这个 Activity”
  • 角色扮演:”作为 Android 安全专家,审查此登录代码,重点关注 SharedPreferences 加密、网络请求安全、WebView 配置”

避免

  • 模糊请求(”让这个更好”)
  • 一次多个无关任务
  • 缺少错误堆栈的调试请求

3. 善用代码上下文和文件引用

核心问题:AI 不了解项目结构,生成代码可能与项目风格不一致。

提供上下文的方法

引用文件

1
请参考 UserRepository.kt 的写法,为 ProductRepository 实现相同的缓存逻辑

粘贴关键代码

1
2
3
4
5
// 现有的 BaseViewModel 实现
abstract class BaseViewModel : ViewModel() {
    protected val _loading = MutableStateFlow(false)
}
// 请按此模式实现 UserViewModel

说明架构

1
2
3
4
5
项目使用 MVVM 架构:
- data/ 层:Repository + DataSource
- domain/ 层:UseCase + Model  
- presentation/ 层:ViewModel + Activity/Fragment
使用 HiltRoomRetrofit

使用 @文件 语法

1
2
@MainActivity.kt 这个 Activity 的内存泄漏在哪里?
@app/build.gradle 添加 Coil 图片加载库

首次对话说明技术栈和架构,涉及多文件时列出文件名,生成代码时提供参考示例。


4. 针对复杂问题开启扩展思考模式

适用场景:复杂算法(图片压缩、列表优化)、不明确原因的 bug(ANR、内存泄漏)、架构决策(MVVM vs MVI)、跨文件重构、性能优化。

不推荐:简单补全、语法修复、基本 CRUD、简单 UI 布局。

Claude Code 魔法词

  • think:4,000 token
  • think hard / megathink:10,000 token
  • think harder / ultrathink:31,999 token

性能提升:SWE-bench Verified 从 62.3% 提升至 70.3%,数学问题达 96.2%。

从最小预算(1,024 token)开始,根据问题复杂度逐步增加。


5. 采用小步迭代开发

黄金法则:一次生成太多代码会导致混乱和 bug,使用最小有意义增量。

增量流程:定义最小增量 → 编写失败测试 → AI 编写通过测试的代码 → 立即运行测试 → 失败则让 AI 诊断修复 → 重复。

架构先行:编码前先绘制模块图(Repository-ViewModel-View)、定义数据流(LiveData/StateFlow)、确定组件职责。

多轮精炼:第一轮生成基本结构 → 第二轮添加错误处理 → 第三轮优化性能 → 第四轮添加完整文档。每一轮都小而专注、可测试。


6. 审查代码并建立测试防线

核心原则:永远不要盲目接受 AI 输出。GitClear 研究发现粗心使用 AI 导致 bug 增加 41%。

重点审查:潜在 bug、安全漏洞(未加密 SharedPreferences、不安全 WebView、Intent 劫持)、性能瓶颈(主线程阻塞、内存泄漏、过度绘制)、缺失错误处理、生命周期管理问题。

测试生成:使用 AI 生成单元测试和 UI 测试,但必须人工验证测试是否真实有效、边界情况有意义(空列表、网络错误、权限拒绝)、使用合适框架(JUnit、Mockito、Espresso、Robolectric)。

覆盖率目标:ViewModel/Repository 层、复杂业务逻辑、数据转换工具类追求 80%+ 代码覆盖率。


7. 与版本控制系统深度集成

核心原则:Git 是安全网,每个 AI 生成的代码都必须提交,便于快速回退错误修改。

快速回退命令

1
2
3
4
git checkout .              # 放弃所有修改
git checkout -- file.kt     # 放弃指定文件修改
git reset HEAD <file>       # 回退暂存区
git reset --hard HEAD^      # 完全回退到上一次提交

原子提交:每次提交一个逻辑变更,使用祈使语气,消息正文解释原因。

分支管理:使用清晰命名(feat/add-retrofit-apifix/memory-leak-viewmodel)、所有 AI 实验都使用分支、出问题直接删除分支。


8. 建立项目规则和指南文件

规则文件:创建 .editorconfigdetekt.ymldocs/coding-standards.md,定义编码规范(优先使用 Kotlin、Activity/Fragment 最大 500 行、使用 ViewBinding)、测试要求(80% 覆盖率、JUnit 和 Espresso、ViewModel 必须有单元测试)、文档标准(public 方法使用 KDoc、每个模块需 README)、安全策略(永不硬编码 API 密钥、使用 EncryptedSharedPreferences)。

Android 常用配置

  • .editorconfig - 统一代码格式
  • detekt.yml - Kotlin 代码质量检查
  • lint.xml - Android Lint 配置
  • docs/android-conventions.md - 团队开发规范

配置方法:Cursor IDE 在设置中添加用户规则,Claude Projects 在自定义指令中添加规则。

核心收益:AI 自动遵循项目规范,团队代码生成一致,保持质量和可维护性。


9. 及时会话管理

三种策略

清理会话(Clear):任务切换或 AI 出现混乱时使用 /clear 或 Cmd/Ctrl + Shift + N。

新建会话(New Chat):开始完全不同的任务时,避免上下文混淆。

压缩会话(Compact):长会话超过 50 轮对话时使用 /compact,保留关键信息,释放上下文空间。

时长建议

  • 短任务:15-30 分钟,10-20 轮
  • 中等任务:1-2 小时,30-50 轮
  • 复杂项目:单次不超过 3-4 小时

超过 50-70 轮对话后性能明显下降,及时管理会话。


10. 使用 MCP 扩展 AI 能力

什么是 MCP:Model Context Protocol 让 AI 直接执行 ADB 命令、查询设备状态,无需手动复制日志。

Android ADB MCP Server

创建 adb-mcp-server.js

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
#!/usr/bin/env node
const { Server } = require('@modelcontextprotocol/sdk/server/index.js');
const { exec } = require('child_process');
const { promisify } = require('util');
const execPromise = promisify(exec);

const server = new Server({ name: 'adb-server', version: '1.0.0' });

server.tool('adb-logcat', 'Get device logs', async ({ lines = 100 }) => {
  const { stdout } = await execPromise(`adb logcat -d -t ${lines}`);
  return { content: stdout };
});

server.tool('adb-meminfo', 'Get memory usage', async ({ packageName }) => {
  const { stdout } = await execPromise(`adb shell dumpsys meminfo ${packageName}`);
  return { content: stdout };
});

server.tool('adb-install', 'Install APK', async ({ apkPath }) => {
  const { stdout } = await execPromise(`adb install -r ${apkPath}`);
  return { content: stdout };
});

server.start();

配置 Claude Desktop~/Library/Application Support/Claude/claude_desktop_config.json):

1
2
3
4
5
6
7
8
{
  "mcpServers": {
    "adb": {
      "command": "node",
      "args": ["/path/to/adb-mcp-server.js"]
    }
  }
}

实战场景

分析崩溃

1
2
3
用户:应用启动时崩溃
AI:[调用 adb-logcat] 发现 NullPointerException in MainActivity:45
     user 对象为 null,建议使用 safe call (?.)

性能排查

1
2
3
用户:列表滑动很卡
AI:[调用 adb-meminfo] 内存 450MB,ImageLoader 持有 Activity 引用
     建议使用 WeakReference  Glide 生命周期管理

配置建议

安全限制:添加包名白名单,避免误操作。 错误处理:捕获异常,提示检查 USB 调试。 性能优化:限制 logcat 行数(默认 100),使用 -d 参数。


总结

掌握这十个最佳实践后,原本需要 3 天完成的功能模块可以压缩到半小时。关键在于将 AI 视为超级助手而非普通工具,快速试错、频繁提交、大胆回退,让看似不可能的开发效率成为现实。



同样是 Sonnet 4.5,为何 CLI 工具差距这么大

2025-10-13 08:00:00

最近使用 Claude Code CLI 和 GitHub Copilot CLI 时发现,虽然两者都使用 Claude Sonnet 4.5 模型,但 Claude Code 明显更智能。本文记录性能差异的技术原因。

核心问题

同模型不等于同性能。Claude Sonnet 4.5 原生支持 200K tokens 上下文和 Extended Thinking,但 Copilot CLI 通过中间层大幅限制了这些能力。


Copilot CLI 的三大限制

1. 上下文窗口严重缩水

Claude Sonnet 4.5 原生能力

  • 标准:200K tokens
  • 长上下文版本:1M tokens

Copilot CLI 实际限制

  • 8K tokens 上下文窗口
  • 官方未明确公布,但用户实测约为此值

实际影响

1
2
3
4
5
6
7
8
9
10
11
# 场景:分析涉及 10 个文件的代码库

# Claude Code CLI (200K 上下文)
# ✓ 可同时加载多个相关文件
# ✓ 保持完整的代码关系理解
# ✓ 前后一致的分析结果

# Copilot CLI (8K 上下文)
# ✗ 只能保持 1-2 个文件
# ✗ 频繁遗忘先前分析内容
# ✗ 需要反复重新读取

8K tokens 约等于 6000 英文单词1500 行代码。Copilot CLI 的小窗口导致频繁的上下文切换和信息丢失。

2. Extended Thinking 功能完全缺失

什么是 Extended Thinking

允许模型进行深度推理,配置 1K-64K tokens 的”思考预算”,在复杂任务中显著提升表现。

Claude Code CLI 配置示例

1
2
3
4
5
6
7
{
  "model": "claude-sonnet-4-5-20250929",
  "thinking": {
    "type": "enabled",
    "budget_tokens": 10000
  }
}

Copilot CLI

完全不支持此配置,无法启用 Extended Thinking。这是两者智能表现差异的关键原因。

3. 资源配额与超时策略

Claude Code CLI

  • 按 token 计费($3/百万输入,$15/百万输出)
  • 允许长时间运行
  • 支持检查点功能,可保存进度

Copilot CLI

  • Premium Request 配额制(Pro 300 次/月)
  • 隐性”思考预算”限制
  • 超时中断机制

比喻

Claude Code 设计用于 跑马拉松(长时间、多步骤任务),Copilot CLI 只能 跑百米(快速交互)。


架构差异

Claude Code CLI:直接访问

1
用户  Anthropic API  Claude Sonnet 4.5

特点

  • 完整的 200K tokens 上下文
  • 支持 Extended Thinking
  • 完全参数控制
  • 并行工具调用
  • 无功能限制

代价

  • 使用 grep-only 检索(无语义索引)
  • 大量顺序工具调用
  • 速度慢 4-5 倍

Copilot CLI:中间层架构

1
用户  GitHub 编排层  Anthropic API  Claude Sonnet 4.5

中间层作用

  • 模型路由和切换
  • 成本控制和配额管理
  • 上下文窗口限制(8K)
  • 屏蔽高级功能(Extended Thinking)
  • GitHub 生态集成

优点

  • 多模型选择
  • GitHub 深度集成
  • 相对稳定

代价

  • 模型能力被”阉割”
  • 上下文限制严重
  • 复杂任务表现差

实测性能对比

速度差异

重构 React 前端任务(约 15 个文件):

  • Claude Code CLI: 18 分 20 秒
  • Claude Chat 手动: 4 分 30 秒
  • Copilot CLI: 约 90 分钟(18 分 × 5)

用户反馈:Copilot CLI 比 Claude Code 慢 5 倍以上

大文件处理

Claude Code 限制

1
2
# 单文件读取限制 25K tokens
Error: File content (28375 tokens) exceeds maximum allowed tokens (25000)

需要使用 offset 和 limit 分块读取,导致反复工具调用。

Copilot CLI 问题

  • 8K tokens 窗口导致频繁分块
  • 1000 行文件经常卡顿

  • 有时卡死 30 分钟后超时

为什么 Claude Code “更智能”

1. 全局视野 vs 局部视野

Claude Code

  • 200K tokens 上下文窗口
  • 可同时保持多个文件内容
  • 理解代码全局关系

Copilot CLI

  • 8K tokens 上下文窗口
  • 只能保持少量文件
  • 频繁遗忘先前内容

2. 深度思考 vs 快速响应

Claude Code

  • Extended Thinking 允许模型”想”得更久
  • 可以进行复杂推理
  • 适合多步骤任务

Copilot CLI

  • 无 Extended Thinking
  • 思考预算受限
  • 倾向快速给出结果

3. 马拉松 vs 百米

Claude Code

  • 设计用于长时间运行
  • 可处理复杂、多步骤任务
  • 允许大量 token 消耗

Copilot CLI

  • 为快速交互优化
  • 超出一定时限就中断
  • 控制成本和资源

稳定性问题

Copilot CLI

GitHub 社区反馈:

  • Claude Sonnet 4 在约 5 个提示后停止
  • 频繁 “I’m sorry but there was an error”
  • 上下文突然丢失

官方承认是”已知的服务器端问题”。

Claude Code

  • 使用 31% 配额时被提前限制
  • 陷入”无限压缩循环”
  • 读取大文件时崩溃

优化建议

Claude Code

使用语义索引

1
2
# 安装 MCP 服务器(如 Serena MCP)
# 替代低效 grep 检索

主动管理上下文

1
2
/clear   # 清理上下文
/compact # 压缩上下文

维护 CLAUDE.md

  • 项目规范
  • 禁止目录
  • 常用命令

Copilot CLI

监控配额

1
/usage  # 查看使用情况

按任务选模型

  • 复杂任务:Claude Sonnet 4.5
  • 简单任务:Haiku

避免大任务接近限制时启动


总结

两者差异的根本原因:

维度 Claude Code CLI Copilot CLI
上下文窗口 200K tokens ~8K tokens
Extended Thinking ✓ 支持 ✗ 不支持
资源策略 马拉松 百米
架构 直接访问 中间层限制
适用场景 复杂重构 快速迭代

Claude Code 提供完整模型能力但速度慢,像让模型”看全局、想得久”。

Copilot CLI 功能受限但集成好,像让模型”看局部、快速答”。

用户反馈的”8K tokens 限制”并非误解,而是 Copilot CLI 的真实约束。这个限制加上 Extended Thinking 缺失,是智能表现差异的核心原因。

实际使用中,许多开发者两者并用:Claude Code 处理复杂任务,Copilot CLI 处理快速交互。



定位 Android 权限声明来源

2025-10-12 08:00:00

开发中经常需要排查某个权限是由哪个依赖库引入的,本文记录通过 Gradle daemon 日志快速定位权限来源的方法。

查询方法

使用以下命令在 Gradle daemon 日志中搜索权限声明:

1
grep -n -C 2 "android.permission.INTERNET" --include="*.out.log" -R ~/.gradle/daemon/ .

参数说明

  • -n:显示行号
  • -C 2:显示匹配行前后各 2 行上下文(关于 grep 上下文参数的详细用法见这篇文章
  • --include="*.out.log":只搜索 .out.log 文件
  • -R:递归搜索目录
  • ~/.gradle/daemon/:Gradle daemon 日志目录

daemon 日志文件说明

什么是 daemon*.out.log

Gradle daemon 是 Gradle 构建系统的后台进程,用于加速构建过程。daemon-*.out.log 文件记录了 daemon 进程的详细输出信息,包括:

  • 依赖解析过程:库的下载、合并信息
  • Manifest 合并日志:权限、组件的合并来源
  • 构建任务执行:编译、打包等任务的详细输出
  • 错误堆栈信息:构建失败时的完整日志

文件位置

1
2
3
4
5
6
7
~/.gradle/daemon/
├── 5.4.1/
│   ├── daemon-77407.out.log
│   └── daemon-77408.out.log
├── 7.0.2/
│   └── daemon-88901.out.log
└── ...

每个 Gradle 版本对应一个目录,每次 daemon 启动会生成新的日志文件,文件名中的数字为进程 ID。


结果分析

查询结果示例

1
2
/daemon/5.4.1/daemon-77407.out.log:132336:Merging uses-permission#android.permission.INTERNET 
with lower [net.butterflytv.utils:rtmp-client:3.0.1] AndroidManifest.xml:11:5-67

信息解读

从日志可以看出:

  • 权限名称android.permission.INTERNET
  • 来源依赖net.butterflytv.utils:rtmp-client:3.0.1
  • 声明位置:该依赖的 AndroidManifest.xml 第 11 行
  • 日志文件daemon-77407.out.log 第 132336 行

处理方式

使用 <uses-permission tools:node="remove"> 在主 Manifest 中显式移除。


注意事项

  • daemon 日志会随着构建次数增多而变大,定期清理 ~/.gradle/daemon/ 目录
  • 不同 Gradle 版本的日志格式可能略有差异
  • 查询结果可能包含多个匹配项,需根据依赖关系判断实际来源

延伸阅读



Android 升级 targetSDK 35 解决 namespace 问题

2025-10-04 10:00:00

升级 Android targetSDK 至 35 并使用 Gradle 8.0+ 后,遇到了第三方库 namespace 配置问题。

错误信息

1
2
3
4
Execution failed for task ':react-native-inappbrowser:processDebugManifest'.
> A failure occurred while executing com.android.build.gradle.tasks.ProcessLibraryManifest$ProcessLibWorkAction
> Setting the namespace via the package attribute in the source AndroidManifest.xml is no longer supported.
  Recommendation: remove package="com.proyecto26.inappbrowser" from the source AndroidManifest.xml.

或者类似错误:

1
2
3
4
5
Namespace not specified. Please specify a namespace in the module's build.gradle file like so:

android {
    namespace 'com.example.namespace'
}

原因分析

Android Gradle Plugin 8.0+ 不再支持在 AndroidManifest.xml 中通过 package 属性设置 namespace,要求在 build.gradle 中显式声明。升级 targetSDK 至 35 需要使用 Gradle 8.0+,但很多第三方库(如 react-native-inappbrowserappcenter-analytics 等)尚未更新配置,导致构建失败。


解决方案

在项目根目录的 android/build.gradle 文件中添加以下代码:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
allprojects {
    repositories {
        google()
        mavenCentral()
        
        // 如果使用 Detox 测试框架,添加此配置
        maven {
            url("$rootDir/../node_modules/detox/Detox-android")
        }
    }
    
    subprojects {
        afterEvaluate { project ->
            if (project.hasProperty('android')) {
                project.android {
                    // 自动设置 namespace
                    if (namespace == null || namespace.isEmpty()) {
                        def defaultNamespace = project.group.toString().replace('.', '_')
                        namespace = defaultNamespace
                    }

                    // 启用 buildConfig
                    buildFeatures {
                        buildConfig = true
                    }
                }

                // 自动修复 namespace 和清理 AndroidManifest.xml
                project.tasks.register("fixManifestsAndNamespace") {
                    doLast {
                        // 1. 从 AndroidManifest.xml 提取 package 并添加到 build.gradle
                        def buildGradleFile = file("${project.projectDir}/build.gradle")
                        if (buildGradleFile.exists()) {
                            def buildGradleContent = buildGradleFile.getText('UTF-8')
                            def manifestFile = file("${project.projectDir}/src/main/AndroidManifest.xml")
                            if (manifestFile.exists()) {
                                def manifestContent = manifestFile.getText('UTF-8')
                                def packageName = manifestContent.find(/package="([^"]+)"/) { match, p -> p }
                                if (packageName && !buildGradleContent.contains("namespace")) {
                                    println "Setting namespace in ${buildGradleFile}"
                                    buildGradleContent = buildGradleContent.replaceFirst(
                                        /android\s*\{/, "android {\n    namespace '${packageName}'"
                                    )
                                    buildGradleFile.write(buildGradleContent, 'UTF-8')
                                }
                            }
                        }

                        // 2. 移除 AndroidManifest.xml 中的 package 属性
                        def manifests = fileTree(dir: project.projectDir, includes: ['**/AndroidManifest.xml'])
                        manifests.each { File manifestFile ->
                            def manifestContent = manifestFile.getText('UTF-8')
                            if (manifestContent.contains('package=')) {
                                println "Removing package attribute from ${manifestFile}"
                                manifestContent = manifestContent.replaceAll(/package="[^"]*"/, '')
                                manifestFile.write(manifestContent, 'UTF-8')
                            }
                        }
                    }
                }

                // 在构建前自动执行修复
                project.tasks.matching { it.name.startsWith("preBuild") }.all {
                    dependsOn project.tasks.named("fixManifestsAndNamespace")
                }
            }
        }
    }
}

说明

工作原理

此方案包含三个层次的处理:

  1. 自动设置 namespace:如果子项目未配置 namespace,自动使用 project.group 并将点号替换为下划线作为 namespace
  2. 启用 buildConfig:自动为所有子项目启用 buildConfig 特性
  3. 自动迁移配置
    • AndroidManifest.xml 中提取 package 属性
    • 将其写入对应的 build.gradle 作为 namespace
    • 移除 AndroidManifest.xml 中的 package 属性

这个 task 在每次构建前(preBuild)自动执行,确保所有第三方库都符合 Gradle 8.0+ 的要求。

适用场景

  • React Native 项目升级 targetSDK 35
  • Flutter 项目升级 targetSDK 35
  • 使用 Detox 测试框架的项目
  • 原生 Android 项目使用旧版第三方库
  • 任何遇到 “namespace not specified” 或 “package attribute not supported” 错误的场景

注意事项

  • 此方案会自动修改第三方库的 build.gradleAndroidManifest.xml 文件
  • 修改仅在 node_modules 中生效,不影响源码仓库
  • 建议在 CI/CD 中首次构建后检查修改是否正确
  • 如果某些库已经声明了 namespace,不会被覆盖

验证

执行以下命令重新构建项目:

1
2
3
cd android
./gradlew clean
./gradlew assembleDebug

或在 React Native 项目中:

1
npx react-native run-android

构建过程中会看到类似输出:

1
2
Setting namespace in /path/to/project/android/react-native-inappbrowser/build.gradle
Removing package attribute from /path/to/project/android/react-native-inappbrowser/src/main/AndroidManifest.xml

与简化方案对比

如果只需要为缺少 namespace 的库自动设置默认值,可以使用简化版:

1
2
3
4
5
6
7
8
9
10
11
subprojects {
    afterEvaluate { project ->
        if (project.hasProperty('android')) {
            project.android {
                if (namespace == null || namespace.isEmpty()) {
                    namespace project.group.toString().replace('.', '_')
                }
            }
        }
    }
}

简化方案不会修改任何文件,仅在内存中设置 namespace,但可能无法解决所有第三方库的问题。


参考



解决 Android Studio 关闭后终端 flutter run 进程自动结束的问题

2025-09-28 08:56:00

在 Flutter 开发过程中,很多开发者遇到一个困扰的问题:当使用终端运行 flutter run 命令进行开发时,一旦关闭 Android Studio 或 IntelliJ IDEA,终端中的 flutter run 进程就会自动结束,导致应用停止运行。本文将详细分析这个问题的原因并提供解决方案。

问题现象

典型场景

  1. 在终端中执行 flutter run 启动 Flutter 应用
  2. 同时打开 Android Studio 进行代码编辑
  3. 关闭 Android Studio 或 IntelliJ IDEA
  4. 终端中的 flutter run 进程自动结束,应用停止运行

影响范围

  • 通过终端启动的 flutter run 进程
  • 相关的热重载功能失效
  • 调试连接中断
  • 需要重新启动应用才能继续开发

问题原因分析

根本原因

当 Android Studio 启动时,它会自动管理 ADB(Android Debug Bridge)服务器的生命周期。默认情况下,IDE 会:

  1. 启动自己的 ADB 服务器实例
  2. 接管现有的调试连接
  3. 在退出时终止所有相关的调试进程

这种设计导致即使是通过终端独立启动的 flutter run 进程,也会因为 ADB 服务器的关闭而被迫结束。

进程依赖关系

1
终端 flutter run → ADB 连接 → Android Studio 管理的 ADB 服务器

当 Android Studio 关闭时,它管理的 ADB 服务器也会关闭,进而导致所有依赖该 ADB 连接的进程(包括终端的 flutter run)都被终止。

解决方案

配置外部 ADB 服务器管理

最有效的解决方案是让 Android Studio 使用外部手动管理的 ADB 服务器,而不是自己管理一个实例:

配置步骤

  1. 打开 Android Studio 设置(Preferences/Settings)
  2. 导航到 Build, Execution, DeploymentDebugger
  3. 找到 Android Debug Bridge (adb) 部分
  4. Adb Server Lifecycle Management 中选择 Use existing manually managed server
  5. 设置 Existing ADB server port5037(默认端口)

android studio adb config

关键配置说明

  • Use existing manually managed server: 告诉 Android Studio 不要自己管理 ADB 服务器,而是使用外部已存在的服务器
  • Existing ADB server port: 指定外部 ADB 服务器的端口(通常为 5037)

这样配置后,Android Studio 不会在启动时接管 ADB 服务器,也不会在关闭时终止它,从而保证终端运行的进程不受影响。

验证配置是否生效

配置完成后,可以通过以下步骤验证:

  1. 在终端启动 flutter run
  2. 打开 Android Studio
  3. 关闭 Android Studio
  4. 检查终端中的 flutter run 是否依然运行

如果 flutter run 进程没有被终止,说明配置成功。

总结

通过配置 Android Studio 使用外部手动管理的 ADB 服务器,可以有效解决 IDE 关闭后终端 flutter run 进程自动结束的问题。这种方法的优势在于:

  1. 进程独立性:终端和 IDE 的调试进程相互独立
  2. 开发效率:无需频繁重启应用
  3. 资源优化:避免不必要的进程重启
  4. 稳定性:减少因 IDE 操作导致的调试中断

推荐所有 Flutter 开发者采用这种配置方式,特别是那些习惯在终端中运行 flutter run 的开发者。