mcpField
A
android-native-dev

android-native-dev

github.com/minimax-ai开发13.7k2.7k 次安装28 周前更新
代码仓库

关于 android-native-dev

android-native-dev 是面向 Android 应用开发的技能,涵盖 Kotlin 与 Jetpack Compose。它为开发者提供项目配置、编码规范、界面实践和 Gradle 构建排错指导。

主要功能

  1. 01Android 项目初始化与 Gradle 配置
  2. 02Kotlin 编码与协程实践
  3. 03Jetpack Compose 状态和界面指导
  4. 04构建变体与产品风味配置
  5. 05构建错误诊断与调试

适用场景

  1. 01从空目录启动 Android 项目
  2. 02为开发、预发布和生产版本添加产品风味
  3. 03检查 Kotlin 与 Compose 代码中的常见问题

安装

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

$npx skills add https://github.com/minimax-ai/skills --skill android-native-dev

安装后在 Claude Code 中输入/android-native-dev确认已加载。

包含的文件

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

references/
accessibility.md
adaptive-screens.md
design-style-guide.md
functional-requirements.md
motion-system.md
performance-stability.md
privacy-security.md
testing.md
visual-design.md

详细介绍

1. 项目场景评估

开始开发前,先评估当前项目状态:

场景特征方法
空目录不存在任何文件需要完整初始化,包括 Gradle Wrapper
已有 Gradle Wrapper存在 gradlew 和 gradle/wrapper/直接使用 ./gradlew 构建
Android Studio 项目项目结构完整,可能缺少 wrapper检查 wrapper,如有需要,运行 gradle wrapper
项目不完整存在部分文件检查缺少的文件,补全配置

关键原则:

  • 编写业务逻辑前,确保 ./gradlew assembleDebug 能够成功运行
  • 如果缺少 gradle.properties,先创建该文件并配置 AndroidX
1.1 必需文件检查清单
MyApp/
├── gradle.properties          # Configure AndroidX and other settings
├── settings.gradle.kts
├── build.gradle.kts           # Root level
├── gradle/wrapper/
│   └── gradle-wrapper.properties
├── app/
│   ├── build.gradle.kts       # Module level
│   └── src/main/
│       ├── AndroidManifest.xml
│       ├── java/com/example/myapp/
│       │   └── MainActivity.kt
│       └── res/
│           ├── values/
│           │   ├── strings.xml
│           │   ├── colors.xml
│           │   └── themes.xml
│           └── mipmap-*/       # App icons

2. 项目配置

2.1 gradle.properties
# Required configuration
android.useAndroidX=true
android.enableJetifier=true

# Build optimization
org.gradle.parallel=true
kotlin.code.style=official

# JVM memory settings (adjust based on project size)
# Small projects: 2048m, Medium: 4096m, Large: 8192m+
# org.gradle.jvmargs=-Xmx4096m -Dfile.encoding=UTF-8

注意:如果构建时遇到 OutOfMemoryError,请增大 -Xmx 的值。依赖较多的大型项目可能需要 8GB 或更多。

2.2 依赖声明规范
dependencies {
    // Use BOM to manage Compose versions
    implementation(platform("androidx.compose:compose-bom:2024.02.00"))
    implementation("androidx.compose.ui:ui")
    implementation("androidx.compose.material3:material3")
    
    // Activity & ViewModel
    implementation("androidx.activity:activity-compose:1.8.2")
    implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.7.0")
}
2.3 构建变体和产品风味

产品风味可用于创建应用的不同版本(例如免费版/付费版、开发版/预发布版/生产版)。

在 app/build.gradle.kts 中配置:

