whoami
Lucas Jenß
cat /etc/motd
The Coding Journal ツ — Notes taken on an epic coding journey. Technical solutions, debugging notes, and practical guides from the trenches of software development.
ls -la ~/languages/
- drwxr-xr-x
- ▶ PHP
- ▶ Ruby
- ▶ Scala
- ▶ C#
- ▶ JavaScript
- ▶ Objective-C
- ▶ Shell Scripting
ls -la ~/toolchain/
- drwxr-xr-x
- ▶ Typo3
- ▶ Akka
- ▶ Capistrano
- ▶ Git
- ▶ MAMP
- ▶ Adobe Illustrator
- ▶ NSTrackingArea (Cocoa)
uname -a
- drwxr-xr-x
- ▶ Mac OS X
- ▶ Unix
Fixing Android NDK JNI Errors When Native Libraries Fail
A native Android library can fail before your application reaches its first screen. The visible symptom is often a java.lang.UnsatisfiedLinkError, but that message describes several different problems: an absent .so file, an incompatible CPU architecture, a missing dependency, an exported-symbol mismatch, or a JNI method that Android cannot resolve.
The fastest way to fix the issue is to treat library loading as a chain of checks rather than as a single Java exception. The APK must contain the correct binary, the device must be able to execute it, the dynamic linker must find its dependencies, and the JVM must locate the expected JNI entry points.
This approach is useful whether you are testing on a Pixel in Melbourne, an older Samsung handset in Brisbane, or an emulator running on a developer workstation in Sydney. Device and build differences frequently expose packaging mistakes that remain hidden on the machine used to compile the application.
Read The Complete Error
Start with the complete Logcat output, not just the final Java stack trace. Run adb logcat while launching the application and search for UnsatisfiedLinkError, dlopen failed, JNI DETECTED ERROR, and No implementation found. The lines immediately before the exception often contain the actual cause.
A message such as library "libfoo.so" not found points towards packaging or an incorrect library name. is 64-bit instead of 32-bit indicates an ABI conflict, while needed or dlopened by ... is not accessible commonly identifies a dependency or linker-namespace problem. If the library loads successfully and the error says No implementation found for ..., the problem has moved from loading to JNI registration.
The distinction matters because rebuilding C++ code will not repair a library omitted from the APK. Likewise, changing System.loadLibrary() will not fix a function whose JNI name or signature does not match the Java declaration.
Verify The Library Name And Location
Android expects the argument to System.loadLibrary() without the lib prefix and .so suffix. For a file named libimagecodec.so, use:
static {
System.loadLibrary("imagecodec");
}
Calling System.loadLibrary("libimagecodec.so") causes Android to construct the wrong filename. System.load("/absolute/path/libimagecodec.so") follows different rules and is rarely the right solution for an application package.
Inspect the generated APK or App Bundle rather than trusting the Gradle project layout. An APK should contain paths such as lib/arm64-v8a/libimagecodec.so and, where supported, lib/armeabi-v7a/libimagecodec.so. With a bundle, Google Play may produce device-specific APK splits, so the universal output you inspect locally may not match what an Australian customer downloads from Google Play.
A frequent mistake is placing prebuilt files under a source directory that is not connected to the Android module. Configure jniLibs explicitly when necessary, or use CMake and ensure the native target is linked into the application. Android Studio’s APK Analyzer and the command unzip -l app-release.apk provide a quick confirmation.
Match The ABI To The Device
Every native binary targets one or more ABIs. Modern phones generally use arm64-v8a, while older devices may require armeabi-v7a. Android emulators can use x86_64 or x86, depending on the image. A library compiled only for ARM will not load inside an x86_64 emulator.
Check the device architecture with:
adb shell getprop ro.product.cpu.abilist
adb shell getprop ro.product.cpu.abi
Then check the contents and architecture of each native file:
unzip -l app-debug.apk | grep '\.so'
readelf -h libimagecodec.so
Your Gradle configuration should reflect the ABIs you intend to ship. For example:
android {
defaultConfig {
ndk {
abiFilters 'arm64-v8a', 'armeabi-v7a'
}
}
}
Do not add an ABI filter merely to silence an error. Removing x86_64 may make a physical ARM phone work while breaking local emulator testing. Similarly, shipping only 32-bit code can conflict with current distribution requirements and exclude newer devices. Test at least one physical 64-bit handset and the emulator architecture used by your team.
| Error or symptom | Likely cause | Useful check | Typical repair |
|---|---|---|---|
library ... not found |
File absent or wrong name | Inspect APK lib/ paths |
Correct System.loadLibrary() or packaging |
wrong ELF class |
32-bit and 64-bit mismatch | readelf -h and device ABI |
Build and package matching ABIs |
cannot locate symbol |
Missing or incompatible dependency | Logcat and readelf -d |
Ship the dependency or rebuild consistently |
No implementation found |
JNI name/signature mismatch | Compare Java and native declarations | Fix registration or exported symbol |
| Works in debug, fails in release | Shrinking, split packaging, or flags | Inspect release APK and mapping | Adjust packaging and keep required classes |
Track Native Dependencies And Load Order
A library can exist in the APK and still fail because it depends on another .so that is missing. CMake may link libcodec.so against libcrypto.so, but the application package may contain only the first file. Logcat commonly reports this as cannot locate symbol or library ... not found.
Use readelf -d to inspect the NEEDED entries:
readelf -d libcodec.so | grep NEEDED
Check that every application-owned dependency is built for the same ABI and included in the corresponding directory. A 64-bit libcodec.so cannot use a 32-bit copy of its dependency. Avoid copying random system libraries into the project; Android’s platform linker and vendor namespaces impose restrictions, and such workarounds are fragile across OS versions.
Load order can matter with older native integrations. Loading a dependency explicitly before the main library may help, although a correctly linked library should normally describe its dependencies through ELF metadata:
static {
System.loadLibrary("crypto");
System.loadLibrary("codec");
}
If your build has moved between NDK releases, compare compiler flags, STL configuration, and minimum SDK settings. Consistent builds are easier to operate than a mixture of prebuilt binaries from unrelated toolchains. The same deployment discipline applies to native artifacts as it does to server releases; these Capistrano migration notes offer a useful reminder that repeatable packaging reduces environment-specific failures.
Check JNI Names And Registration
Once dlopen succeeds, JNI can still fail. For a method declared as:
public native int decode(byte[] input);
the C or C++ implementation must use the correct generated name, or the application must register the method explicitly with RegisterNatives. A small change to the Java package, class name, or parameter list changes the expected JNI symbol.
Prefer generated headers and signature-aware declarations where possible:
JNIEXPORT jint JNICALL
Java_com_example_codec_NativeDecoder_decode(
JNIEnv* env, jobject instance, jbyteArray input) {
return 0;
}
The package separator becomes an underscore, and overloaded methods require an encoded signature. C++ functions also need extern "C" when using name-based discovery, otherwise C++ name mangling prevents the VM from finding them.
For larger projects, explicit registration in JNI_OnLoad is often clearer and less vulnerable to naming mistakes. Confirm that the native method is registered against the exact class loaded by the application. Duplicate classes, product flavours, and relocated packages can make a correct-looking registration target the wrong class.
Account For Release Builds And Modern Android
A release-only loading failure usually suggests a packaging or optimisation difference. Compare debug and release APKs with APK Analyzer, paying particular attention to native files, ABI splits, and compression. R8 generally does not remove native symbols, but it can remove or rename Java classes involved in reflective JNI registration. Add keep rules for classes and methods discovered indirectly.
Use the NDK and Android Gradle Plugin versions supported by your project rather than mixing old prebuilt objects with a new toolchain without testing. Android platform updates can also expose assumptions about linker visibility, executable memory, page alignment, and minimum supported SDK versions. Current devices and store checks reward clean, reproducible native builds.
Crash reporting deserves care in production. A stack trace may contain account identifiers, file paths, or payload fragments. If an app used across Australia sends diagnostic data offshore or to a third-party service, review the Australian Privacy Act 1988 and your stated privacy policy. Capture the error needed to diagnose ABI and linker faults without collecting unnecessary personal data.
Build A Repeatable Diagnostic Routine
When a native load fails, reproduce it on the smallest useful matrix: one physical ARM64 phone, one older ARM device if supported, and the emulator architecture used by development. A regional user in Australia may have intermittent connectivity that delays a split download or update, so test fresh installation, upgrade installation, and offline launch separately.
Record the application version, Android version, device ABI, NDK version, and exact Logcat message. Then work through the chain: confirm the Java library name, inspect the APK, compare ABIs, inspect dependencies, verify JNI declarations, and compare release settings. This order prevents time being wasted in C++ when the binary never reached the linker.
Automate the checks in CI where practical. A script can fail the build if a required .so is missing, if an expected ABI directory is empty, or if readelf reports an unexpected architecture. A small device-test job can launch the app and call one harmless native method, catching errors before a release reaches Google Play users in Perth, Adelaide, or regional areas.
Keep native loading close to application startup when failure should stop the feature immediately, and defer it when the feature is optional. Either way, catch and report UnsatisfiedLinkError with useful context, then provide a controlled fallback where possible. The goal is a clear diagnostic path, not a generic “app crashed” message.
Apply the checks to the next failing build: capture the full linker output, inspect the packaged ABIs, verify every dependency, and exercise the JNI entry point on a physical device. Once those steps become part of your build and release routine, Android NDK library failures become traceable packaging or interface defects rather than mysterious startup crashes.
cat ~/interests.json
| Key | Value |
|---|---|
| editor | Terminal-first workflow |
| os | Mac OS X / Unix |
| vcs | Git, distributed version control |
| deploy | Capistrano, cron automation |
| graphics | SVG, Adobe Illustrator troubleshooting |
| networking | IP validation, SSH, VPN |
git log --oneline --reverse
Solving SVG import issues in Adobe Illustrator CS6 and CC
When importing an SVG into Illustrator, the operation fails with an unknown error [CANT]. A workaround for this Adobe-side bug.
Solving NDK build issues on OS X
Troubleshooting native development kit compilation problems on Mac OS X.
Programmatically adding PHP generated TypoScript to the backend configuration
Integrating dynamically generated TypoScript into Typo3 backend setups using PHP.
ArgumentError: Could not parse PKey: no start line
Debugging an SSH key parsing error encountered during deployment.
Validating IP-Addresses in PHP
Using PHP filter functions with flags like FILTER_FLAG_IPV4 and FILTER_FLAG_IPV6, and understanding how filter_var handles reserved IP addresses.
Cocoa: Using NSTrackingArea
A short tutorial on using Cocoa's NSTrackingArea to capture mouseEntered and mouseExited events.
cat ~/contact.txt