import java.io.FileInputStream import java.util.Properties plugins { id("com.android.application") // The Flutter Gradle Plugin must be applied after the Android and Kotlin Gradle plugins. id("dev.flutter.flutter-gradle-plugin") } // 发布签名凭据放在 android/key.properties(和 mathtool2 同一套)。 // 该文件和 key.jks 都已被 android/.gitignore 忽略,不会进仓库。 val keystorePropertiesFile = rootProject.file("key.properties") val keystoreProperties = Properties() if (keystorePropertiesFile.exists()) { keystoreProperties.load(FileInputStream(keystorePropertiesFile)) } android { namespace = "com.nonvioce.cal" compileSdk = flutter.compileSdkVersion compileOptions { sourceCompatibility = JavaVersion.VERSION_17 targetCompatibility = JavaVersion.VERSION_17 // Symja 使用 java.time(API 26 才有);minSdk 24 必须开 desugaring。 // 注意:Ijs 在这一点上是错的(minSdk 24 却没开),这里顺手修掉。 isCoreLibraryDesugaringEnabled = true } defaultConfig { applicationId = "com.nonvioce.cal" minSdk = 24 targetSdk = flutter.targetSdkVersion versionCode = flutter.versionCode versionName = flutter.versionName // 光 Symja 就有约 42k 方法,必须 multiDex multiDexEnabled = true } signingConfigs { // 只有拿到凭据才建这个配置;否则下面的 release 退回调试签名, // 保证"没带 key.properties 的机器"上依然能构建。 if (keystorePropertiesFile.exists()) { create("release") { keyAlias = keystoreProperties.getProperty("keyAlias") keyPassword = keystoreProperties.getProperty("keyPassword") storeFile = file(keystoreProperties.getProperty("storeFile")) storePassword = keystoreProperties.getProperty("storePassword") } } } buildTypes { release { signingConfig = if (keystorePropertiesFile.exists()) { signingConfigs.getByName("release") } else { signingConfigs.getByName("debug") } // 【真机实测的结论】不要对 Symja 开 R8 混淆。 // Symja 及其依赖大量用「类名字符串」做反射创建,R8 看不到引用关系就会改名, // 运行时按原名找不到 -> 静态初始化直接炸。实测踩到两个: // 1. log4j2-android 的 com.celeral.android.logging.log4j.AndroidLoggerContextFactory // (由 system property "log4j2.loggerContextFactory" 指定) // 2. apfloat 的 org.apfloat.internal.LongBuilderFactory(由 "builderFactory" 属性指定) // -> F. 抛 ExceptionInInitializerError // 官方的 Symja Android 参考工程 axkr/symja-example 同样不开 minify。 // proguard-rules.pro 里的 -keep 规则保留着,将来若要重新开混淆可以从那里起步, // 但必须先在真机上回归。 isMinifyEnabled = false isShrinkResources = false proguardFiles( getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro", ) } } packaging { resources { excludes += setOf( "META-INF/DEPENDENCIES", "META-INF/LICENSE", "META-INF/LICENSE.txt", "META-INF/license.txt", "META-INF/NOTICE", "META-INF/NOTICE.txt", "META-INF/notice.txt", "META-INF/ASL2.0", "META-INF/*.kotlin_module", "Log4j2Plugins.dat", ) pickFirsts += setOf( "**/*.xsd", ) } // 只留 arm64-v8a 的原生库。 // // 另外两条路都试过、都不行: // · defaultConfig 里写 ndk.abiFilters —— 实测三个 ABI 的 libflutter.so 照样全进包 // · android/gradle.properties 里写 target-platform —— flutter_tools 会显式传 // -Ptarget-platform=... (gradle.dart:581),命令行参数盖过 gradle.properties // 这里在 APK 组装阶段剔目录,Flutter 就管不着了。 jniLibs { excludes += setOf( "lib/armeabi-v7a/**", "lib/x86/**", "lib/x86_64/**", ) } } } kotlin { compilerOptions { jvmTarget = org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17 } } dependencies { coreLibraryDesugaring("com.android.tools:desugar_jdk_libs:2.1.5") implementation("org.matheclipse:matheclipse-core:3.2.0") // log4j-api 在 Android 上需要一个实现,否则 Symja 的日志路径会抛错 runtimeOnly("com.celeral:log4j2-android:1.0.0") // 用 Android 变体的 guava,避免 JRE 专属类(java.beans 等) implementation("com.google.guava:guava:33.5.0-android") // MCP 服务器用的内嵌 HTTP 服务器(JSON-RPC over HTTP) implementation("org.nanohttpd:nanohttpd:2.3.1") } configurations.all { resolutionStrategy { force("com.google.guava:guava:33.5.0-android") } } flutter { source = "../.." }