android {
    // Define flavor dimensions
    flavorDimensions += "environment"
    
    productFlavors {
        create("dev") {
            dimension = "environment"
            applicationIdSuffix = ".dev"
            versionNameSuffix = "-dev"
            
            // Different config values per flavor
            buildConfigField("String", "API_BASE_URL", "\"https://dev-api.example.com\"")
            buildConfigField("Boolean", "ENABLE_LOGGING", "true")
            
            // Different resources
            resValue("string", "app_name", "MyApp Dev")
        }
        
        create("staging") {
            dimension = "environment"
            applicationIdSuffix = ".staging"
            versionNameSuffix = "-staging"
            
            buildConfigField("String", "API_BASE_URL", "\"https://staging-api.example.com\"")
            buildConfigField("Boolean", "ENABLE_LOGGING", "true")
            resValue("string", "app_name", "MyApp Staging")
        }
        
        create("prod") {
            dimension = "environment"
            // No suffix for production
            
            buildConfigField("String", "API_BASE_URL", "\"https://api.example.com\"")
            buildConfigField("Boolean", "ENABLE_LOGGING", "false")
            resValue("string", "app_name", "MyApp")
        }
    }
    
    buildTypes {
        debug {
            isDebuggable = true
            isMinifyEnabled = false
        }
        release {
            isDebuggable = false
            isMinifyEnabled = true
            proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
        }
    }
}

构建变体命名:{flavor}{BuildType} → 例如 devDebug、prodRelease

Gradle 构建命令:

# List all available build variants
./gradlew tasks --group="build"

# Build specific variant (flavor + buildType)
./gradlew assembleDevDebug        # Dev flavor, Debug build
./gradlew assembleStagingDebug    # Staging flavor, Debug build
./gradlew assembleProdRelease     # Prod flavor, Release build

# Build all variants of a specific flavor
./gradlew assembleDev             # All Dev variants (debug + release)
./gradlew assembleProd            # All Prod variants

# Build all variants of a specific build type
./gradlew assembleDebug           # All flavors, Debug build
./gradlew assembleRelease         # All flavors, Release build

# Install specific variant to device
./gradlew installDevDebug
./gradlew installProdRelease

# Build and install in one command
./gradlew installDevDebug && adb shell am start -n com.example.myapp.dev/.MainActivity

在代码中访问 BuildConfig:

注意:从 AGP 8.0 开始,默认不再生成 BuildConfig。必须在 build.gradle.kts 中显式启用:

android {
    buildFeatures {
        buildConfig = true
    }
}
// Use build config values in your code
val apiUrl = BuildConfig.API_BASE_URL
val isLoggingEnabled = BuildConfig.ENABLE_LOGGING

if (BuildConfig.DEBUG) {
    // Debug-only code
}

特定风味的源集:

app/src/
├── main/           # Shared code for all flavors
├── dev/            # Dev-only code and resources
│   ├── java/
│   └── res/
├── staging/        # Staging-only code and resources
├── prod/           # Prod-only code and resources
├── debug/          # Debug build type code
└── release/        # Release build type code

多个风味维度(例如环境 + 等级):

android {
    flavorDimensions += listOf("environment", "tier")
    
    productFlavors {
        create("dev") { dimension = "environment" }
        create("prod") { dimension = "environment" }
        
        create("free") { dimension = "tier" }
        create("paid") { dimension = "tier" }
    }
}
// Results in: devFreeDebug, devPaidDebug, prodFreeRelease, etc.

3. Kotlin 开发规范

3.1 命名约定
类型约定示例
类/接口PascalCaseUserRepository、MainActivity
函数/变量camelCasegetUserName()、isLoading
常量SCREAMING_SNAKEMAX_RETRY_COUNT
包小写com.example.myapp
ComposablePascalCase@Composable fun UserCard()
3.2 代码规范(重要)

空安全:

// ❌ Avoid: Non-null assertion !! (may crash)
val name = user!!.name

// ✅ Recommended: Safe call + default value
val name = user?.name ?: "Unknown"

// ✅ Recommended: let handling
user?.let { processUser(it) }

异常处理:

// ❌ Avoid: Random try-catch in business layer swallowing exceptions
fun loadData() {
    try {
        val data = api.fetch()
    } catch (e: Exception) {
        // Swallowing exception, hard to debug
    }
}

// ✅ Recommended: Let exceptions propagate, handle at appropriate layer
suspend fun loadData(): Result<Data> {
    return try {
        Result.success(api.fetch())
    } catch (e: Exception) {
        Result.failure(e)  // Wrap and return, let caller decide handling
    }
}

// ✅ Recommended: Unified handling in ViewModel
viewModelScope.launch {
    runCatching { repository.loadData() }
        .onSuccess { _uiState.value = UiState.Success(it) }
        .onFailure { _uiState.value = UiState.Error(it.message) }
}
3.3 线程与协程(关键)

