389 lines
10 KiB
Markdown
389 lines
10 KiB
Markdown
|
|
---
|
|||
|
|
name: mobile-expert
|
|||
|
|
description: >
|
|||
|
|
移动端开发专家。当用户需要 React Native、Flutter、Expo 跨平台开发,
|
|||
|
|
iOS Swift/SwiftUI、Android Kotlin/Jetpack Compose 原生开发,
|
|||
|
|
App 性能优化、应用上架 App Store/Google Play,
|
|||
|
|
Android 设备控制、ADB 操作、设备截图、UI 自动化测试、
|
|||
|
|
应用安装/卸载/启动、模拟器操作、手势模拟、屏幕元素检测,
|
|||
|
|
或说 "移动端"、"App开发"、"跨平台"、"ADB"、"设备截图" 时使用此技能。
|
|||
|
|
allowed-tools: Read, Glob, Grep, Edit, Write, Bash, mcp__mobile
|
|||
|
|
maturity: stable
|
|||
|
|
last-reviewed: 2026-03-01
|
|||
|
|
---
|
|||
|
|
|
|||
|
|
# 移动端开发专家 (Mobile Developer Expert)
|
|||
|
|
|
|||
|
|
> **Output Style**: 本技能使用内联输出规范
|
|||
|
|
|
|||
|
|
资深移动端开发工程师,精通 React Native、Flutter 和原生开发。
|
|||
|
|
|
|||
|
|
## 触发关键词
|
|||
|
|
|
|||
|
|
- **跨平台**: `React Native`, `Flutter`, `Expo`, `跨平台`, `Ionic`
|
|||
|
|
- **原生开发**: `iOS`, `Android`, `Swift`, `Kotlin`, `原生开发`
|
|||
|
|
- **应用开发**: `App开发`, `移动端`, `手机应用`, `APP`
|
|||
|
|
- **发布相关**: `应用上架`, `App Store`, `Google Play`, `签名`
|
|||
|
|
- **性能相关**: `移动性能`, `启动优化`, `内存优化`
|
|||
|
|
- **设备控制**: `ADB`, `adb devices`, `设备截图`, `模拟器`, `真机调试`
|
|||
|
|
- **MCP 操作**: `mobile MCP`, `设备列表`, `应用安装`, `UI自动化`, `手势模拟`, `屏幕元素`
|
|||
|
|
|
|||
|
|
> **路由消歧**: Android + ADB/设备截图/模拟器/UI自动化 → mobile-expert; Playwright/Selenium + 测试 → tester-expert; 浏览器自动化 → browser-automation-expert
|
|||
|
|
|
|||
|
|
## 技术栈
|
|||
|
|
|
|||
|
|
### 跨平台框架 (2024-2025)
|
|||
|
|
- **React Native 0.74+**: 新架构 (Fabric/TurboModules)
|
|||
|
|
- **Expo 51**: 托管工作流和开发工具
|
|||
|
|
- **Flutter 3.24+**: Dart 3.5+, Impeller 渲染引擎
|
|||
|
|
|
|||
|
|
### 原生开发
|
|||
|
|
- **iOS**: Swift 5.9+, SwiftUI, UIKit
|
|||
|
|
- **Android**: Kotlin 1.9+, Jetpack Compose
|
|||
|
|
|
|||
|
|
### 状态管理
|
|||
|
|
- **React Native**: Zustand, Jotai, Redux Toolkit
|
|||
|
|
- **Flutter**: Riverpod, Bloc, Provider
|
|||
|
|
|
|||
|
|
## React Native 项目结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
myapp/
|
|||
|
|
├── src/
|
|||
|
|
│ ├── components/ # 通用组件
|
|||
|
|
│ │ ├── ui/
|
|||
|
|
│ │ └── layout/
|
|||
|
|
│ ├── screens/ # 页面
|
|||
|
|
│ ├── navigation/ # 导航配置
|
|||
|
|
│ ├── hooks/ # 自定义 Hooks
|
|||
|
|
│ ├── services/ # API 服务
|
|||
|
|
│ ├── store/ # 状态管理
|
|||
|
|
│ ├── utils/ # 工具函数
|
|||
|
|
│ └── types/ # TypeScript 类型
|
|||
|
|
├── android/
|
|||
|
|
├── ios/
|
|||
|
|
├── App.tsx
|
|||
|
|
└── package.json
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## React Native 组件示例
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// src/components/ui/Button.tsx
|
|||
|
|
import React from 'react';
|
|||
|
|
import { TouchableOpacity, Text, ActivityIndicator } from 'react-native';
|
|||
|
|
|
|||
|
|
interface ButtonProps {
|
|||
|
|
title: string;
|
|||
|
|
onPress: () => void;
|
|||
|
|
variant?: 'primary' | 'secondary' | 'outline';
|
|||
|
|
loading?: boolean;
|
|||
|
|
disabled?: boolean;
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export const Button: React.FC<ButtonProps> = ({
|
|||
|
|
title,
|
|||
|
|
onPress,
|
|||
|
|
variant = 'primary',
|
|||
|
|
loading = false,
|
|||
|
|
disabled = false,
|
|||
|
|
}) => {
|
|||
|
|
const getStyle = () => {
|
|||
|
|
const base = { borderRadius: 8, padding: 12, alignItems: 'center' };
|
|||
|
|
const variants = {
|
|||
|
|
primary: { backgroundColor: '#007AFF' },
|
|||
|
|
secondary: { backgroundColor: '#5856D6' },
|
|||
|
|
outline: { borderWidth: 2, borderColor: '#007AFF' },
|
|||
|
|
};
|
|||
|
|
return { ...base, ...variants[variant] };
|
|||
|
|
};
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<TouchableOpacity
|
|||
|
|
style={getStyle()}
|
|||
|
|
onPress={onPress}
|
|||
|
|
disabled={disabled || loading}
|
|||
|
|
activeOpacity={0.7}
|
|||
|
|
>
|
|||
|
|
{loading ? (
|
|||
|
|
<ActivityIndicator color="#FFF" />
|
|||
|
|
) : (
|
|||
|
|
<Text style={{ color: variant === 'outline' ? '#007AFF' : '#FFF' }}>
|
|||
|
|
{title}
|
|||
|
|
</Text>
|
|||
|
|
)}
|
|||
|
|
</TouchableOpacity>
|
|||
|
|
);
|
|||
|
|
};
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Navigation 配置
|
|||
|
|
|
|||
|
|
```typescript
|
|||
|
|
// src/navigation/RootNavigator.tsx
|
|||
|
|
import { NavigationContainer } from '@react-navigation/native';
|
|||
|
|
import { createNativeStackNavigator } from '@react-navigation/native-stack';
|
|||
|
|
import { createBottomTabNavigator } from '@react-navigation/bottom-tabs';
|
|||
|
|
|
|||
|
|
const Stack = createNativeStackNavigator();
|
|||
|
|
const Tab = createBottomTabNavigator();
|
|||
|
|
|
|||
|
|
function MainTabs() {
|
|||
|
|
return (
|
|||
|
|
<Tab.Navigator screenOptions={{ headerShown: false }}>
|
|||
|
|
<Tab.Screen name="Home" component={HomeScreen} />
|
|||
|
|
<Tab.Screen name="Profile" component={ProfileScreen} />
|
|||
|
|
</Tab.Navigator>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
export function RootNavigator() {
|
|||
|
|
const { user } = useAuth();
|
|||
|
|
|
|||
|
|
return (
|
|||
|
|
<NavigationContainer>
|
|||
|
|
<Stack.Navigator screenOptions={{ headerShown: false }}>
|
|||
|
|
{!user ? (
|
|||
|
|
<Stack.Screen name="Login" component={LoginScreen} />
|
|||
|
|
) : (
|
|||
|
|
<Stack.Screen name="Main" component={MainTabs} />
|
|||
|
|
)}
|
|||
|
|
</Stack.Navigator>
|
|||
|
|
</NavigationContainer>
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Flutter 项目结构
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
myapp/
|
|||
|
|
├── lib/
|
|||
|
|
│ ├── main.dart
|
|||
|
|
│ ├── core/
|
|||
|
|
│ │ ├── theme/
|
|||
|
|
│ │ └── network/
|
|||
|
|
│ ├── features/
|
|||
|
|
│ │ ├── auth/
|
|||
|
|
│ │ ├── home/
|
|||
|
|
│ │ └── profile/
|
|||
|
|
│ └── shared/
|
|||
|
|
│ ├── widgets/
|
|||
|
|
│ └── services/
|
|||
|
|
├── android/
|
|||
|
|
├── ios/
|
|||
|
|
└── pubspec.yaml
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Flutter 组件示例
|
|||
|
|
|
|||
|
|
```dart
|
|||
|
|
// lib/shared/widgets/custom_button.dart
|
|||
|
|
import 'package:flutter/material.dart';
|
|||
|
|
|
|||
|
|
enum ButtonVariant { primary, secondary, outline }
|
|||
|
|
|
|||
|
|
class CustomButton extends StatelessWidget {
|
|||
|
|
final String text;
|
|||
|
|
final VoidCallback? onPressed;
|
|||
|
|
final ButtonVariant variant;
|
|||
|
|
final bool isLoading;
|
|||
|
|
|
|||
|
|
const CustomButton({
|
|||
|
|
Key? key,
|
|||
|
|
required this.text,
|
|||
|
|
this.onPressed,
|
|||
|
|
this.variant = ButtonVariant.primary,
|
|||
|
|
this.isLoading = false,
|
|||
|
|
}) : super(key: key);
|
|||
|
|
|
|||
|
|
@override
|
|||
|
|
Widget build(BuildContext context) {
|
|||
|
|
return ElevatedButton(
|
|||
|
|
onPressed: isLoading ? null : onPressed,
|
|||
|
|
style: ElevatedButton.styleFrom(
|
|||
|
|
backgroundColor: _getBackgroundColor(),
|
|||
|
|
padding: const EdgeInsets.symmetric(horizontal: 24, vertical: 12),
|
|||
|
|
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
|
|||
|
|
),
|
|||
|
|
child: isLoading
|
|||
|
|
? const SizedBox(
|
|||
|
|
width: 20,
|
|||
|
|
height: 20,
|
|||
|
|
child: CircularProgressIndicator(strokeWidth: 2, color: Colors.white),
|
|||
|
|
)
|
|||
|
|
: Text(text, style: TextStyle(fontSize: 16, fontWeight: FontWeight.w600)),
|
|||
|
|
);
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
Color _getBackgroundColor() {
|
|||
|
|
switch (variant) {
|
|||
|
|
case ButtonVariant.primary:
|
|||
|
|
return const Color(0xFF007AFF);
|
|||
|
|
case ButtonVariant.secondary:
|
|||
|
|
return const Color(0xFF5856D6);
|
|||
|
|
case ButtonVariant.outline:
|
|||
|
|
return Colors.transparent;
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
}
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 性能优化
|
|||
|
|
|
|||
|
|
### React Native
|
|||
|
|
```typescript
|
|||
|
|
// 1. 使用 FlatList 而不是 ScrollView + map
|
|||
|
|
<FlatList
|
|||
|
|
data={data}
|
|||
|
|
renderItem={renderItem}
|
|||
|
|
keyExtractor={(item) => item.id}
|
|||
|
|
removeClippedSubviews={true}
|
|||
|
|
maxToRenderPerBatch={10}
|
|||
|
|
windowSize={10}
|
|||
|
|
getItemLayout={(data, index) => ({
|
|||
|
|
length: ITEM_HEIGHT,
|
|||
|
|
offset: ITEM_HEIGHT * index,
|
|||
|
|
index,
|
|||
|
|
})}
|
|||
|
|
/>
|
|||
|
|
|
|||
|
|
// 2. 使用 useCallback 和 useMemo
|
|||
|
|
const handlePress = useCallback(() => {
|
|||
|
|
doSomething(id);
|
|||
|
|
}, [id]);
|
|||
|
|
|
|||
|
|
const sortedData = useMemo(() =>
|
|||
|
|
data.sort((a, b) => a.name.localeCompare(b.name)),
|
|||
|
|
[data]
|
|||
|
|
);
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Flutter
|
|||
|
|
```dart
|
|||
|
|
// 1. 使用 const 构造函数
|
|||
|
|
const Text('Hello');
|
|||
|
|
|
|||
|
|
// 2. 使用 ListView.builder
|
|||
|
|
ListView.builder(
|
|||
|
|
itemCount: items.length,
|
|||
|
|
itemBuilder: (context, index) => ListTile(title: Text(items[index])),
|
|||
|
|
);
|
|||
|
|
|
|||
|
|
// 3. 使用 RepaintBoundary 隔离重绘
|
|||
|
|
RepaintBoundary(child: ExpensiveWidget());
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 应用上架
|
|||
|
|
|
|||
|
|
### iOS App Store
|
|||
|
|
```bash
|
|||
|
|
# 构建归档
|
|||
|
|
xcodebuild -workspace MyApp.xcworkspace \
|
|||
|
|
-scheme MyApp -configuration Release \
|
|||
|
|
-archivePath MyApp.xcarchive archive
|
|||
|
|
|
|||
|
|
# 导出 IPA
|
|||
|
|
xcodebuild -exportArchive \
|
|||
|
|
-archivePath MyApp.xcarchive \
|
|||
|
|
-exportPath ./build \
|
|||
|
|
-exportOptionsPlist ExportOptions.plist
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### Android Google Play
|
|||
|
|
```bash
|
|||
|
|
# 生成签名 AAB (推荐)
|
|||
|
|
cd android
|
|||
|
|
./gradlew bundleRelease
|
|||
|
|
|
|||
|
|
# 输出: android/app/build/outputs/bundle/release/app-release.aab
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## Android MCP 工具 (mcp__mobile)
|
|||
|
|
|
|||
|
|
通过 `@mobilenext/mobile-mcp` 提供 Android 真机/模拟器设备控制能力。
|
|||
|
|
|
|||
|
|
### 工具速查 (5 类 20 个)
|
|||
|
|
|
|||
|
|
```yaml
|
|||
|
|
# 设备管理
|
|||
|
|
mobile_list_available_devices: 列出已连接设备 (ADB)
|
|||
|
|
mobile_get_screen_size: 获取屏幕分辨率
|
|||
|
|
mobile_get_orientation: 获取屏幕方向
|
|||
|
|
mobile_set_orientation: 设置屏幕方向 (portrait/landscape)
|
|||
|
|
|
|||
|
|
# 应用管理
|
|||
|
|
mobile_list_apps: 列出已安装应用
|
|||
|
|
mobile_launch_app: 启动应用 (包名)
|
|||
|
|
mobile_terminate_app: 终止应用
|
|||
|
|
mobile_install_app: 安装 APK
|
|||
|
|
mobile_uninstall_app: 卸载应用
|
|||
|
|
|
|||
|
|
# 屏幕操作
|
|||
|
|
mobile_take_screenshot: 设备截图 (返回 base64)
|
|||
|
|
mobile_save_screenshot: 保存截图到文件
|
|||
|
|
mobile_list_elements_on_screen: 获取屏幕 UI 元素树
|
|||
|
|
|
|||
|
|
# 触摸手势
|
|||
|
|
mobile_click_on_screen_at_coordinates: 点击坐标
|
|||
|
|
mobile_double_tap_on_screen: 双击
|
|||
|
|
mobile_long_press_on_screen_at_coordinates: 长按坐标
|
|||
|
|
mobile_swipe_on_screen: 滑动 (方向/坐标)
|
|||
|
|
|
|||
|
|
# 输入
|
|||
|
|
mobile_type_keys: 输入文本
|
|||
|
|
mobile_press_button: 按下物理/虚拟按键 (home/back/enter)
|
|||
|
|
mobile_open_url: 在设备上打开 URL
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 典型工作流
|
|||
|
|
|
|||
|
|
**工作流 1: 应用安装与验证**
|
|||
|
|
```
|
|||
|
|
mobile_list_available_devices → mobile_install_app(apkPath)
|
|||
|
|
→ mobile_launch_app(bundleId) → mobile_take_screenshot → 验证 UI
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**工作流 2: UI 自动化操作**
|
|||
|
|
```
|
|||
|
|
mobile_launch_app → mobile_list_elements_on_screen → 分析元素坐标
|
|||
|
|
→ mobile_click_on_screen_at_coordinates → mobile_type_keys → mobile_take_screenshot
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
**工作流 3: 多设备对比测试**
|
|||
|
|
```
|
|||
|
|
mobile_list_available_devices → 遍历设备
|
|||
|
|
→ mobile_launch_app → mobile_take_screenshot → 对比截图差异
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
### 框架选择决策树 (含 MCP)
|
|||
|
|
|
|||
|
|
```
|
|||
|
|
需要移动端操作?
|
|||
|
|
├── 需要控制真机/模拟器设备?
|
|||
|
|
│ └── → Android MCP (mcp__mobile)
|
|||
|
|
├── 需要开发跨平台 App?
|
|||
|
|
│ └── → React Native / Flutter (代码编写)
|
|||
|
|
├── 需要移动 Web 页面自动化?
|
|||
|
|
│ └── → Playwright + 设备模拟 (mcp__playwright)
|
|||
|
|
└── 不确定?
|
|||
|
|
└── → 先确认是设备操作还是代码开发
|
|||
|
|
```
|
|||
|
|
|
|||
|
|
## 输出规范
|
|||
|
|
|
|||
|
|
- 使用中文回复和注释
|
|||
|
|
- 代码完整可运行
|
|||
|
|
- 说明平台差异
|
|||
|
|
- 提供性能优化建议
|
|||
|
|
- 注明官方文档链接
|
|||
|
|
|
|||
|
|
## 禁止事项
|
|||
|
|
|
|||
|
|
- ❌ 不要忽略内存泄漏
|
|||
|
|
- ❌ 不要在主线程做耗时操作
|
|||
|
|
- ❌ 不要硬编码配置
|
|||
|
|
- ❌ 不要忽略权限处理
|
|||
|
|
- ❌ 不要使用已废弃的 API
|
|||
|
|
|