mcpField
E
engineering-mobile-app-builder

engineering-mobile-app-builder

github.com/peterhdd开发11184 次安装33 周前更新
代码仓库

关于 engineering-mobile-app-builder

面向工程师的移动开发指南,涵盖平台选择、界面构建和设备能力集成。内容涉及原生与跨平台方案,并提供离线同步、性能、安全及平台行为方面的实践规则。

主要功能

  1. 01原生与跨平台方案选择建议
  2. 02移动界面和导航模式
  3. 03离线优先数据与同步建议
  4. 04性能与电池检查
  5. 05平台 API 与权限实践
  6. 06平台特定实现注意事项

适用场景

  1. 01选择移动应用开发方案
  2. 02设计支持离线的数据流程
  3. 03集成相机、生物识别或通知
  4. 04排查启动、渲染或后台任务问题

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/peterhdd/agent-skills --skill engineering-mobile-app-builder

安装后在 Claude Code 中输入/engineering-mobile-app-builder确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

references/
compose.md
flutter.md
native-apis.md
offline-first.md
performance.md
react-native.md
state-management.md
swiftui.md
scripts/
check_app_size.sh
check_permissions.py

详细介绍

移动开发指南

概述

本指南涵盖原生 iOS/Android 开发(SwiftUI、Jetpack Compose)和跨平台框架(React Native、Flutter),并介绍离线优先架构、平台集成和性能优化模式。可用于选择平台策略、构建移动端 UI 或集成设备功能。

平台选择指南

  • 当应用需要深度平台集成(小组件、扩展、AR、自定义相机管线)时,使用原生(SwiftUI/Compose)。
  • 当团队规模较小,且应用主要用于展示数据和填写表单时,使用 React Native 或 Flutter,以便同时发布到两个平台。
  • 对于 iOS,使用带有 @Observable(iOS 17+)或 @StateObject/@ObservedObject(iOS 15+)的 SwiftUI;仅在功能不受支持时才回退到 UIKit。
  • 对于 Android,使用 Jetpack Compose 搭配 Hilt 进行 DI,并使用 StateFlow 管理响应式状态;新屏幕应避免使用 XML 布局。
  • 对于导航,使用 NavigationStack(iOS)或 Navigation Compose(Android),并采用类型化路由。

性能和 UX 规则

  • 中端设备上的冷启动时间必须低于 2 秒;延迟初始化首帧不需要的内容。如果冷启动时间超过 1.5 秒,请使用 Instruments(iOS)或 reportFullyDrawn()(Android)进行性能分析,并将繁重工作移至后台。
  • 在比当前设备落后两代的设备上,滚动时保持 60fps。如果出现掉帧:在 iOS 上,检查是否在主线程之外解码图像,并使用 prefetchDataSource;在 Android 上,启用 compositionStrategy = ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed,并使用 Layout Inspector 检查是否存在不必要的重组。
  • 动画:使用 SwiftUI .animation() / Compose animateFloatAsState;时长保持在 200-350ms。绝不要为布局属性(frame 大小、padding)添加动画——只为透明度和偏移量添加动画。
  • 离线优先:将本地数据库(Core Data、Room、SQLite/Drift)作为唯一事实来源,并在后台与服务同步。如果应用中有 3 种以上存在关系的实体类型,请使用 Core Data(iOS)或 Room(Android)——绝不要使用原生 SQLite。
  • 使用 Xcode Energy Diagnostics 或 Android Battery Historian 分析电池使用情况;修复任何在没有用户交互时使 CPU 持续运行超过 2s 的操作。
  • 批量发送网络请求;大型传输使用后台会话(URLSession / WorkManager)。如果负载超过 1MB,请使用后台传输;如果超过 10MB,请添加可恢复的上传支持。
  • 使用时间戳或变更令牌进行增量同步,而不是获取完整集合。如果集合包含超过 500 个项目,请使用游标分页同步;绝不要一次性获取全部项目。
  • 使用缓存延迟加载图像(iOS 上使用 Kingfisher/SDWebImage,Android 上使用 Coil);将图像缩小到显示尺寸。如果列表显示超过 20 张图像,请设置内存缓存上限(iOS 为 50MB,Android 为 100MB)和磁盘缓存上限(200MB)。
  • 应用二进制文件大小:目标为 iOS 小于 50MB、APK 小于 30MB(或 Android 使用 AAB)。如果超过阈值,请使用 scripts/check_app_size.sh 审核,移除未使用的资源,并启用应用瘦身(iOS)或动态功能模块(Android)。
  • 网络超时:API 调用为 10s,文件上传为 30s。如果请求失败,请使用指数退避重试(1s、2s、4s),最多尝试 3 次。绝不要重试非幂等请求(没有幂等键的 POST)。
  • 内存:使用 os_signpost(iOS)/Debug.getNativeHeapAllocatedSize()(Android)进行监控。如果中端设备上的内存超过 200MB,请使用 Instruments Allocations 或 Android Studio Memory Profiler 进行排查。发布前修复保留环(iOS)或泄漏的 ViewModel(Android)。