线程选择原则:

操作类型线程说明
UI 更新Dispatchers.Main更新 View、State、LiveData
网络请求Dispatchers.IOHTTP 调用、API 请求
文件 I/ODispatchers.IO本地存储、数据库操作
计算密集型任务Dispatchers.DefaultJSON 解析、排序、加密

正确用法:

// In ViewModel
viewModelScope.launch {
    // Default Main thread, can update UI State
    _uiState.value = UiState.Loading
    
    // Switch to IO thread for network request
    val result = withContext(Dispatchers.IO) {
        repository.fetchData()
    }
    
    // Automatically returns to Main thread, update UI
    _uiState.value = UiState.Success(result)
}

// In Repository (suspend functions should be main-safe)
suspend fun fetchData(): Data = withContext(Dispatchers.IO) {
    api.getData()
}

常见错误:

// ❌ Wrong: Updating UI on IO thread
viewModelScope.launch(Dispatchers.IO) {
    val data = api.fetch()
    _uiState.value = data  // Crash or warning!
}

// ❌ Wrong: Executing time-consuming operation on Main thread
viewModelScope.launch {
    val data = api.fetch()  // Blocking main thread! ANR
}

// ✅ Correct: Fetch on IO, update on Main
viewModelScope.launch {
    val data = withContext(Dispatchers.IO) { api.fetch() }
    _uiState.value = data
}
3.4 可见性规则
// Default is public, declare explicitly when needed
class UserRepository {           // public
    private val cache = mutableMapOf<String, User>()  // Visible only within class
    internal fun clearCache() {} // Visible only within module
}

// data class properties are public by default, be careful when used across modules
data class User(
    val id: String,       // public
    val name: String
)
3.5 常见语法问题
// ❌ Wrong: Accessing uninitialized lateinit
class MyViewModel : ViewModel() {
    lateinit var data: String
    fun process() = data.length  // May crash
}

// ✅ Correct: Use nullable or default value
class MyViewModel : ViewModel() {
    var data: String? = null
    fun process() = data?.length ?: 0
}

// ❌ Wrong: Using return in lambda
list.forEach { item ->
    if (item.isEmpty()) return  // Returns from outer function!
}

// ✅ Correct: Use return@forEach
list.forEach { item ->
    if (item.isEmpty()) return@forEach
}
3.6 服务端响应数据类的字段必须可空
// ❌ Wrong: Fields declared as non-null (server may not return them)
data class UserResponse(
    val id: String = "",
    val name: String = "",
    val avatar: String = ""
)

// ✅ Correct: All fields declared as nullable
data class UserResponse(
    @SerializedName("id")
    val id: String? = null,
    @SerializedName("name")
    val name: String? = null,
    @SerializedName("avatar")
    val avatar: String? = null
)
3.7 生命周期资源管理
// ❌ Wrong: Only adding Observer, not removing
class MyView : View {
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        activity?.lifecycle?.addObserver(this)
    }
    // Memory leak!
}

// ✅ Correct: Paired add and remove
class MyView : View {
    override fun onAttachedToWindow() {
        super.onAttachedToWindow()
        activity?.lifecycle?.addObserver(this)
    }

    override fun onDetachedFromWindow() {
        activity?.lifecycle?.removeObserver(this)
        super.onDetachedFromWindow()
    }
}
3.8 日志级别用法
import android.util.Log

// Info: Key checkpoints in normal flow
Log.i(TAG, "loadData: started, userId = $userId")

// Warning: Abnormal but recoverable situations
Log.w(TAG, "loadData: cache miss, fallback to network")

// Error: Failure/error situations
Log.e(TAG, "loadData failed: ${error.message}")
级别使用场景
i (Info)正常流程、方法入口、关键参数
w (Warning)可恢复的异常、回退处理、返回 null
e (Error)请求失败、捕获到的异常、不可恢复的错误

4. Jetpack Compose 规范

4.1 @Composable 上下文规则
// ❌ Wrong: Calling Composable from non-Composable function
fun showError(message: String) {
    Text(message)  // Compile error!
}

// ✅ Correct: Mark as @Composable
@Composable
fun ErrorMessage(message: String) {
    Text(message)
}

// ❌ Wrong: Using suspend outside LaunchedEffect
@Composable
fun MyScreen() {
    val data = fetchData()  // Error!
}

