Skip to content

Modernize build for Android API 36 + NDK 27, fix JNI class loader SIGABRT#21

Open
sharpninja wants to merge 1 commit intosevar83:masterfrom
sharpninja:api36-compat
Open

Modernize build for Android API 36 + NDK 27, fix JNI class loader SIGABRT#21
sharpninja wants to merge 1 commit intosevar83:masterfrom
sharpninja:api36-compat

Conversation

@sharpninja
Copy link
Copy Markdown

Summary

This PR brings android-spatialite up to date with current Android tooling (API 36, NDK r27, Gradle 8, AGP 8.7) and fixes a fatal SIGABRT that occurs when the library is loaded by non-standard runtimes such as .NET Android (Mono/Xamarin).

Build system modernization

  • AGP: 3.6.0-alpha06 → 8.7.3
  • Gradle: 5.6 → 8.11.1
  • compileSdk / targetSdk: 29 → 36
  • minSdk: 16 → 21 (matches current Google Play requirements)
  • Java compatibility: 1.7 → 1.8
  • Repository: replaced deprecated jcenter() with mavenCentral()
  • settings.gradle: added pluginManagement block (required by Gradle 8)
  • AndroidX dependencies: updated to current stable releases
  • SQLite amalgamation: updated to 3.49.1 (January 2025)
  • Misc: replaced deprecated GradleScriptException with GradleException

NDK / native changes

Setting Before After Why
APP_STL c++_shared c++_static Eliminates libc++_shared.so APK dependency; avoids version conflicts when multiple native libraries ship in the same app
APP_PLATFORM android-16 android-21 Aligns with minSdk 21
APP_ABI armeabi-v7a,arm64-v8a,x86,x86_64 armeabi-v7a arm64-v8a x86_64 Drops x86 (virtually unused on real devices); space-separated per NDK r23+ convention
APP_DEPRECATED_HEADERS true removed Deprecated and unnecessary with NDK r27
APP_SHORT_COMMANDS true NDK_APP_SHORT_COMMANDS = true Corrected variable name

Added -Wno-incompatible-function-pointer-types to individual ndk-module .mk files where Clang 18+ (NDK r27) emits hard errors for legacy C function pointer casts in vendored source (freexl, geos, libiconv, liblzma, libspatialite, libxml2, proj.4).

JNI class loader fix (critical — SIGABRT)

Problem: When libandroid_spatialite.so is loaded by a non-standard runtime (e.g., .NET Android / Mono), System.loadLibrary fires before any Java application code runs. JNI_OnLoad therefore executes on the bootstrap class loader thread, where FindClass("org/spatialite/database/SQLiteCustomFunction") returns NULL. The existing code calls LOG_FATAL_IF(!clazz, ...) which triggers abort()SIGABRT.

This is specific to runtimes that preload native libraries during VM initialization (before the app class loader is available). Standard Android Java/Kotlin apps are unaffected because SQLiteDatabase.<clinit> runs on an app thread.

Fix:

  1. JNI_OnLoad now only saves the JavaVM* pointer — no RegisterNatives, no FindClass, no GetFieldID.
  2. All four registration functions (register_android_database_SQLiteConnection, SQLiteGlobal, SQLiteDebug, CursorWindow) are called exactly once from nativeOpen() via std::call_once, which is the first native method invoked on an actual Java application thread where the app class loader is available.
  3. Replaced the FIND_CLASS macro with direct env->FindClass() + LOG_FATAL_IF in register_android_database_SQLiteConnection for clarity and debuggability.

This is fully backward-compatible: standard Java/Kotlin Android apps call nativeOpen() on an app thread identically to before; the lazy registration is transparent.

Test plan

  • Built with NDK r27.2 (Clang 18) on Windows — all ABIs compile clean
  • Verified .NET Android (Mono) app loads libandroid_spatialite.so without SIGABRT
  • Verified nativeOpen() succeeds and RegisterNatives completes on first database open
  • Verified mod_spatialite extension loads via SELECT load_extension('mod_spatialite')
  • Upstream maintainer verification on standard Java/Kotlin Android project

🤖 Generated with Claude Code

…ABRT

Build system:
- Upgrade AGP 3.6.0-alpha06 → 8.7.3, Gradle 5.6 → 8.11.1
- Replace deprecated jcenter() with mavenCentral()
- compileSdk/targetSdk 29 → 36, minSdk 16 → 21
- Java source/target 1.7 → 1.8
- Add pluginManagement block in settings.gradle
- Update AndroidX dependencies to current stable
- Update SQLite amalgamation to 3.49.1 (2025)
- Replace GradleScriptException with GradleException

NDK / native:
- APP_STL c++_shared → c++_static (eliminate libc++_shared.so APK dependency)
- APP_PLATFORM android-16 → android-21
- Drop armeabi-v7a from default ABIs (x86 already dropped)
- Replace deprecated APP_DEPRECATED_HEADERS / APP_SHORT_COMMANDS with
  NDK_APP_SHORT_COMMANDS
- Add -Wno-incompatible-function-pointer-types to ndk-modules .mk files
  (required by Clang 18+ in NDK r27)

JNI class loader fix (SIGABRT on .NET Android / Mono):
- Mono's DSO preload calls System.loadLibrary before any Java app code
  runs, so JNI_OnLoad fires on the bootstrap class loader thread where
  FindClass("org/spatialite/...") returns null → LOG_FATAL_IF → SIGABRT.
- JNI_OnLoad now only saves the JavaVM pointer.
- All RegisterNatives / FindClass / GetFieldID calls moved to a
  std::call_once-guarded doLazyRegisterAll() invoked from nativeOpen(),
  which always runs on an app Java thread with the app class loader.
- Replaced FIND_CLASS macro with direct env->FindClass() + LOG_FATAL_IF
  in register_android_database_SQLiteConnection for clarity.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant