feat(app): E.1 内测 APK 构建配置 — Android 品牌化 + 签名 + ProGuard
Some checks failed
Main Merge / backend (push) Has been cancelled
Main Merge / frontend (push) Has been cancelled

- 更新应用名称为「暖记」(AndroidManifest + strings.xml)
- 添加必要权限: INTERNET, CAMERA, READ_MEDIA_IMAGES, READ_EXTERNAL_STORAGE
- 生成 release 签名密钥 (RSA 2048, 10000 天有效期)
- 配置 ProGuard/R8 代码混淆 + 资源压缩
- 品牌化启动页: 奶油白背景 + 珊瑚色圆形「暖」字 logo
- 品牌化应用图标: 各密度 mipmap (mdpi~xxxhdpi)
- 添加阿里云 Maven 镜像加速依赖下载
- AGP 9.x 兼容: 自动为旧 Flutter 插件注入 namespace
- Gradle 性能优化: 并行编译 + 构建缓存
This commit is contained in:
iven
2026-06-07 20:17:19 +08:00
parent ec8a04c80a
commit 7af7cd64e6
20 changed files with 155 additions and 35 deletions

View File

@@ -1,9 +1,19 @@
import java.util.Properties
import java.io.FileInputStream
plugins { plugins {
id("com.android.application") id("com.android.application")
// The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins.
id("dev.flutter.flutter-gradle-plugin") id("dev.flutter.flutter-gradle-plugin")
} }
// 从 key.properties 加载签名配置
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
keystoreProperties.load(FileInputStream(keystorePropertiesFile))
}
android { android {
namespace = "com.nuanji.nuanji_app" namespace = "com.nuanji.nuanji_app"
compileSdk = flutter.compileSdkVersion compileSdk = flutter.compileSdkVersion
@@ -15,21 +25,33 @@ android {
} }
defaultConfig { defaultConfig {
// TODO: Specify your own unique Application ID (https://developer.android.com/studio/build/application-id.html).
applicationId = "com.nuanji.nuanji_app" applicationId = "com.nuanji.nuanji_app"
// You can update the following values to match your application needs. minSdk = 24 // Android 7.0+ — 支持 Isar 原生库 + CameraX
// For more information, see: https://flutter.dev/to/review-gradle-config.
minSdk = flutter.minSdkVersion
targetSdk = flutter.targetSdkVersion targetSdk = flutter.targetSdkVersion
versionCode = flutter.versionCode versionCode = flutter.versionCode
versionName = flutter.versionName versionName = flutter.versionName
} }
signingConfigs {
create("release") {
if (keystorePropertiesFile.exists()) {
keyAlias = keystoreProperties["keyAlias"] as String
keyPassword = keystoreProperties["keyPassword"] as String
storeFile = file(keystoreProperties["storeFile"] as String)
storePassword = keystoreProperties["storePassword"] as String
}
}
}
buildTypes { buildTypes {
release { release {
// TODO: Add your own signing config for the release build. signingConfig = signingConfigs.getByName("release")
// Signing with the debug keys for now, so `flutter run --release` works. isMinifyEnabled = true
signingConfig = signingConfigs.getByName("debug") isShrinkResources = true
proguardFiles(
getDefaultProguardFile("proguard-android-optimize.txt"),
"proguard-rules.pro"
)
} }
} }
} }

38
app/android/app/proguard-rules.pro vendored Normal file
View File

@@ -0,0 +1,38 @@
# 暖记 ProGuard 规则
# 保留 Flutter 引擎
-keep class io.flutter.app.** { *; }
-keep class io.flutter.plugin.** { *; }
-keep class io.flutter.util.** { *; }
-keep class io.flutter.view.** { *; }
-keep class io.flutter.** { *; }
-keep class io.flutter.plugins.** { *; }
# Isar 数据库 保留 native 调用
-keep class com.isor.** { *; }
-keep class isar.** { *; }
-keepclassmembers class ** {
native <methods>;
}
# Dio 网络库
-dontwarn okhttp3.**
-dontwarn okio.**
-keep class okhttp3.** { *; }
-keep interface okhttp3.** { *; }
# Gson / JSON 序列化
-keepattributes Signature
-keepattributes *Annotation*
-keep class com.google.gson.** { *; }
# freezed 生成的类 保留 JSON 序列化
-keepclassmembers class **.models.** {
*** fromJson(...);
*** toJson();
}
# 保留所有序列化相关类
-keepclassmembers class * {
*** INSTANCE;
*** Companion;
}

View File

@@ -1,9 +1,20 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"> <manifest xmlns:android="http://schemas.android.com/apk/res/android">
<!-- 网络权限 — API 同步、图片上传 -->
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<!-- 相机权限 — 日记拍照 -->
<uses-permission android:name="android.permission.CAMERA"/>
<!-- 照片权限 — Android 13+ 细化媒体权限 -->
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<!-- 照片权限 — Android 12 及以下 -->
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" android:maxSdkVersion="32"/>
<application <application
android:label="nuanji_app" android:label="@string/app_name"
android:name="${applicationName}" android:name="${applicationName}"
android:icon="@mipmap/ic_launcher" android:icon="@mipmap/ic_launcher"
android:networkSecurityConfig="@xml/network_security_config"> android:networkSecurityConfig="@xml/network_security_config"
android:usesCleartextTraffic="false">
<activity <activity
android:name=".MainActivity" android:name=".MainActivity"
android:exported="true" android:exported="true"

View File