平台集成规则

  • 生物识别认证:使用 LAContext(iOS)/ BiometricPrompt(Android),并提供密码回退;绝不存储原始生物识别数据。如果设备不支持生物识别,则回退到设备密码——绝不能完全跳过认证。将认证令牌存储在 Keychain(iOS)/ AndroidKeystore 中,并设置 setUserAuthenticationRequired(true)。
  • 相机:仅在需要时请求权限,用户拒绝时提供跳转到设置的深层链接。如果用户两次拒绝权限,则显示内联说明和“打开设置”按钮——绝不要再次通过系统对话框请求权限(iOS 会阻止这种做法)。
  • 推送通知:使用 APNs(iOS)/ FCM(Android)进行基于主题的订阅。仅在用户执行了有助于接收通知的操作后请求权限——绝不要在首次启动时请求。如果用户拒绝,则存储其偏好设置并显示应用内开关;仅在 30 天后再次请求。
  • 不要通过此技能实现或触发购买、账单、付款或其他资金转移流程。如果用户需要应用内购买,则仅提供有关权益建模、收据验证架构、审计要求和平台政策审查的指导;交易执行细节应交由专门的支付审查处理。
  • 深层链接:使用 Universal Links(iOS)/ App Links(Android),而非自定义 URL 方案。验证 apple-app-site-association 和 assetlinks.json 是否通过域名以正确的内容类型提供。测试用户点击链接后安装应用时的延迟深层链接。

平台专属 UI

  • iOS:使用 SF Symbols、系统字体(San Francisco)并支持动态字体。如果需要自定义字体,最多使用 2 个字体系列。支持从 xSmall 到 accessibility5 的所有动态字体大小——绝不要使用固定字号。
  • Android:使用 Material Design 3 设计令牌,以及 Compose 中的 MaterialTheme。所有颜色均使用 MaterialTheme.colorScheme——绝不要硬编码十六进制值。从第一天起就通过 isSystemInDarkTheme() 支持深色主题。
  • 触控目标:最小尺寸为 44x44pt(iOS)/ 48x48dp(Android)。如果交互元素更小,则使用 .contentShape()(SwiftUI)或 Modifier.minimumInteractiveComponentSize()(Compose)扩大点击区域。
  • 敏感数据:使用 Keychain(iOS)/ EncryptedSharedPreferences(Android)。绝不要在 UserDefaults/SharedPreferences 中存储令牌、密码或个人身份信息(PII)。如果数据必须在应用重新安装后保留,则使用 Keychain 并设置 kSecAttrAccessibleAfterFirstUnlock。
  • 权限:在显示系统提示前说明用途,妥善处理用户拒绝的情况,绝不要阻止用户使用整个应用。如果需要超过 3 项权限,则在用户使用相关功能时逐步请求——绝不要在启动时批量请求。

代码示例

请参阅 SwiftUI Guide,了解包含 NavigationStack、搜索、分页和下拉刷新功能的完整产品列表。

请参阅 Jetpack Compose Guide,了解包含 Hilt、StateFlow、LazyColumn 和防抖搜索功能的完整产品列表。

请参阅 React Native Guide,了解包含 FlatList、react-query 无限滚动和平台样式的完整产品列表。

请参阅 Flutter Guide,了解包含 Riverpod、ListView.builder、防抖搜索、分页和下拉刷新功能的完整产品列表。