// ✅ Correct: Use LaunchedEffect
@Composable
fun MyScreen() {
    var data by remember { mutableStateOf<Data?>(null) }
    LaunchedEffect(Unit) {
        data = fetchData()
    }
}
4.2 状态管理
// Basic State
var count by remember { mutableStateOf(0) }

// Derived State (avoid redundant computation)
val isEven by remember { derivedStateOf { count % 2 == 0 } }

// Persist across recomposition (e.g., scroll position)
val scrollState = rememberScrollState()

// State in ViewModel
class MyViewModel : ViewModel() {
    private val _uiState = MutableStateFlow(UiState())
    val uiState: StateFlow<UiState> = _uiState.asStateFlow()
}
4.3 Compose 常见错误
// ❌ Wrong: Creating objects in Composable (created on every recomposition)
@Composable
fun MyScreen() {
    val viewModel = MyViewModel()  // Wrong!
}

// ✅ Correct: Use viewModel() or remember
@Composable
fun MyScreen(viewModel: MyViewModel = viewModel()) {
    // ...
}

5. 资源与图标

5.1 应用图标要求

必须提供多种分辨率的图标:

目录尺寸用途
mipmap-mdpi48x48基准
mipmap-hdpi72x721.5x
mipmap-xhdpi96x962x
mipmap-xxhdpi144x1443x
mipmap-xxxhdpi192x1924x

建议使用自适应图标(Android 8+):


<adaptive-icon>
    <background android:drawable="@color/ic_launcher_background"/>
    <foreground android:drawable="@mipmap/ic_launcher_foreground"/>
</adaptive-icon>
5.2 资源命名约定
类型前缀示例
布局layout_layout_main.xml
图像ic_、img_、bg_ic_user.png
颜色color_color_primary
字符串-app_name、btn_submit
5.3 避免使用 Android 保留名称(重要)

变量名、资源 ID、颜色、图标和 XML 元素不得使用 Android 保留字或系统资源名称。使用保留名称会导致构建错误或资源冲突。

应避免使用的常见保留名称:

类别保留名称(请勿使用)
颜色background、foreground、transparent、white、black
图标/可绘制对象icon、logo、image、drawable
视图view、text、button、layout、container
属性id、name、type、style、theme、color
系统app、android、content、data、action

示例:


<color name="background">#FFFFFF</color>
<color name="icon">#000000</color>


<color name="app_background">#FFFFFF</color>
<color name="icon_primary">#000000</color>
// ❌ Wrong: Variable names conflict with system
val icon = R.drawable.my_icon
val background = Color.White

// ✅ Correct: Use descriptive names
val appIcon = R.drawable.my_icon
val screenBackground = Color.White

<ImageView android:src="@drawable/icon" />


<ImageView android:src="@drawable/ic_home" />

6. 构建错误诊断与修复

6.1 常见错误速查
错误关键字原因修复方法
Unresolved reference缺少导入或未定义检查导入,确认依赖项
Type mismatch类型不兼容检查参数类型,添加类型转换
Cannot access可见性问题检查 public/private/internal
@Composable invocations可组合上下文错误确保调用方也带有 @Composable
Duplicate class依赖冲突使用 ./gradlew dependencies 进行排查
AAPT: error资源文件错误检查 XML 语法和资源引用
6.2 修复最佳实践
  1. 先阅读完整的错误消息:定位文件和行号
  2. 检查最近的更改:问题通常出在最新修改中
  3. 清理并构建:./gradlew clean assembleDebug
  4. 检查依赖项版本:版本冲突是常见原因
  5. 必要时刷新依赖项:清除缓存并重新构建
6.3 调试命令
# Clean and build
./gradlew clean assembleDebug

# View dependency tree (investigate conflicts)
./gradlew :app:dependencies

# View detailed errors
./gradlew assembleDebug --stacktrace

# Refresh dependencies
./gradlew --refresh-dependencies

7. Material Design 3 指南

检查 Android UI 文件是否符合 Material Design 3 指南和 Android 最佳实践。

设计理念
M3 核心原则
原则描述
个性化根据用户偏好和壁纸动态调整颜色
自适应适配所有屏幕尺寸和设备形态
富有表现力通过鲜明的颜色和字体展现个性
无障碍为所有用户提供包容性的设计
M3 Expressive(最新版本)