@@ -1,12 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- Modify this file to customize your launch splash screen --> <!-- 暖记启动页 — 奶油白背景 + 居中 logo -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android"> <layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@android:color/white" /> <item android:drawable="@color/bg_light" />
<item>
<!-- You can insert your own image assets here -->
<!-- <item>
<bitmap <bitmap
android:gravity="center" android:gravity="center"
android:src="@mipmap/launch_image" /> android:src="@drawable/launch_logo" />
</item> --> </item>
</layer-list> </layer-list>

View File

@@ -0,0 +1,10 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- 暖记启动页 (深色模式) — 深色背景 + 居中 logo -->
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@color/bg_dark" />
<item>
<bitmap
android:gravity="center"
android:src="@drawable/launch_logo" />
</item>
</layer-list>

Binary file not shown.

After

Width:  |  Height:  |  Size: 24 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 544 B

After

Width:  |  Height:  |  Size: 4.3 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 442 B

After

Width:  |  Height:  |  Size: 2.8 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 721 B

After

Width:  |  Height:  |  Size: 5.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.0 KiB

After

Width:  |  Height:  |  Size: 8.9 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.4 KiB

After

Width:  |  Height:  |  Size: 12 KiB

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="launch_bg">#1A1614</color>
</resources>

View File

@@ -1,17 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is on --> <!-- 深色模式启动主题 — 深色背景 -->
<style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Black.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when <item name="android:windowBackground">@drawable/launch_background_dark</item>
the Flutter engine draws its first frame --> <item name="android:statusBarColor">@color/bg_dark</item>
<item name="android:windowBackground">@drawable/launch_background</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Black.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">暖记</string>
</resources>

View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<!-- 暖记设计系统颜色 -->
<color name="bg_light">#FFF8F0</color> <!-- 奶油白背景 -->
<color name="bg_dark">#1A1614</color> <!-- 深色背景 -->
<color name="accent">#E07A5F</color> <!-- 珊瑚色主色 -->
<color name="fg_light">#2D2420</color> <!-- 浅色模式文字 -->
<color name="fg_dark">#F0E8DF</color> <!-- 深色模式文字 -->
</resources>

View File

@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<string name="app_name">暖记</string>
</resources>

View File

@@ -1,17 +1,10 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<resources> <resources>
<!-- Theme applied to the Android Window while the process is starting when the OS's Dark Mode setting is off --> <!-- 浅色模式启动主题 — 奶油白背景 -->
<style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="LaunchTheme" parent="@android:style/Theme.Light.NoTitleBar">
<!-- Show a splash screen on the activity. Automatically removed when
the Flutter engine draws its first frame -->
<item name="android:windowBackground">@drawable/launch_background</item> <item name="android:windowBackground">@drawable/launch_background</item>
<item name="android:statusBarColor">@color/bg_light</item>
</style> </style>
<!-- Theme applied to the Android Window as soon as the process has started.
This theme determines the color of the Android Window while your
Flutter UI initializes, as well as behind your Flutter UI while its
running.
This Theme is only used starting with V2 of Flutter's Android embedding. -->
<style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar"> <style name="NormalTheme" parent="@android:style/Theme.Light.NoTitleBar">
<item name="android:windowBackground">?android:colorBackground</item> <item name="android:windowBackground">?android:colorBackground</item>
</style> </style>

View File

@@ -1,5 +1,8 @@
allprojects { allprojects {
repositories { repositories {
// 阿里云 Maven 镜像 — 加速中国大陆依赖下载
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/central") }
google() google()
mavenCentral() mavenCentral()
} }
@@ -15,6 +18,28 @@ subprojects {
val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name) val newSubprojectBuildDir: Directory = newBuildDir.dir(project.name)
project.layout.buildDirectory.value(newSubprojectBuildDir) project.layout.buildDirectory.value(newSubprojectBuildDir)
} }
subprojects {
// 为缺少 namespace 的旧 Flutter 插件自动注入 namespace
// 解决 AGP 9.x 要求必须指定 namespace 的问题
plugins.withId("com.android.library") {
val android = project.extensions.getByName("android")
if (android is com.android.build.gradle.LibraryExtension && android.namespace == null) {
val manifestFile = project.file("src/main/AndroidManifest.xml")
if (manifestFile.exists()) {
val packageName = manifestFile.readLines()
.firstOrNull { it.contains("package=") }
?.let { line ->
Regex("package=\"([^\"]+)\"").find(line)?.groupValues?.get(1)
}
if (packageName != null) {
android.namespace = packageName
}
}
}
}
}
subprojects { subprojects {
project.evaluationDependsOn(":app") project.evaluationDependsOn(":app")
} }

View File

@@ -4,3 +4,8 @@ android.useAndroidX=true
android.newDsl=false android.newDsl=false
# This builtInKotlin flag was added by the Flutter template # This builtInKotlin flag was added by the Flutter template
android.builtInKotlin=false android.builtInKotlin=false
# 构建性能优化
org.gradle.parallel=true
org.gradle.caching=true
# 暂不启用 configuration-cache与 init 脚本冲突)
# org.gradle.configuration-cache=true

View File

@@ -11,6 +11,10 @@ pluginManagement {
includeBuild("$flutterSdkPath/packages/flutter_tools/gradle") includeBuild("$flutterSdkPath/packages/flutter_tools/gradle")
repositories { repositories {
// 阿里云 Maven 镜像 — 加速中国大陆依赖下载
maven { url = uri("https://maven.aliyun.com/repository/google") }
maven { url = uri("https://maven.aliyun.com/repository/central") }
maven { url = uri("https://maven.aliyun.com/repository/gradle-plugin") }
google() google()
mavenCentral() mavenCentral()
gradlePluginPortal() gradlePluginPortal()