perf(android): reduce release APK size

Keep Dart debug data outside libapp.so and archive matching symbols and R8 mappings in CI. Pack native relocations, preserve JNI exports, and recompress PNG assets losslessly.

ARM64 APK shrinks from 80,306,680 to 76,080,247 bytes (5.26%). Verified with 1,877 Flutter tests, 19 native device tests, Discord SDK integration, and release startup on a 16 KiB Android emulator.
This commit is contained in:
zarzet committed 2026-10-06 15:22:42 +07:00
1 parent 4be1073203
commit b8c50b1527
11 files changed
+102 -4

No files matched your search

+18
View File
@@ -313,3 +313,21 @@ jobs:
"$ANDROID_HOME/build-tools/37.0.0/apksigner" verify --verbose \
"build/app/outputs/flutter-apk/app-${abi}-release.apk"
done
- name: Record Android symbol provenance
run: |
test -s build/symbols/android/app.android-arm64.symbols
test -s build/symbols/android/app.android-arm.symbols
test -s build/app/outputs/mapping/release/mapping.txt
git rev-parse HEAD > build/symbols/android/commit.txt
sha256sum build/app/outputs/flutter-apk/*release.apk > build/symbols/android/apk-sha256.txt
- name: Retain Android debug symbols
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: android-symbols-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
if-no-files-found: error
retention-days: 90
path: |
build/symbols/android/
build/app/outputs/mapping/release/mapping.txt
+18
View File
@@ -227,6 +227,24 @@ jobs:
"$apk_dir/SpotiFLAC-${VERSION}-${abi}.apk"
done
- name: Record Android symbol provenance
run: |
test -s build/symbols/android/app.android-arm64.symbols
test -s build/symbols/android/app.android-arm.symbols
test -s build/app/outputs/mapping/release/mapping.txt
git rev-parse HEAD > build/symbols/android/commit.txt
sha256sum build/app/outputs/flutter-apk/SpotiFLAC-*.apk > build/symbols/android/apk-sha256.txt
- name: Retain Android debug symbols
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
name: android-symbols-${{ needs.get-version.outputs.version }}-${{ github.sha }}-${{ github.run_id }}-${{ github.run_attempt }}
if-no-files-found: error
retention-days: 90
path: |
build/symbols/android/
build/app/outputs/mapping/release/mapping.txt
- name: Upload APK artifact
uses: actions/upload-artifact@b7c566a772e6b6bfb58ed0dc250532a479d7789f # v6
with:
+12
View File
@@ -82,6 +82,18 @@ remain the default. To build only ARM64, set
run CocoaPods and the app build with `SPOTIFLAC_DISCORD_SDK=0`; an existing Pods
installation must be regenerated with that setting.
Android release builds, including plain `flutter build apk --release`, keep Dart
debug data in `build/symbols/android/app.<architecture>.symbols` to reduce APK
size without obfuscation. An explicit `--split-debug-info` path overrides this
default; `--analyze-size` retains Flutter's normal behavior. Keep these symbols
with the matching APK before another local build replaces them. Decode a trace
with `flutter symbolize --debug-info=<symbols-file> --input=crash.txt`.
CI and release jobs retain an `android-symbols-…` artifact with the commit, APK
SHA-256 hashes, Dart symbols, and R8 mapping, identified by run and attempt.
Archive it before its 90-day retention expires and match the reported APK hash
when choosing symbols; equal version numbers do not guarantee a match.
For native iOS codec checks, see the [FFmpeg capability probe](scripts/README_ios_ffmpeg_capabilities.md).
---
+7
View File
@@ -8,6 +8,13 @@ plugins {
id("dev.flutter.flutter-gradle-plugin")
}
// Flutter reads this when configuring variant tasks, after this build script.
// Keep release AOT debug data outside libapp.so; explicit paths and size analysis take precedence.
if (!project.hasProperty("split-debug-info") && !project.hasProperty("code-size-directory")) {
project.extensions.extraProperties["split-debug-info"] =
rootProject.file("../build/symbols/android").absolutePath
}
val keystorePropertiesFile = rootProject.file("key.properties")
val keystoreProperties = Properties()
if (keystorePropertiesFile.exists()) {
+9 -1
View File
@@ -24,4 +24,12 @@ add_library(spotiflac_audio SHARED native_audio.cpp)
target_compile_features(spotiflac_audio PRIVATE cxx_std_17)
target_compile_options(spotiflac_audio PRIVATE -Wall -Wextra -Werror -fvisibility=hidden)
target_link_libraries(spotiflac_audio PRIVATE oboe WavPack::WavPack log)
target_link_options(spotiflac_audio PRIVATE "-Wl,-z,max-page-size=16384")
# Only JNI is public; static Oboe/WavPack/C++ implementation symbols must not
# keep otherwise unused code alive. APS2 is supported by our minimum API 24.
target_link_options(spotiflac_audio PRIVATE
"-Wl,-z,max-page-size=16384"
"-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/native_audio.map"
"-Wl,--icf=safe"
"-Wl,--pack-dyn-relocs=android")
set_property(TARGET spotiflac_audio APPEND PROPERTY LINK_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/native_audio.map")
+17
View File
@@ -0,0 +1,17 @@
{
global:
Java_com_zarz_spotiflac_NativeAudio_openOboe;
Java_com_zarz_spotiflac_NativeAudio_bitsOboe;
Java_com_zarz_spotiflac_NativeAudio_deviceOboe;
Java_com_zarz_spotiflac_NativeAudio_writeOboe;
Java_com_zarz_spotiflac_NativeAudio_startOboe;
Java_com_zarz_spotiflac_NativeAudio_flushOboe;
Java_com_zarz_spotiflac_NativeAudio_framesOboe;
Java_com_zarz_spotiflac_NativeAudio_closeOboe;
Java_com_zarz_spotiflac_NativeAudio_openWavPack;
Java_com_zarz_spotiflac_NativeAudio_infoWavPack;
Java_com_zarz_spotiflac_NativeAudio_readWavPack;
Java_com_zarz_spotiflac_NativeAudio_seekWavPack;
Java_com_zarz_spotiflac_NativeAudio_closeWavPack;
local: *;
};
Binary file not shown.

Before

Width:  |  Height:  |  Size: 143 KiB

After

Width:  |  Height:  |  Size: 67 KiB

Binary file not shown.

Before

Width:  |  Height:  |  Size: 71 KiB

After

Width:  |  Height:  |  Size: 68 KiB

+4 -2
View File
@@ -98,8 +98,10 @@ if [[ "$PLATFORM" == "android" ]]; then
done
rustup target add "${ANDROID_TARGETS[@]}"
# Align ELF segments for Android devices using 16 KB pages.
export RUSTFLAGS="${RUSTFLAGS:-} -C link-arg=-Wl,-z,max-page-size=16384"
# Align ELF segments for 16 KB pages and pack relocations (Android API 23+).
# Keep APS2 rather than RELR, which requires API 28; our minimum is API 24.
# Fold only identical functions whose addresses are not significant.
export RUSTFLAGS="${RUSTFLAGS:-} -C link-arg=-Wl,-z,max-page-size=16384 -C link-arg=-Wl,--pack-dyn-relocs=android -C link-arg=-Wl,--icf=safe"
for target in "${ANDROID_TARGETS[@]}"; do
cargo build --locked --release -p spotiflac-mobile --target "$target"
case "$target" in
+8 -1
View File
@@ -4,7 +4,14 @@ set(CMAKE_CXX_STANDARD 20)
option(SPOTIFLAC_DISCORD_SDK "Include the staged Discord Social SDK" ON)
add_library(spotiflac_discord SHARED src/main/cpp/presence.cpp)
target_compile_options(spotiflac_discord PRIVATE -Wall -Wextra -Werror -fvisibility=hidden)
target_link_options(spotiflac_discord PRIVATE "-Wl,-z,max-page-size=16384")
# Keep every JNI entry point while hiding the wrapper's static C++ runtime.
target_link_options(spotiflac_discord PRIVATE
"-Wl,-z,max-page-size=16384"
"-Wl,--version-script,${CMAKE_CURRENT_SOURCE_DIR}/presence.map"
"-Wl,--icf=safe"
"-Wl,--pack-dyn-relocs=android")
set_property(TARGET spotiflac_discord APPEND PROPERTY LINK_DEPENDS
"${CMAKE_CURRENT_SOURCE_DIR}/presence.map")
set(SDK "${CMAKE_CURRENT_SOURCE_DIR}/../sdk/android")
if(SPOTIFLAC_DISCORD_SDK AND EXISTS "${SDK}/jni/${ANDROID_ABI}/libdiscord_partner_sdk.so")
add_library(discord_partner_sdk SHARED IMPORTED)
+9
View File
@@ -0,0 +1,9 @@
{
global:
Java_com_zarz_spotiflac_discord_DiscordPresencePlugin_nativeStart;
Java_com_zarz_spotiflac_discord_DiscordPresencePlugin_nativeTick;
Java_com_zarz_spotiflac_discord_DiscordPresencePlugin_nativeUpdate;
Java_com_zarz_spotiflac_discord_DiscordPresencePlugin_nativeClear;
Java_com_zarz_spotiflac_discord_DiscordPresencePlugin_nativeStop;
local: *;
};