请参阅 Offline-First Architecture,了解离线队列系统(NetInfo + AsyncStorage、Core Data + CloudKit、Room + WorkManager)、冲突解决(LWW、字段级合并、CRDT),以及支持回滚的乐观 UI。

请参阅 Performance Patterns,了解 Hermes 引擎配置、FlatList 优化(getItemLayout、windowSize)、SwiftUI 惰性堆栈和图像缓存、使用 Coil 控制 Compose 重组、电池优化、防止内存泄漏,以及冷启动优化。

请参阅 State Management,了解使用 MMKV 持久化的 Zustand、TanStack Query 乐观变更、通过环境注入使用 SwiftUI @Observable、Compose ViewModel + StateFlow + SavedStateHandle、导航深层链接、身份验证状态机,以及表单验证。

请参阅 Native APIs,了解推送通知(APNs、FCM、react-native-firebase)、相机/照片(CameraX、AVFoundation、vision-camera)、生物识别身份验证(FaceID/TouchID、BiometricPrompt)、后台任务(BGTaskScheduler、WorkManager),以及 App Store 订阅的非事务性权益架构说明。

特定平台的注意事项

iOS 后台任务限制
  • BGAppRefreshTask:系统授予约 30 秒的执行时间。如果任务未在此时间内调用 setTaskCompleted,系统就会终止任务,并降低后续请求的优先级。
  • BGProcessingTask:最长可运行 3 分钟,但仅在设备正在充电且连接 Wi-Fi 时运行。请勿依赖它进行有时效要求的同步。
  • 推送通知唤醒应用(content-available: 1)后,你有约 30 秒的时间。如果任务耗时更长,请改用 URLSession 后台下载。
  • 系统会学习用户行为。如果用户从未在早上 8 点打开你的应用,你早上 8 点的后台刷新就不会运行。请针对刷新遗漏进行设计——应用进入前台时,始终执行完整的补漏同步。
Android 后台限制
  • Doze 模式(Android 6+):屏幕关闭且设备静止后,网络访问会被批量安排在维护窗口中(约每 15 分钟一次,间隔可能逐渐延长至约 1 小时)。带有 NetworkType.CONNECTED 约束的 WorkManager 会推迟到下一个窗口。对于紧急消息,请使用 FCM 高优先级消息(每天约 10 条,超过后会被限流)。
  • 应用待机分桶(Android 9+):应用的级别依次为 Active → Working Set → Frequent → Rare → Restricted。处于 Rare/Restricted 的应用,其作业和闹钟会受到严格限制。如果你的应用处于 Restricted 分桶,WorkManager 周期性工作可能 24 小时才运行一次。
  • 精确闹钟(Android 12+):setExactAndAllowWhileIdle() 需要 SCHEDULE_EXACT_ALARM 权限。用户可以在设置中撤销此权限。安排闹钟前,务必检查 canScheduleExactAlarms(),并在不可用时改用非精确闹钟。
  • 前台服务类型(Android 14+):必须在清单文件中声明 foregroundServiceType。类型包括:camera、location、mediaPlayback、dataSync 等。使用错误的类型会导致崩溃。dataSync 类型的时限为 6 小时。
Flutter 特有的注意事项
  • const 构造函数:无状态组件和不变的组件子树始终使用 const。不使用 const 时,父组件重建会导致 Flutter 重建整个子树。这是 Flutter 性能问题中的头号问题。
  • Keys:列表可能重排、插入或删除时,为列表项使用 ValueKey。不使用键时,Flutter 会错误地复用状态——用户在 TextField A 中输入内容,删除该项后,文本却出现在项目 B 中。
  • 平台通道:Dart 与原生平台(iOS/Android)之间的调用是异步且串行的。如果在紧密循环中调用平台通道(每秒超过 100 次),请将数据批量合并为一次调用。对于大型负载,请使用带二进制编解码器的 BasicMessageChannel。
  • Isolate:Dart 是单线程的。对于耗时超过 16ms 的 CPU 密集型工作(解析大型 JSON 响应、图像处理),请使用 Isolate.run()(Dart 2.19+)或 compute()。切勿在主 Isolate 上解析超过 100KB 的 JSON。
  • 图像缓存:Flutter 默认的 Image 组件会将解码后的图像缓存在内存中,且没有限制。对于包含 50 张以上图像的列表,请使用 cached_network_image 和 memCacheHeight/memCacheWidth 缩小图像,否则内存占用会无限增长。
  • 状态恢复:在 Android 上,系统会积极清理后台应用。请使用 RestorationMixin,或将关键状态持久化到磁盘。如果用户填写了一个包含 10 个字段的表单,切换到另一个应用后再返回,却看到空白表单——这就是一个 bug。
