engineering-mobile-app-builder
Compare original and translation side by side
🇺🇸
Original
English🇨🇳
Translation
ChineseMobile Development Guide
移动开发指南
Overview
概述
This guide covers native iOS/Android development (SwiftUI, Jetpack Compose) and cross-platform frameworks (React Native, Flutter) with patterns for offline-first architecture, platform integrations, and performance optimization. Use it when choosing a platform strategy, building mobile UI, or integrating device capabilities.
本指南涵盖iOS/Android原生开发(SwiftUI、Jetpack Compose)与跨平台框架(React Native、Flutter)相关内容,包含离线优先架构、平台能力集成、性能优化的最佳实践。你可以在制定平台选型策略、搭建移动UI、集成设备能力时参考本指南。
Platform Selection Guide
平台选型指南
- Use native (SwiftUI/Compose) when the app requires deep platform integration (widgets, extensions, AR, custom camera pipelines).
- Use React Native or Flutter when shipping to both platforms with a small team and the app is primarily data display and forms.
- For iOS, use SwiftUI with (iOS 17+) or
@Observable/@StateObject(iOS 15+); fall back to UIKit only for unsupported features.@ObservedObject - For Android, use Jetpack Compose with Hilt for DI and for reactive state; avoid XML layouts in new screens.
StateFlow - For navigation, use (iOS) or Navigation Compose (Android) with typed routes.
NavigationStack
- 当应用需要深度平台集成(小组件、扩展、AR、自定义相机 pipeline)时,选择原生开发(SwiftUI/Compose)。
- 当团队规模较小、需要同时发版双平台,且应用以数据展示和表单为主时,选择React Native或Flutter。
- iOS端优先使用SwiftUI搭配(iOS 17+)或
@Observable/@StateObject(iOS 15+);仅当遇到不支持的特性时回退到UIKit。@ObservedObject - Android端优先使用Jetpack Compose搭配Hilt实现依赖注入、实现响应式状态;新页面不要使用XML布局。
StateFlow - 导航场景使用(iOS)或Navigation Compose(Android)搭配类型化路由实现。
NavigationStack
Performance and UX Rules
性能与UX规则
- Cold start must be under 2 seconds on mid-range devices; defer initialization not needed for first frame. If cold start exceeds 1.5s, profile with Instruments (iOS) or (Android) and move heavy work to background.
reportFullyDrawn() - Maintain 60fps scrolling on devices two generations behind current. If frame drops occur: on iOS, check for off-main-thread image decoding and use ; on Android, enable
prefetchDataSourceand check for unnecessary recomposition with Layout Inspector.compositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed - Animations: use SwiftUI / Compose
.animation(); keep durations 200-350ms. Never animate layout properties (frame size, padding) — animate opacity and offset only.animateFloatAsState - Offline-first: local database (Core Data, Room, SQLite/Drift) as single source of truth, background server sync. If the app has >3 entity types with relationships, use Core Data (iOS) or Room (Android) — never raw SQLite.
- Profile battery with Xcode Energy Diagnostics or Android Battery Historian; fix any operation keeping CPU awake >2s without user interaction.
- Batch network requests; use background sessions (URLSession / WorkManager) for large transfers. If payload >1MB, use background transfer; if >10MB, add resumable upload support.
- Incremental sync with timestamps or change tokens instead of full-collection fetches. If collection has >500 items, paginate sync with cursor; never fetch all.
- Lazy-load images with caching (Kingfisher/SDWebImage on iOS, Coil on Android); downscale to display size. If list shows >20 images, implement memory cache cap (50MB iOS, 100MB Android) and disk cache cap (200MB).
- App binary size: target <50MB iOS, <30MB APK (or use AAB for Android). If over threshold, audit with , remove unused assets, and enable app thinning (iOS) or dynamic feature modules (Android).
scripts/check_app_size.sh - Network timeout: 10s for API calls, 30s for file uploads. If a request fails, retry with exponential backoff (1s, 2s, 4s) max 3 attempts. Never retry non-idempotent requests (POST without idempotency key).
- Memory: monitor with (iOS) /
os_signpost(Android). If memory exceeds 200MB on mid-range device, investigate with Instruments Allocations or Android Studio Memory Profiler. Fix retain cycles (iOS) or leaked ViewModels (Android) before shipping.Debug.getNativeHeapAllocatedSize()
- 中端设备上冷启动时间必须控制在2秒以内,首帧渲染不需要的初始化逻辑要延后执行。如果冷启动时间超过1.5秒,使用Instruments(iOS)或(Android)进行性能剖析,将重负载任务移至后台执行。
reportFullyDrawn() - 落后当前两代的设备上要保持60fps的滚动帧率。如果出现掉帧:iOS端检查是否在主线程外解码图片,使用优化;Android端开启
prefetchDataSource,通过Layout Inspector排查不必要的重组。compositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed - 动画:使用SwiftUI / Compose
.animation()实现,时长控制在200-350ms。绝对不要对布局属性(帧大小、内边距)做动画,仅对透明度和偏移量做动画。animateFloatAsState - 离线优先:本地数据库(Core Data、Room、SQLite/Drift)作为唯一可信数据源,后台执行服务端同步。如果应用有超过3个带关联关系的实体类型,使用Core Data(iOS)或Room(Android),绝对不要直接使用原生SQLite。
- 使用Xcode Energy Diagnostics或Android Battery Historian分析耗电情况,修复所有无用户交互时CPU唤醒超过2秒的操作。
- 网络请求批量发送;大文件传输使用后台会话(URLSession / WorkManager)。如果 payload 超过1MB,使用后台传输;如果超过10MB,新增断点续传支持。
- 优先使用时间戳或变更标记实现增量同步,不要全量拉取集合数据。如果集合超过500条,使用游标分页同步,绝对不要全量拉取。
- 图片懒加载搭配缓存(iOS端用Kingfisher/SDWebImage,Android端用Coil),提前缩放到显示尺寸。如果列表展示超过20张图片,设置内存缓存上限(iOS 50MB,Android 100MB)和磁盘缓存上限(200MB)。
- 应用包大小目标:iOS <50MB,APK <30MB(Android端也可以使用AAB格式)。如果超过阈值,用分析,移除未使用的资源,开启应用瘦身(iOS)或动态功能模块(Android)。
scripts/check_app_size.sh - 网络超时设置:API请求10秒,文件上传30秒。请求失败时使用指数退避重试(1s、2s、4s),最多重试3次。绝对不要重试非幂等请求(没有幂等键的POST请求)。
- 内存监控:使用(iOS)/
os_signpost(Android)。如果中端设备内存占用超过200MB,使用Instruments Allocations或Android Studio Memory Profiler排查。发版前修复内存引用循环(iOS)或ViewModel泄漏(Android)问题。Debug.getNativeHeapAllocatedSize()
Platform Integration Rules
平台集成规则
- Biometric auth: (iOS) /
LAContext(Android) with passcode fallback; never store raw biometric data. If device lacks biometrics, fall back to device passcode — never skip auth entirely. Store auth tokens in Keychain (iOS) / AndroidKeystore withBiometricPrompt.setUserAuthenticationRequired(true) - Camera: request permissions just-in-time, handle denial with settings deep link. If permission denied twice, show inline explanation with "Open Settings" button — never re-prompt via system dialog (iOS blocks it).
- Push notifications: APNs (iOS) / FCM (Android) with topic-based subscription. Request permission only after user performs an action that benefits from notifications — never on first launch. If user declines, store preference and show in-app toggle; re-prompt only after 30 days.
- In-app purchases: StoreKit 2 (iOS) / Google Play Billing Library 6+ (Android) with server-side receipt validation. Never trust client-side purchase verification alone. Implement restore purchases flow on first launch for returning users. Handle interrupted purchases (app killed mid-transaction) with (iOS) /
Transaction.unfinished(Android).queryPurchasesAsync - Deep linking: use Universal Links (iOS) / App Links (Android) — not custom URL schemes. Validate and
apple-app-site-associationare served from the domain with correct content-type. Test deferred deep links for users who install after clicking link.assetlinks.json
- 生物识别认证:使用(iOS)/
LAContext(Android),支持密码 fallback;绝对不要存储原始生物特征数据。如果设备不支持生物识别,回退到设备密码验证,绝对不要完全跳过认证。认证令牌存储在Keychain(iOS)/AndroidKeystore中,设置BiometricPrompt。setUserAuthenticationRequired(true) - 相机:仅在需要使用时即时申请权限,权限被拒时提供跳转设置的深度链接。如果用户两次拒绝权限,展示带「打开设置」按钮的内联说明,绝对不要再次弹出系统授权弹窗(iOS会屏蔽重复弹窗)。
- 推送通知:使用APNs(iOS)/FCM(Android),支持基于主题的订阅。仅在用户执行了需要通知的操作后再申请权限,绝对不要在首次启动时申请。如果用户拒绝,存储偏好设置并提供应用内开关,30天后才可再次申请权限。
- 应用内购买:使用StoreKit 2(iOS)/Google Play Billing Library 6+(Android),搭配服务端收据验证。绝对不要仅信任客户端的购买校验结果。首次启动时为老用户实现购买恢复流程。使用(iOS)/
Transaction.unfinished(Android)处理被中断的购买(交易过程中应用被杀死)。queryPurchasesAsync - 深度链接:使用Universal Links(iOS)/App Links(Android),不要使用自定义URL scheme。校验和
apple-app-site-association在对应域名下的返回内容类型正确。测试用户点击链接后才安装应用的延迟深度链接场景。assetlinks.json
Platform-Specific UI
平台专属UI规则
- iOS: SF Symbols, system font (San Francisco), Dynamic Type support. If custom fonts are needed, limit to 2 families max. Support all Dynamic Type sizes from to
xSmall— never use fixed font sizes.accessibility5 - Android: Material Design 3 tokens, in Compose. Use
MaterialThemefor all colors — never hardcode hex values. Support dark theme from day one viaMaterialTheme.colorScheme.isSystemInDarkTheme() - Touch targets: minimum 44x44pt (iOS) / 48x48dp (Android). If interactive elements are smaller, expand the tap area with (SwiftUI) or
.contentShape()(Compose).Modifier.minimumInteractiveComponentSize() - Sensitive data: Keychain (iOS) / EncryptedSharedPreferences (Android). Never store tokens, passwords, or PII in UserDefaults/SharedPreferences. If data must persist across app reinstalls, use Keychain with .
kSecAttrAccessibleAfterFirstUnlock - Permissions: explain before system prompt, handle denial gracefully, never block entire app. If >3 permissions needed, request them progressively as features are used — never batch-request on launch.
- iOS:使用SF Symbols、系统字体(San Francisco)、支持Dynamic Type。如果需要自定义字体,最多使用2个字体族。支持从到
xSmall的所有Dynamic Type字号,绝对不要使用固定字号。accessibility5 - Android:使用Material Design 3 token,Compose中使用。所有颜色使用
MaterialTheme,绝对不要硬编码十六进制色值。开发初期就通过MaterialTheme.colorScheme支持深色模式。isSystemInDarkTheme() - 点击热区:最小44x44pt(iOS)/48x48dp(Android)。如果交互元素尺寸更小,通过(SwiftUI)或
.contentShape()(Compose)扩大点击区域。Modifier.minimumInteractiveComponentSize() - 敏感数据:存储在Keychain(iOS)/EncryptedSharedPreferences(Android)。绝对不要将令牌、密码、个人可识别信息存储在UserDefaults/SharedPreferences中。如果数据需要在应用重装后保留,使用设置了的Keychain存储。
kSecAttrAccessibleAfterFirstUnlock - 权限:系统弹窗前先说明用途,优雅处理权限被拒的情况,绝对不要因为权限问题阻塞整个应用。如果需要超过3个权限,在用户使用对应功能时逐步申请,绝对不要在启动时批量申请。
Code Examples
代码示例
See SwiftUI Guide for a full product list with NavigationStack, search, pagination, and pull-to-refresh.
See Jetpack Compose Guide for a full product list with Hilt, StateFlow, LazyColumn, and debounced search.
See React Native Guide for a full product list with FlatList, react-query infinite scrolling, and platform styling.
See Flutter Guide for a full product list with Riverpod, ListView.builder, debounced search, pagination, and pull-to-refresh.
See Offline-First Architecture for offline queue systems (NetInfo + AsyncStorage, Core Data + CloudKit, Room + WorkManager), conflict resolution (LWW, field-level merge, CRDT), and optimistic UI with rollback.
See Performance Patterns for Hermes engine config, FlatList optimization (getItemLayout, windowSize), SwiftUI lazy stacks and image caching, Compose recomposition control with Coil, battery optimization, memory leak prevention, and cold start optimization.
See State Management for Zustand with MMKV persistence, TanStack Query optimistic mutations, SwiftUI @Observable with environment injection, Compose ViewModel + StateFlow + SavedStateHandle, navigation deep linking, auth state machines, and form validation.
See Native APIs for push notifications (APNs, FCM, react-native-firebase), camera/photo (CameraX, AVFoundation, vision-camera), biometric auth (FaceID/TouchID, BiometricPrompt), background tasks (BGTaskScheduler, WorkManager), and in-app purchases (StoreKit 2, Google Play Billing).
查看SwiftUI指南了解包含NavigationStack、搜索、分页、下拉刷新的完整商品列表实现。
查看Jetpack Compose指南了解包含Hilt、StateFlow、LazyColumn、防抖搜索的完整商品列表实现。
查看React Native指南了解包含FlatList、react-query无限滚动、平台样式的完整商品列表实现。
查看Flutter指南了解包含Riverpod、ListView.builder、防抖搜索、分页、下拉刷新的完整商品列表实现。
查看离线优先架构了解离线队列系统(NetInfo + AsyncStorage、Core Data + CloudKit、Room + WorkManager)、冲突解决(LWW、字段级合并、CRDT)、带回滚能力的乐观UI实现。
查看性能优化模式了解Hermes引擎配置、FlatList优化(getItemLayout、windowSize)、SwiftUI懒加载栈与图片缓存、Compose重组控制与Coil使用、耗电优化、内存泄漏预防、冷启动优化方案。
查看状态管理了解带MMKV持久化的Zustand、TanStack Query乐观更新、SwiftUI @Observable与环境注入、Compose ViewModel + StateFlow + SavedStateHandle、导航深度链接、认证状态机、表单校验方案。
查看原生API了解推送通知(APNs、FCM、react-native-firebase)、相机/相册(CameraX、AVFoundation、vision-camera)、生物识别认证(FaceID/TouchID、BiometricPrompt)、后台任务(BGTaskScheduler、WorkManager)、应用内购买(StoreKit 2、Google Play Billing)实现。
Platform-Specific Gotchas
平台专属注意事项
iOS Background Task Limits
iOS后台任务限制
- : System grants ~30 seconds of execution. If the task does not call
BGAppRefreshTaskwithin that window, the system kills it and deprioritizes future requests.setTaskCompleted - : Up to 3 minutes, but only runs when device is charging and on Wi-Fi. Do not rely on this for time-sensitive sync.
BGProcessingTask - After a push notification wakes the app (), you get ~30 seconds. If the work takes longer, start a
content-available: 1background download instead.URLSession - The system learns user behavior. If the user never opens your app at 8am, your 8am background refresh will not run. Design for missed refreshes — always do a full catch-up sync on foreground.
- :系统仅分配约30秒执行时间。如果任务没有在窗口期内调用
BGAppRefreshTask,系统会杀死任务并降低后续请求的优先级。setTaskCompleted - :最多执行3分钟,但仅在设备充电且连接Wi-Fi时运行。不要依赖该任务执行时间敏感的同步操作。
BGProcessingTask - 推送通知唤醒应用后(),你有约30秒执行时间。如果任务耗时更长,启动
content-available: 1后台下载代替。URLSession - 系统会学习用户行为。如果用户从来不在早上8点打开你的应用,你设置的8点后台刷新不会执行。要考虑刷新失败的场景,应用回到前台时始终执行一次全量补全同步。
Android Background Restrictions
Android后台限制
- Doze mode (Android 6+): After screen off + stationary, network access is batched into maintenance windows (~every 15 min, increasing to ~1 hour). with
WorkManagerconstraint will defer until the next window. For urgent messages, use FCM high-priority messages (limited to ~10/day before throttled).NetworkType.CONNECTED - App Standby Buckets (Android 9+): Apps are ranked Active → Working Set → Frequent → Rare → Restricted. Rare/Restricted apps get severely limited jobs and alarms. If your app is in Restricted bucket, periodic work may run only once per 24 hours.
WorkManager - Exact alarms (Android 12+): requires
setExactAndAllowWhileIdle()permission. Users can revoke it in Settings. Always checkSCHEDULE_EXACT_ALARMbefore scheduling and fall back to inexact.canScheduleExactAlarms() - Foreground service types (Android 14+): Must declare in manifest. Types:
foregroundServiceType,camera,location,mediaPlayback, etc. Using wrong type → crash.dataSynctype is limited to 6 hours.dataSync
- Doze模式(Android 6+):设备熄屏且静止后,网络访问会被合并到维护窗口执行(约每15分钟一次,之后逐渐延长到1小时一次)。设置了约束的
NetworkType.CONNECTED会延迟到下一个维护窗口执行。紧急消息使用FCM高优先级消息(每天最多10条,超过会被限流)。WorkManager - 应用待机分组(Android 9+):应用按优先级分为活跃 → 工作集 → 频繁 → 稀少 → 受限。稀少/受限分组的应用的任务和闹钟会被严格限制。如果你的应用在受限分组,周期任务可能24小时仅执行一次。
WorkManager - 精确闹钟(Android 12+):需要
setExactAndAllowWhileIdle()权限,用户可以在设置中撤销该权限。调度前始终检查SCHEDULE_EXACT_ALARM,不可用时回退到非精确闹钟。canScheduleExactAlarms() - 前台服务类型(Android 14+):必须在manifest中声明,可选类型:
foregroundServiceType、camera、location、mediaPlayback等。使用错误类型会导致崩溃。dataSync类型最多运行6小时。dataSync
Flutter-Specific Gotchas
Flutter专属注意事项
- constructors: Always use
constfor stateless widgets and unchanging widget subtrees. Withoutconst, Flutter rebuilds the entire subtree on parent rebuild. This is the #1 Flutter performance issue.const - : Use
Keyson list items when the list can reorder, insert, or delete. Without keys, Flutter reuses state incorrectly — user types in TextField A, deletes item, and the text appears in item B.ValueKey - Platform channels: Calls between Dart and native (iOS/Android) are async and serialized. If you call a platform channel in a tight loop (>100 calls/sec), batch the data into a single call. Use with binary codec for large payloads.
BasicMessageChannel - Isolates: Dart is single-threaded. For CPU work >16ms (JSON parsing large responses, image processing), use (Dart 2.19+) or
Isolate.run(). Never parse >100KB JSON on the main isolate.compute() - Image caching: Flutter's default widget caches decoded images in memory with no limit. For lists with >50 images, use
Imagewithcached_network_image/memCacheHeightto downscale, or the memory will grow unbounded.memCacheWidth - State restoration: On Android, the system kills background apps aggressively. Use or persist critical state to disk. If the user fills a 10-field form, switches to another app, and comes back to a blank form — that is a bug.
RestorationMixin
- 构造函数:无状态组件和不变的组件子树始终使用
const修饰。没有const修饰时,父组件重建会触发整个子树重建,这是Flutter排名第一的性能问题。const - :列表可重排、插入、删除时,列表项要使用
Keys。没有key时,Flutter会错误复用状态:用户在TextField A输入内容,删除该条目后,输入的内容会出现在条目B中。ValueKey - 平台通道:Dart和原生(iOS/Android)之间的调用是异步且序列化的。如果在循环中高频调用平台通道(>100次/秒),将数据合并为单次调用。大payload使用搭配二进制编解码器。
BasicMessageChannel - Isolate:Dart是单线程的。超过16ms的CPU任务(大响应JSON解析、图片处理)使用(Dart 2.19+)或
Isolate.run()执行。绝对不要在主isolate中解析超过100KB的JSON。compute() - 图片缓存:Flutter默认组件的解码图片内存缓存没有上限。如果列表超过50张图片,使用
Image搭配cached_network_image/memCacheHeight缩放图片,否则内存会无限增长。memCacheWidth - 状态恢复:Android系统会主动杀死后台应用。使用或将关键状态持久化到磁盘。如果用户填完10个字段的表单,切换到其他应用再回来时表单为空,这是bug。
RestorationMixin
Compose-Specific Gotchas
Compose专属注意事项
- Recomposition: Any lambda that captures a mutable value triggers recomposition of its parent. Pass -ed lambdas or use
rememberfor expensive computations. Use Layout Inspector > "Show Recomposition Counts" to find hot spots.derivedStateOf - Stability: Compose skips recomposition for or
@Stabletypes. If your data class uses@Immutable(unstable), Compose recomposes every time. UseList<T>or annotate withkotlinx.collections.immutable.ImmutableListif you guarantee immutability.@Immutable - item keys: Always provide
LazyColumninkey. Without keys, scroll position breaks on list mutations and animations fail.items(key = { it.id })
- 重组:任何捕获了可变值的lambda都会触发其父组件重组。传递包裹的lambda,或对计算量较大的逻辑使用
remember。使用Layout Inspector的「显示重组计数」功能排查热点。derivedStateOf - 稳定性:或
@Stable类型的变更会跳过重组。如果你的数据类使用了@Immutable(非稳定类型),每次变更都会触发重组。使用List<T>,或如果你能保证不可变性,添加kotlinx.collections.immutable.ImmutableList注解。@Immutable - 条目key:始终在
LazyColumn中提供key。没有key时,列表变更时滚动位置会错乱,动画也会失效。items(key = { it.id })
Offline Sync Decision Rules
离线同步决策规则
Conflict Resolution Strategy
冲突解决策略
- Last-Write-Wins (LWW): Use when data is user-owned and rarely edited concurrently (user profile, settings, personal notes). Simple: compare timestamps, latest wins. Risk: silent data loss if two devices edit simultaneously.
- Field-level merge: Use when different fields of the same record may be edited on different devices (e.g., user edits on phone,
nameon tablet). Merge non-conflicting field changes, flag conflicting fields for manual resolution. Requires tracking per-field timestamps.email - Operational Transform / CRDT: Use only for real-time collaborative editing (shared documents, collaborative whiteboards). Complex to implement — use a library (Yjs, Automerge). Never build custom OT/CRDT.
- Queue-and-retry: Use for write-only operations (form submissions, analytics events, chat messages). Queue locally, send when online, retry with idempotency keys. No conflict possible because each operation is independent.
- 最后写入获胜(LWW):适用于用户私有、很少并发编辑的数据(用户资料、设置、个人笔记)。实现简单:比较时间戳,最新的修改获胜。风险:如果两台设备同时编辑会出现静默数据丢失。
- 字段级合并:适用于同一条目的不同字段可能在不同设备编辑的场景(比如用户在手机上编辑,在平板上编辑
姓名)。合并无冲突的字段变更,标记冲突字段供人工解决。需要逐字段跟踪修改时间戳。邮箱 - Operational Transform / CRDT:仅适用于实时协作编辑场景(共享文档、协作白板)。实现复杂,使用现成库(Yjs、Automerge),绝对不要自研OT/CRDT。
- 队列重试:适用于仅写操作(表单提交、埋点事件、聊天消息)。本地入队,在线时发送,使用幂等键重试。因为每个操作都是独立的,不存在冲突。
Sync Architecture Decision Rules
同步架构决策规则
- If the app has <5 entity types and sync is user-to-server only (no collaboration): use timestamp-based incremental sync. Client sends , server returns changed records since that timestamp.
lastSyncTimestamp - If the app has 5-15 entity types or needs server-to-client push: use change tokens (CloudKit) or server-sent events. Client stores a sync cursor, server pushes deltas.
- If the app supports real-time collaboration (multiple users editing same data): use WebSocket with OT/CRDT library. This is 10x the complexity of simple sync — only choose it if collaboration is a core requirement.
- 如果应用实体类型少于5个,且仅支持用户到服务端的同步(无协作):使用基于时间戳的增量同步。客户端发送,服务端返回该时间戳之后变更的记录。
lastSyncTimestamp - 如果应用实体类型为5-15个,或需要服务端主动推送:使用变更标记(CloudKit)或服务端发送事件。客户端存储同步游标,服务端推送增量数据。
- 如果应用支持实时协作(多用户编辑同一份数据):使用WebSocket搭配OT/CRDT库。该方案复杂度是普通同步的10倍,仅当协作为核心需求时选择。
App Lifecycle Decision Rules
应用生命周期决策规则
Forced Update Strategy
强制更新策略
- Critical security patch: Force-block the app with a full-screen modal. No dismiss option. Check on every app launch against a remote config ().
/api/min-version - Breaking API change: Show a blocking modal with "Update Now" button linking to the store. Allow a 2-week grace period with a dismissable banner before blocking.
- New features: Never force-update for features. Show a dismissable banner. If the user ignores it 3 times, stop showing it for 30 days.
- Implementation: Store in remote config (Firebase Remote Config, LaunchDarkly, or a simple API endpoint). Compare against the app's build number on launch. Never hardcode version checks.
minSupportedVersion
- 关键安全补丁:全屏弹窗强制拦截应用,不提供关闭选项。每次应用启动时请求远程配置()校验版本。
/api/min-version - 破坏性API变更:展示带「立即更新」按钮的拦截弹窗,跳转至应用商店。允许2周的缓冲期,缓冲期内可关闭提示 banner,之后再拦截。
- 新功能:绝对不要为了新功能强制更新。展示可关闭的提示 banner,如果用户忽略3次,30天内不再展示。
- 实现方案:将存储在远程配置(Firebase Remote Config、LaunchDarkly或简单API接口)中,启动时和应用的构建号对比。绝对不要硬编码版本校验逻辑。
minSupportedVersion
Migration Between App Versions
应用版本迁移
- If local database schema changes: use versioned migrations (Room , Core Data lightweight migration, Drift schema versioning, Hive type adapters). Always test migration from version N-2 to N (users skip versions).
@Database(version = N) - If auth tokens change format: support both old and new token formats simultaneously for 2 release cycles. Never invalidate all sessions in a single release — users will flood support.
- If switching backend endpoints: use remote config to gradually shift traffic. 10% → 50% → 100% over 1 week. If error rate >1% at any stage, pause and investigate.
- 如果本地数据库 schema 变更:使用版本化迁移(Room 、Core Data轻量迁移、Drift schema版本控制、Hive类型适配器)。始终测试从版本N-2到N的迁移(用户可能跳过版本升级)。
@Database(version = N) - 如果认证令牌格式变更:同时兼容新旧两种令牌格式至少2个发布周期。绝对不要在单个版本中让所有会话失效,否则会导致客服进线激增。
- 如果切换后端接口:通过远程配置逐步切流,1周内按10% → 50% → 100%的比例灰度。任何阶段错误率超过1%都要暂停并排查问题。
Anti-Patterns
反模式
- Never use for state owned by the current view — use
@ObservedObject(iOS 15) or@StateObjectwith@State(iOS 17+).@Observableis for injected dependencies only.@ObservedObject - Never use outside a ViewModel in Compose — all mutable state lives in ViewModels, UI observes via
mutableStateOf.collectAsStateWithLifecycle() - Never call on a disposed widget in Flutter — always check
setStatebefore async state updates. In Riverpod, usemountedto cancel async work.ref.onDispose() - Never use without
FlatListandkeyExtractorin React Native — without these, scrolling performance degrades dramatically on lists >50 items.getItemLayout - Never store navigation state in a global store — use the platform navigation stack. Global nav state causes back-button bugs and deep link failures.
- Never block the main thread with synchronous database reads — use (Android) / background actors (iOS) /
withContext(Dispatchers.IO)(React Native) /InteractionManager.runAfterInteractions(Flutter).Isolate.run() - Never parse large JSON (>100KB) on the main thread in Flutter — use or
compute(). On Android, never do network or disk I/O on the main thread —Isolate.run()will catch this in debug builds.StrictMode - Never use platform-specific code without a fallback. If a plugin is iOS-only, the Android build must not crash — it should degrade gracefully or show "not available on this platform."
- 绝对不要在当前视图所属的状态上使用,使用
@ObservedObject(iOS 15)或@StateObject搭配@State(iOS 17+)。@Observable仅用于注入的依赖。@ObservedObject - Compose中绝对不要在ViewModel外使用,所有可变状态都存储在ViewModel中,UI通过
mutableStateOf观察。collectAsStateWithLifecycle() - Flutter中绝对不要在已销毁的widget上调用,异步更新状态前始终检查
setState。使用Riverpod时,通过mounted取消异步任务。ref.onDispose() - React Native中使用时必须提供
FlatList和keyExtractor,没有这两个属性的话,列表超过50条时滚动性能会急剧下降。getItemLayout - 绝对不要将导航状态存储在全局store中,使用平台自带的导航栈。全局导航状态会导致返回按钮异常和深度链接失败。
- 绝对不要用同步数据库读操作阻塞主线程,使用(Android)/后台actor(iOS)/
withContext(Dispatchers.IO)(React Native)/InteractionManager.runAfterInteractions(Flutter)执行。Isolate.run() - Flutter中绝对不要在主线程解析超过100KB的JSON,使用或
compute()。Android端绝对不要在主线程执行网络或磁盘I/O,debug构建下Isolate.run()会检测到该问题。StrictMode - 平台专属代码必须提供降级方案。如果插件仅支持iOS,Android构建不能崩溃,应该优雅降级或展示「该平台暂不支持」提示。
Workflow
工作流
Step 1: Platform Strategy and Setup
步骤1:平台策略与搭建
- Choose native vs cross-platform: if app needs AR, custom camera, or widgets, go native. If app is data display + forms with <3 platform-specific features, go cross-platform.
- Set minimum OS versions: iOS 16+ (or 15+ if enterprise), Android API 26+ (Android 8.0). If targeting older, document why.
- Configure build tools: Xcode + SwiftPM (iOS), Gradle with version catalogs (Android), Expo with EAS Build (React Native), or Flutter with and Fastlane/Codemagic for CI.
flutter_lints
- 选择原生或跨平台:如果应用需要AR、自定义相机或小组件,选择原生开发。如果应用是数据展示+表单,平台专属功能少于3个,选择跨平台开发。
- 设置最低支持OS版本:iOS 16+(企业级应用可支持15+),Android API 26+(Android 8.0)。如果要支持更低版本,说明原因。
- 配置构建工具:iOS用Xcode + SwiftPM,Android用Gradle搭配版本目录,React Native用Expo + EAS Build,Flutter用搭配Fastlane/Codemagic实现CI。
flutter_lints
Step 2: Architecture and Design
步骤2:架构与设计
- If app works offline, design local-first schema before API schema. Define conflict resolution strategy (LWW for simple, field-level merge for collaborative).
- Define navigation graph with all screens, modals, and deep link routes before building UI.
- Choose state management: + SwiftData (iOS 17+), ViewModel + StateFlow (Android), Zustand + TanStack Query (React Native), Riverpod + Drift/Isar (Flutter).
@Observable
- 如果应用支持离线,先设计本地优先schema再设计API schema。定义冲突解决策略(简单场景用LWW,协作场景用字段级合并)。
- 开发UI前先定义导航图,包含所有页面、弹窗、深度链接路由。
- 选择状态管理:iOS 17+用+ SwiftData,Android用ViewModel + StateFlow,React Native用Zustand + TanStack Query,Flutter用Riverpod + Drift/Isar。
@Observable
Step 3: Development and Integration
步骤3:开发与集成
- Build the critical user flow first (onboarding → core action → result). Do not build settings, profile, or secondary flows until core flow works end-to-end.
- Integrate platform features (camera, notifications, biometrics) one at a time. Test each on a real device before moving to the next.
- Add crash reporting (Crashlytics / Sentry) and basic analytics before first TestFlight / internal track release.
- 优先开发核心用户流程( onboarding → 核心操作 → 结果)。核心流程全链路跑通前不要开发设置、个人中心或次要流程。
- 逐个集成平台特性(相机、通知、生物识别),每个特性在真机上测试通过后再开发下一个。
- 首次提交TestFlight/内部测试版前,接入崩溃上报(Crashlytics / Sentry)和基础埋点。
Step 4: Testing and Deployment
步骤4:测试与部署
- Test on real devices: minimum 2 iOS devices (oldest supported + current) and 3 Android devices (low-end, mid-range, flagship).
- Run UI tests for the critical flow on CI. Unit test ViewModels/repositories with >80% coverage.
- Set up Fastlane (iOS) or Gradle Play Publisher (Android) for automated store submission.
- 真机测试:至少2台iOS设备(最低支持版本+当前最新版本)和3台Android设备(低端、中端、旗舰)。
- CI上运行核心流程的UI测试,ViewModel/仓库层单元测试覆盖率超过80%。
- 配置Fastlane(iOS)或Gradle Play Publisher(Android)实现自动化商店提交。
Self-Verification Protocol
自校验协议
After completing any mobile implementation, verify before submitting for review:
- Run the app on a real device (not just simulator). Simulators hide performance, memory, and gesture issues.
- Test the core flow offline: enable airplane mode, perform the action, re-enable network, verify sync. If the app crashes or loses data offline, it is not ready.
- Profile memory on a mid-range device. If peak memory exceeds 200MB, investigate before shipping.
- Measure cold start time on the oldest supported device. If >2s, defer initialization until after first frame.
- Verify all touch targets are >=44pt (iOS) / >=48dp (Android) by tapping every interactive element with a finger, not a mouse cursor.
- Check Dark Mode on every screen. If any text is unreadable or any element is invisible, fix before merge.
- Test with Dynamic Type (iOS) / Font Scale 200% (Android). If text truncates or overlaps, fix the layout.
- Run the app with VoiceOver (iOS) / TalkBack (Android) on the core flow. Every interactive element must be announced meaningfully.
- Check that no sensitive data (tokens, passwords, PII) appears in logs. Run (Android) or Console.app (iOS) during the flow.
adb logcat
完成任何移动端功能实现后,提交评审前先校验以下项:
- 在真机上运行应用(不要仅用模拟器)。模拟器会隐藏性能、内存、手势相关问题。
- 离线测试核心流程:开启飞行模式,执行操作,重新联网,验证同步正常。如果应用离线时崩溃或丢失数据,说明未达到上线标准。
- 在中端设备上分析内存占用,如果峰值内存超过200MB,发版前排查问题。
- 在最低支持版本的设备上测试冷启动时间,如果超过2秒,将初始化逻辑延后到首帧渲染后执行。
- 用手指(不要用鼠标光标)点击所有交互元素,验证所有点击热区>=44pt(iOS)/ >=48dp(Android)。
- 检查每个页面的深色模式适配,如果有文字不可读或元素不可见,合并前修复问题。
- 用Dynamic Type(iOS)/字体缩放200%(Android)测试,如果文字截断或重叠,修复布局。
- 核心流程开启VoiceOver(iOS)/TalkBack(Android)测试,所有交互元素都要有清晰的语义播报。
- 检查日志中没有泄露敏感数据(令牌、密码、个人可识别信息)。流程执行时用(Android)或Console.app(iOS)排查日志。
adb logcat
Failure Recovery
故障恢复
- App crashes on launch: Check crash log for the exact line. Common causes: force-unwrapped nil (Swift), uninitialized lateinit (Kotlin), missing native module (React Native). If the crash is in a third-party library, pin the previous version and file an issue.
- Build fails after Xcode/Gradle update: Clean derived data () or Gradle cache (
rm -rf ~/Library/Developer/Xcode/DerivedData). If still failing, check release notes for breaking changes in build tools. Pin the toolchain version in the project until the issue is resolved../gradlew clean - UI renders differently on device vs simulator: The simulator uses the Mac GPU. Test on a real device. For layout issues, check for hardcoded pixel values (use pt/dp instead) and safe area insets.
- Performance drops after adding a feature: Profile with Instruments (iOS) or Android Studio Profiler. Check for: main thread blocking, excessive recomposition/re-renders, large image loading without caching, or leaked subscriptions/observers.
- Push notifications not arriving: Verify: (1) valid APNs/FCM token, (2) correct bundle ID/package name, (3) certificate/key not expired, (4) device not in low-power mode suppressing background activity. Test with a manual push via to APNs/FCM before debugging app code.
curl - App rejected by App Store / Play Store: Read the rejection reason exactly. Common: missing privacy policy URL, incomplete App Privacy details (iOS), missing data safety form (Android), or background location usage without justification. Fix the metadata — do not guess.
- 应用启动崩溃:查看崩溃日志定位代码行。常见原因:Swift强制解包nil、Kotlin未初始化lateinit、React Native缺失原生模块。如果崩溃出现在第三方库,锁定上一个可用版本并提交issue。
- Xcode/Gradle更新后构建失败:清理派生数据()或Gradle缓存(
rm -rf ~/Library/Developer/Xcode/DerivedData)。如果还是失败,查看构建工具的发布说明确认破坏性变更,项目锁定工具链版本直到问题解决。./gradlew clean - 真机和模拟器UI渲染不一致:模拟器使用Mac GPU,在真机上测试。布局问题检查是否有硬编码像素值(用pt/dp代替)和安全区域适配问题。
- 新增功能后性能下降:用Instruments(iOS)或Android Studio Profiler分析。排查点:主线程阻塞、过度重组/重渲染、大图片未缓存加载、订阅/观察者泄漏。
- 推送通知收不到:校验点:(1)APNs/FCM token有效,(2)bundle ID/包名正确,(3)证书/密钥未过期,(4)设备未开启低电量模式抑制后台活动。调试应用代码前先用手动调用APNs/FCM接口测试推送。
curl - 应用被App Store/Play Store驳回:准确阅读驳回原因。常见原因:缺失隐私政策链接、iOS App Privacy信息不完整、Android数据安全表单未填写、后台定位使用未说明用途。修复对应元数据,不要盲目猜测。
Existing App Orientation
现有项目适配流程
When taking over or joining an existing mobile project:
- Build and run (15 min) — Clone, install deps, build, run on a real device. If it fails, fix the build first.
- Identify the architecture pattern (10 min) — MVC, MVVM, MVI, or unstructured? Check: where does state live? How does data flow from API to UI?
- Map the navigation graph (10 min) — List all screens and how they connect. Check for deep link handlers. Note any navigation library in use.
- Check the dependency list (5 min) — Podfile/SPM (iOS), build.gradle (Android), package.json (React Native). Flag outdated or abandoned dependencies.
- Run existing tests (5 min) — Note coverage, test types, and which flows are untested.
- Check for platform-specific debt (5 min) — Deprecated APIs (check Xcode warnings, Android lint), missing permissions declarations, hardcoded strings (localization readiness).
- Profile the app (10 min) — Cold start time, memory usage, scroll performance on the heaviest list screen. These numbers are your baseline.
接手或加入现有移动项目时按以下步骤执行:
- 构建并运行(15分钟)—— 克隆代码、安装依赖、构建、在真机上运行。如果构建失败,先修复构建问题。
- 确认架构模式(10分钟)—— MVC、MVVM、MVI还是无结构?排查点:状态存储在哪里?数据如何从API流向UI?
- 梳理导航图(10分钟)—— 列出所有页面和跳转关系,检查深度链接处理逻辑,记录使用的导航库。
- 检查依赖列表(5分钟)—— iOS看Podfile/SPM,Android看build.gradle,React Native看package.json。标记过时或不再维护的依赖。
- 运行现有测试(5分钟)—— 记录覆盖率、测试类型、未覆盖的流程。
- 检查平台技术债务(5分钟)—— 废弃API(查看Xcode警告、Android lint)、缺失权限声明、硬编码字符串(国际化就绪度)。
- 应用性能分析(10分钟)—— 冷启动时间、内存占用、最重列表页的滚动性能,这些数值作为后续优化的基线。
Scripts
脚本
scripts/check_app_size.sh
scripts/check_app_size.shscripts/check_app_size.sh
scripts/check_app_size.shAnalyze a mobile app build output directory for size issues. Takes a build output directory as argument, finds app binaries/bundles (.app, .apk, .aab, .ipa), reports total size, largest files, and asset breakdown by category. Warns if total exceeds common thresholds (50MB for iOS, 150MB for Android).
bash
scripts/check_app_size.sh ./build/outputs/apk/release
scripts/check_app_size.sh --threshold-ios 40 --top-files 10 ./DerivedData/Build/Products/Release-iphoneos分析移动应用构建输出目录的包大小问题。接收构建输出目录作为参数,查找应用二进制/包(.app、.apk、.aab、.ipa),输出总大小、最大文件列表、按类别拆分的资源占比。如果超过通用阈值(iOS 50MB,Android 150MB)会输出警告。
bash
scripts/check_app_size.sh ./build/outputs/apk/release
scripts/check_app_size.sh --threshold-ios 40 --top-files 10 ./DerivedData/Build/Products/Release-iphoneosscripts/check_permissions.py
scripts/check_permissions.pyscripts/check_permissions.py
scripts/check_permissions.pyExtract and audit permissions from AndroidManifest.xml or Info.plist. Identifies requested permissions, flags potentially dangerous ones (CAMERA, LOCATION, CONTACTS, etc.) with explanations, and reports total permission count. Supports JSON output.
bash
scripts/check_permissions.py app/src/main/AndroidManifest.xml
scripts/check_permissions.py --format json ios/Runner/Info.plist从AndroidManifest.xml或Info.plist中提取并审计权限。识别申请的权限,标记高风险权限(CAMERA、LOCATION、CONTACTS等)并说明风险,输出总权限数。支持JSON格式输出。
bash
scripts/check_permissions.py app/src/main/AndroidManifest.xml
scripts/check_permissions.py --format json ios/Runner/Info.plist