最新演进通过以下方式打造以情感驱动的 UX:

  • 鲜活、动态的颜色
  • 直观的运动物理效果
  • 自适应组件
  • 灵活的字体排版
  • 对比鲜明的形状(新增 35 种形状选项)
应用风格选择

关键决策:根据应用类别和目标受众匹配视觉风格。

应用类别视觉风格主要特点
实用工具/工具极简主义简洁、高效、中性色彩
金融/银行专业可信保守的配色,注重安全
健康/身心健康平和自然柔和的颜色、有机形状
儿童(3–5 岁)简单有趣明亮的颜色、大尺寸触控目标(56dp+)
儿童(6–12 岁)趣味十足、引人入胜鲜艳的色彩、游戏化反馈
社交/娱乐富有表现力品牌驱动、丰富的手势交互
生产力简洁、专注极简、高对比度
电子商务以转化为导向清晰的 CTA、便于快速浏览

详细的风格配置请参阅设计风格指南。

快速参考:关键规范
颜色对比度要求
元素最低比率
正文文本4.5:1
大号文本(18sp+)3:1
UI 组件3:1
触控目标
类型尺寸
最小值48 × 48dp
建议值(主要操作)56 × 56dp
儿童应用56dp+
目标之间的间距至少 8dp
8dp 网格系统
令牌值用途
xs4dp图标内边距
sm8dp紧凑间距
md16dp默认内边距
lg24dp区块间距
xl32dp大间隔
xxl48dp屏幕边距
字体排版比例(摘要)
类别尺寸
Display57sp、45sp、36sp
Headline32sp、28sp、24sp
Title22sp、16sp、14sp
Body16sp、14sp、12sp
Label14sp、12sp、11sp
动画时长
类型时长
微动画(波纹)50-100ms
短动画(简单)100-200ms
中等动画(展开/收起)200-300ms
长动画(复杂)300-500ms
组件尺寸
组件高度最小宽度
按钮40dp64dp
FAB56dp56dp
文本字段56dp280dp
应用栏64dp-
底部导航栏80dp-
反模式(必须避免)
UI 反模式
  • 底部导航项超过 5 个
  • 同一屏幕上有多个 FAB
  • 触控目标小于 48dp
  • 间距不一致(不是 8dp 的倍数)
  • 不支持深色主题
  • 未检查对比度的彩色背景文字
性能反模式
  • 启动时间超过 2 秒且没有进度指示器
  • 帧率低于 60 FPS(每帧超过 16ms)
  • 崩溃率超过 1.09%(Google Play 阈值)
  • ANR 率超过 0.47%(Google Play 阈值)
无障碍反模式
  • 交互元素缺少 contentDescription
  • 标签中包含元素类型(例如,用“保存”代替“保存按钮”)
  • 儿童应用中使用复杂手势
  • 面向不识字用户的应用使用纯文本按钮
检查清单
  • 符合 8dp 间距网格
  • 触控目标至少为 48dp
  • 正确使用字体排版比例
  • 符合颜色对比度要求(文本为 4.5:1+)
  • 支持深色主题
  • 所有交互元素均设置 contentDescription
  • 启动时间小于 2 秒,或显示进度
  • 视觉风格与应用类别相符
设计参考
主题参考资料
颜色、字体排版、间距、形状视觉设计
动画与过渡动态效果系统
无障碍指南无障碍
大屏幕与折叠屏设备自适应屏幕
Android Vitals 与性能性能与稳定性
隐私与安全隐私与安全
音频、视频、通知功能要求
按类别划分的应用风格设计风格指南

8. 测试

注意:只有在用户明确要求测试时,才添加测试依赖。

经过充分测试的 Android 应用采用分层测试:使用快速本地单元测试验证逻辑,使用插桩测试验证 UI 和集成,并使用 Gradle Managed Devices 在任何机器(包括 CI)上可重复地运行模拟器。

8.1 测试依赖项

添加测试依赖项前,请检查项目现有版本,以避免冲突:

  1. 检查 gradle/libs.versions.toml——如果存在,请按项目的版本目录风格添加测试依赖项
  2. 检查现有 build.gradle.kts 中是否已固定依赖项版本
  3. 根据下表匹配版本系列