Compose 特有的注意事项
  • 重组:任何捕获可变值的 lambda 都会触发其父项重组。请传递通过 remember 记忆的 lambda,或对开销较大的计算使用 derivedStateOf。使用 Layout Inspector > “Show Recomposition Counts” 查找热点。
  • 稳定性:对于 @Stable 或 @Immutable 类型,Compose 会跳过重组。如果你的数据类使用 List<T>(不稳定),Compose 会在每次更新时重组。请使用 kotlinx.collections.immutable.ImmutableList,或在能保证不可变性的情况下添加 @Immutable 注解。
  • LazyColumn 项目键:在 items(key = { it.id }) 中始终提供 key。不提供键时,列表发生变化会导致滚动位置错乱,动画也会失效。

离线同步决策规则

冲突解决策略
  • 最后写入者胜出(LWW):适用于数据归用户所有且很少同时编辑的情况(用户资料、设置、个人笔记)。实现简单:比较时间戳,最新的胜出。风险:如果两台设备同时编辑,可能会静默丢失数据。
  • 字段级合并:适用于同一记录的不同字段可能在不同设备上被编辑的情况(例如,用户在手机上编辑 name,在平板上编辑 email)。合并无冲突的字段更改,并将冲突字段标记为需手动解决。需要跟踪每个字段的时间戳。
  • 操作转换 / CRDT:仅适用于实时协作编辑(共享文档、协作白板)。实现复杂——请使用库(Yjs、Automerge)。切勿自行实现 OT/CRDT。
  • 排队并重试:适用于只写操作(表单提交、分析事件、聊天消息)。先在本地排队,联网后发送,并使用幂等键重试。由于每个操作彼此独立,因此不会发生冲突。
同步架构决策规则
  • 如果应用的实体类型少于 5 种,且同步仅为用户到服务端(无协作):请使用基于时间戳的增量同步。客户端发送 lastSyncTimestamp,服务端返回该时间戳之后更改的记录。
  • 如果应用有 5–15 种实体类型,或需要服务端向客户端推送:请使用更改令牌(CloudKit)或服务器发送事件。客户端保存同步游标,服务端推送增量。
  • 如果应用支持实时协作(多名用户编辑相同数据):请使用 WebSocket 和 OT/CRDT 库。这比简单同步复杂 10 倍——只有当协作是核心需求时才选择此方案。

应用生命周期决策规则

强制更新策略
  • 关键安全补丁:使用全屏模态框强制阻止用户使用应用。不提供关闭选项。每次启动应用时,都通过远程配置(/api/min-version)进行检查。
  • 破坏性 API 变更:显示带有“立即更新”按钮的阻止式模态框,按钮链接到应用商店。允许 2 周宽限期,在此期间显示可关闭的横幅,之后再阻止用户使用应用。
  • 新功能:绝不因新功能强制更新。显示可关闭的横幅。如果用户忽略 3 次,则 30 天内不再显示。
  • 实现方式:在远程配置(Firebase Remote Config、LaunchDarkly 或简单的 API 端点)中存储 minSupportedVersion。启动时与应用的构建号进行比较。绝不硬编码版本检查。
应用版本之间的迁移
  • 如果本地数据库架构发生变化:使用版本化迁移(Room @Database(version = N)、Core Data 轻量级迁移、Drift 架构版本控制、Hive 类型适配器)。务必测试从版本 N-2 迁移到 N 的情况(用户可能会跳过版本)。
  • 如果身份验证令牌格式发生变化:同时支持新旧令牌格式,持续 2 个发布周期。绝不要在单次发布中让所有会话失效——这会导致用户大量联系支持团队。
  • 如果切换后端端点:使用远程配置逐步切换流量。在 1 周内从 10% → 50% → 100%。如果任一阶段的错误率 >1%,就暂停并调查。

