10

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"?

xsveda
  • 17,074
  • 2
  • 16
  • 16

1 Answers1

6

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.

mstorsjo
  • 12,983
  • 2
  • 39
  • 62
  • In general yes, but almost all devices have support for two ABIs. In my case, Nexus 5 has support for: `CPU_ABI = armeabi-v7a` and `CPU_ABI2 = armeabi`. The same mechanism is used i.e. for Intel devices where primary ABI is `x86` but secondary is `armeabi` so my app runs fine also on Intel devices even though there is no native library in `x86` folder. In my opinion, there is not reason why this shoud not work also on L-Preview. – xsveda Sep 03 '14 at 19:15
  • 1
    Yes, but the package manager only checks for libraries in the secondary ABI directory if none were found in the primary ABI directory. The documentation I linked says: "the package manager service will scan the .apk and look for any shared library of the form: lib//lib.so [...] If one is found, then it is copied under `$APPDIR/lib/lib.so` [...] If none is found, and a secondary ABI is defined, the service will then scan for shared libraries of the form: lib//lib.so". So if the primary ABI directory isn't empty, it won't check the secondary. – mstorsjo Sep 03 '14 at 19:30
  • So that's what I meant in my original answer - it only ever extracts from one single architecture directory - which can be either the primary or secondary ABI. – mstorsjo Sep 03 '14 at 19:36
  • I have test it on Kitkat and L-Preview emulator and on Kitkat, the `libFirstLibrary.so` is taken from `armeabi-v7a` folder and `libSecondLibrary.so` is taken from `armeabi` folder. This does not work anymore on L-Preview where both libraries must be in `armeabi-v7a` folder or both must be in `armeabi` folder only. The documentation text is not 100 % clear for this but there is definitely a change in the behavior between Kitkat and L-Preview. @mstorsjo Please update your answer with this info and I will accept it, thanks for your help! – xsveda Sep 04 '14 at 08:13
  • I was quite sure that older versions of android didn't install libs from more than one architecture subdirectory, but I did a test and found out that it might, but not always. So to me it seems that it was a bug in the older version that occasionally could install them (depending on the file names and order of files within the APK), and this bug has been resolved in android-L. I'll update the answer with this info. – mstorsjo Sep 04 '14 at 12:43
  • Also - I think mixing libraries between the primary and secondary ABI could be problematic anyway. Consider the case with primary being x86 and secondary being armeabi - I'm not familiar with the arm emulaton on x86 android devices, but I would assume that it'd require the whole process running in arm mode. But anyway, the fact that this seemed to work on earlier versions just seems to be a bug. – mstorsjo Sep 04 '14 at 12:45
  • The case with `x86` device is intersting so I tried it and it works on Kitkat same as ARM devices. – xsveda Sep 04 '14 at 14:30