版本对齐规则:

测试依赖项必须与之对齐检查方法
kotlinx-coroutines-test项目的 kotlinx-coroutines-core 版本在构建文件或版本目录中搜索 kotlinx-coroutines
compose-ui-test-junit4项目的 Compose BOM 或 compose-compiler在构建文件中搜索 compose-bom 或 compose.compiler
espresso-*所有 Espresso 构件必须使用相同版本在构建文件中搜索 espresso
androidx.test:runner、rules、ext:junit应使用兼容的 AndroidX Test 版本在构建文件中搜索 androidx.test
mockk必须支持项目的 Kotlin 版本检查根目录 build.gradle.kts 或版本目录中的 kotlin 版本

依赖项参考——仅添加所需的分组:

dependencies {
    // --- Local unit tests (src/test/) ---
    testImplementation("junit:junit:<version>")                          // 4.13.2+
    testImplementation("org.robolectric:robolectric:<version>")          // 4.16.1+
    testImplementation("io.mockk:mockk:<version>")                      // match Kotlin version
    testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:<version>")  // match coroutines-core
    testImplementation("androidx.arch.core:core-testing:<version>")      // InstantTaskExecutorRule for LiveData
    testImplementation("app.cash.turbine:turbine:<version>")             // Flow/StateFlow testing

    // --- Instrumentation tests (src/androidTest/) ---
    androidTestImplementation("androidx.test.ext:junit:<version>")
    androidTestImplementation("androidx.test:runner:<version>")
    androidTestImplementation("androidx.test:rules:<version>")
    androidTestImplementation("androidx.test.espresso:espresso-core:<version>")
    androidTestImplementation("androidx.test.espresso:espresso-contrib:<version>")   // RecyclerView, Drawer
    androidTestImplementation("androidx.test.espresso:espresso-intents:<version>")   // Intent verification
    androidTestImplementation("androidx.test.espresso:espresso-idling-resource:<version>")
    androidTestImplementation("androidx.test.uiautomator:uiautomator:<version>")

    // --- Compose UI tests (only if project uses Compose) ---
    androidTestImplementation("androidx.compose.ui:ui-test-junit4")      // version from Compose BOM
    debugImplementation("androidx.compose.ui:ui-test-manifest")          // required for createComposeRule
}

注意:如果项目使用 Compose BOM,则 ui-test-junit4 和 ui-test-manifest 无需指定显式版本——由 BOM 管理。

在 android 块中启用 Robolectric 资源支持:

android {
    testOptions {
        unitTests.isIncludeAndroidResources = true  // required for Robolectric
    }
}
8.2 分层测试
层级位置运行环境速度用途
单元测试(JUnit)src/test/JVM~毫秒ViewModel、仓库、映射器、验证器
单元测试 + Robolectricsrc/test/JVM + 模拟 Android~100 毫秒需要 Context、资源、SharedPrefs 的代码
Compose UI(本地)src/test/JVM + Robolectric~100 毫秒可组合项渲染与交互
Espressosrc/androidTest/设备/模拟器~秒基于 View 的 UI 流程、Intents、数据库集成
Compose UI(设备)src/androidTest/设备/模拟器~秒使用真实渲染的完整 Compose UI 流程
UI Automatorsrc/androidTest/设备/模拟器~秒系统对话框、通知、多应用场景
托管设备src/androidTest/Gradle 管理的 AVD~分钟(首次运行)CI、跨 API 级别的矩阵测试

请参阅 测试,了解详细示例、代码模式和 Gradle Managed Device 配置。

8.3 测试命令
# Local unit tests (fast, no emulator)
./gradlew test                          # all modules
./gradlew :app:testDebugUnitTest        # app module, debug variant

# Single test class
./gradlew :app:testDebugUnitTest --tests "com.example.myapp.CounterViewModelTest"

# Instrumentation tests (requires device or managed device)
./gradlew connectedDebugAndroidTest     # on connected device
./gradlew pixel6Api34DebugAndroidTest   # on managed device

# Both together
./gradlew test connectedDebugAndroidTest

# Test with coverage report (JaCoCo)
./gradlew testDebugUnitTest jacocoTestReport

评论

支持 Markdown