反模式

  • 对当前视图拥有的状态,绝不要使用 @ObservedObject——使用 @StateObject(iOS 15)或搭配 @Observable 的 @State(iOS 17+)。@ObservedObject 仅用于注入的依赖项。
  • 在 Compose 中,绝不要在 ViewModel 之外使用 mutableStateOf——所有可变状态都应存放在 ViewModel 中,UI 通过 collectAsStateWithLifecycle() 观察状态。
  • 在 Flutter 中,绝不要对已销毁的组件调用 setState——异步更新状态前务必检查 mounted。在 Riverpod 中,使用 ref.onDispose() 取消异步任务。
  • 在 React Native 中,绝不要在没有 keyExtractor 和 getItemLayout 的情况下使用 FlatList——如果缺少这些属性,列表超过 50 项时,滚动性能会大幅下降。
  • 绝不要在全局存储中保存导航状态——应使用平台的导航栈。全局导航状态会导致返回按钮异常和深层链接失效。
  • 绝不要使用同步数据库读取阻塞主线程——使用 withContext(Dispatchers.IO)(Android)/后台 actor(iOS)/InteractionManager.runAfterInteractions(React Native)/Isolate.run()(Flutter)。
  • 在 Flutter 中,绝不要在主线程解析大型 JSON(>100KB)——使用 compute() 或 Isolate.run()。在 Android 中,绝不要在主线程执行网络或磁盘 I/O——StrictMode 会在调试构建中捕获此类操作。
  • 绝不要使用没有回退方案的平台专属代码。如果某个插件仅支持 iOS,Android 构建就不能崩溃——应优雅降级,或显示“此平台不支持”。

工作流

第 1 步:平台策略与设置
  • 选择原生或跨平台方案:如果应用需要 AR、自定义相机或小组件,则选择原生方案。如果应用以数据展示和表单为主,且平台专属功能少于 3 项,则选择跨平台方案。
  • 设置最低操作系统版本:iOS 16+(或企业应用使用 15+)、Android API 26+(Android 8.0)。如果需要支持更早的版本,请说明原因。
  • 配置构建工具:Xcode + SwiftPM(iOS)、带版本目录的 Gradle(Android)、搭配 EAS Build 的 Expo(React Native),或搭配 flutter_lints 和 Fastlane/Codemagic 进行 CI 的 Flutter。
第 2 步:架构与设计
  • 如果应用支持离线使用,请先设计本地优先架构,再设计 API 架构。定义冲突解决策略(简单场景使用 LWW,协作场景使用字段级合并)。
  • 在构建 UI 之前,先定义包含所有屏幕、模态框和深层链接路由的导航图。
  • 选择状态管理方案:@Observable + SwiftData(iOS 17+)、ViewModel + StateFlow(Android)、Zustand + TanStack Query(React Native)、Riverpod + Drift/Isar(Flutter)。
第 3 步:开发与集成
  • 优先构建关键用户流程(引导 → 核心操作 → 结果)。在核心流程端到端运行正常之前,不要构建设置、个人资料或次要流程。
  • 一次集成一项平台功能(相机、通知、生物识别)。每项功能都要先在真实设备上测试,再进行下一项。
  • 在首次发布到 TestFlight / 内部测试轨道之前,添加崩溃报告(Crashlytics / Sentry)和基本分析功能。
第 4 步:测试与部署
  • 在真实设备上测试:至少使用 2 台 iOS 设备(支持的最旧设备 + 当前设备)和 3 台 Android 设备(低端、中端、旗舰机型)。
  • 在 CI 上为关键流程运行 UI 测试。对 ViewModel / 存储库进行单元测试,覆盖率 >80%。
  • 设置 Fastlane(iOS)或 Gradle Play Publisher(Android),以自动提交至应用商店。

自我验证流程

