Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Android L Preview does not search native libraries in "armeabi" folder (UnsatisfiedLinkError)

I have an app with 2 native libraries. 1st works much faster on ARMv7 so I have version both for ARMv7 and ARMv5. 2nd works the same on both platforms so only ARMv5 library is provided.

My native library folder looks like this:

/jniLibs/
    |
    +---armeabi/
    |     |
    |     +---libFirstLibrary.so
    |     +---libSecondLibrary.so
    |
    +---armeabi-v7a/
          |
          +---libFirstLibrary.so

The app works well on all devices and Android versions in production.

When I tested it on my Nexus 5 with L-Preview (hammerhead-lpv79-preview-ac1d8a8e.tgz), I get this error:

java.lang.UnsatisfiedLinkError: Couldn't load SecondLibrary from loader dalvik.system.PathClassLoader[DexPathList[[zip file "/data/app/com.package-1.apk"],nativeLibraryDirectories=[/data/app-lib/com.package-1, /vendor/lib, /system/lib]]]: findLibrary returned null
   at java.lang.Runtime.loadLibrary(Runtime.java:358)
   at java.lang.System.loadLibrary(System.java:610)

The problem is that despite the fact Nexus 5 has CPU_ABI set to armeabi-v7a and CPU_ABI2 set to armeabi, L-Preview uses just CPU_ABI value and looks for "SecondLibrary" only in "armeabi-v7a" folder and crashes as it is not there.

When I copy the .so file also to "armeabi-v7a" folder, everything is fine but the APK is 3.5 MB bigger which I don't really like.

Is it just a bug of Android L-Preview or some "new feature"?

like image 289
xsveda Avatar asked Sep 03 '14 13:09

xsveda


1 Answers

As far as I know, this has always been the intended behaviour, and I'm puzzled why this has worked for you before.

The linker doesn't browse the full APK file on each loadLibrary call - instead, the right native libraries are extracted when the APK is installed. Only one single architecture directory is used, thus if it found lib/armeabi-v7a it won't even look for lib/armeabi.

There is a known issue on some older versions of android (4.0.3 and earlier) where the armeabi can be accidentally used instead of armeabi-v7a though, not sure if that's what makes this seem to work for you.

See e.g. https://android.googlesource.com/platform/ndk/+/532389e89c/docs/text/CPU-ARCH-ABIS.text for an official explanation of this (section "III. ABI Management on the Android platform", in particular subsection III.3 and the note at the end of subsection III.1).

EDIT: It actually seems that the package manager could install some files from the secondary ABI directory even though the primary ABI directory existed, on android versions up to kitkat. http://albin.abo.fi/~mstorsjo/hellojni-test-abis.zip is the source for my test example, and http://albin.abo.fi/~mstorsjo/hellojni-test-abis.apk is a binary of it. This example creates four native libraries, libgello-jni.so, libhello-jni.so, libhello-jni2.so and libtello-jni.so. These files are built for all ABIs, but in armeabi-v7a, I've removed all files except libhello-jni.so - the file listing (for the ARM architecture directories) thus looks like this:

lib/armeabi/libgello-jni.so
lib/armeabi/libhello-jni.so
lib/armeabi/libhello-jni2.so
lib/armeabi/libtello-jni.so
lib/armeabi-v7a/libhello-jni.so

On installation on a kitkat armeabi-v7a device, this installs libhello-jni.so from the armeabi-v7a directory, and libhello-jni2.so and libgello-jni.so from the armeabi directory - but libtello-jni.so is not installed at all. What files are installed seem to depend on the names and order within the APK. So depending on your file names, you might have had luck before and this have worked - even if the documentation explicitly says it isn't supposed to work. In the android-L preview this inconsistency seems to have been corrected.

like image 153
mstorsjo Avatar answered Nov 16 '22 00:11

mstorsjo