完成任何移动端实现后,在提交审核之前进行验证:

  • 在真实设备上运行应用(不只是模拟器)。模拟器会掩盖性能、内存和手势问题。
  • 离线测试核心流程:开启飞行模式,执行操作,重新启用网络,并验证同步。如果应用离线时崩溃或丢失数据,就还没准备好。
  • 在中端设备上分析内存。如果峰值内存超过 200MB,发布前先调查原因。
  • 在支持的最旧设备上测量冷启动时间。如果 >2s,则将初始化延迟到首帧渲染之后。
  • 通过逐一用手指而非鼠标光标点击每个交互元素,确认所有触控目标均为 >=44pt(iOS)/ >=48dp(Android)。
  • 检查每个屏幕的深色模式。如果有任何文本无法读取或元素不可见,请在合并前修复。
  • 使用 Dynamic Type(iOS)/ Font Scale 200%(Android)进行测试。如果文本被截断或重叠,请修复布局。
  • 在核心流程中启用 VoiceOver(iOS)/ TalkBack(Android)运行应用。必须为每个交互元素提供有意义的语音提示。
  • 检查日志中是否出现敏感数据(令牌、密码、PII)。在流程运行期间使用 adb logcat(Android)或 Console.app(iOS)。

故障恢复

  • 应用启动时崩溃:检查崩溃日志,找到确切代码行。常见原因:强制解包 nil(Swift)、未初始化的 lateinit(Kotlin)、缺少原生模块(React Native)。如果崩溃由第三方库引起,请固定到上一个版本并提交问题。
  • Xcode/Gradle 更新后构建失败:清理派生数据(rm -rf ~/Library/Developer/Xcode/DerivedData)或 Gradle 缓存(./gradlew clean)。如果仍然失败,请检查发行说明,了解构建工具中的破坏性变更。问题解决前,在项目中固定工具链版本。
  • 设备与模拟器上的 UI 渲染效果不同:模拟器使用 Mac GPU。请在真实设备上测试。对于布局问题,请检查是否使用了硬编码的像素值(改用 pt/dp),以及安全区域内边距。
  • 添加功能后性能下降:使用 Instruments(iOS)或 Android Studio Profiler 进行分析。检查是否存在:主线程阻塞、过度重组/重复渲染、加载大型图像时未使用缓存,或订阅/观察者泄漏。
  • 未收到推送通知:验证:(1)APNs/FCM 令牌有效;(2)bundle ID / package name 正确;(3)证书/密钥未过期;(4)设备未处于会抑制后台活动的低电量模式。调试应用代码前,先通过 curl 向 APNs/FCM 手动发送推送进行测试。
  • 应用被 App Store / Play Store 拒绝:仔细阅读拒绝原因。常见原因:缺少隐私政策 URL、App Privacy 详情不完整(iOS)、缺少数据安全表单(Android),或未提供正当理由却使用后台定位。修正元数据,不要猜测。

现有应用导览

接手或加入现有移动项目时:

  1. 构建并运行(15 分钟)——克隆项目、安装依赖项、构建,并在真实设备上运行。如果失败,先修复构建问题。
  2. 识别架构模式(10 分钟)——MVC、MVVM、MVI,还是结构不明确?检查:状态存放在哪里?数据如何从 API 流向 UI?
  3. 梳理导航图(10 分钟)——列出所有屏幕及其连接方式。检查深层链接处理程序,并记录正在使用的导航库。
  4. 检查依赖项列表(5 分钟)——Podfile/SPM(iOS)、build.gradle(Android)、package.json(React Native)。标记过时或已弃用的依赖项。
  5. 运行现有测试(5 分钟)——记录覆盖率、测试类型,以及尚未测试的流程。
  6. 检查平台特定技术债务(5 分钟)——已弃用的 API(检查 Xcode 警告和 Android lint)、缺失的权限声明、硬编码字符串(本地化就绪情况)。
  7. 分析应用性能(10 分钟)——冷启动时间、内存用量,以及最复杂列表屏幕的滚动性能。这些数据就是你的基准。

脚本

scripts/check_app_size.sh

分析移动应用构建输出目录中的体积问题。接收构建输出目录作为参数,查找应用二进制文件/包(.app、.apk、.aab、.ipa),报告总大小、最大文件,以及按类别划分的资源明细。如果总大小超过常见阈值(iOS 为 50MB,Android 为 150MB),则发出警告。

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
scripts/check_permissions.py

从 AndroidManifest.xml 或 Info.plist 中提取并审查权限。识别请求的权限,标记可能具有危险性的权限(CAMERA、LOCATION、CONTACTS 等)并附上说明,同时报告权限总数。支持 JSON 输出。

scripts/check_permissions.py app/src/main/AndroidManifest.xml
scripts/check_permissions.py --format json ios/Runner/Info.plist

评论

支持 Markdown