Search code examples
androidgradlejava-native-interface

Why are native shared dependencies unavailable on the target but in its apk?


I want to ship native shared libraries with my apk. I added these shared libraries to the folder src/main/jniLibs/arm64-v8a. The compiled apk does include these libraries. I did verify this by checking the contents of unzip build/outputs/apk/debug/app-debug.apk. These libraries are at lib/arm64-v8a/.

However, I cannot find the libraries on my target device. I installed the app via adb install build/outputs/apk/debug/app-debug.apk. I identified the folder on the target with adb shell pm list packages -f MY_APP. Although the path /data/app/{PATH1}/{PATH2}/lib/arm64/ does exist, it is empty. The libraries are, however, included in the base.apk on the target.

I have not modified anything in my app (gradle config or CMake files, for instance) other than storing the libraries at src/main/jniLibs/arm64-v8a/. I experimented with some teakes in the gradle config but that didn't help. I cannot find relevant information in the Android documentation, https://developer.android.com/studio/projects/gradle-external-native-builds. I am using Gradle 8.


Solution

  • You haven't explicitly triggered access to them during initial installation (via adb install) command that's why you are getting empty folders arm64 and arm64-v8a.

    Try below approach is for development purposes only. You can use adb push to manually push the libraries from your computer to the target device's appropriate location.

    adb push <path_to_library_on_your_pc>/lib/arm64-v8a/ <target_device_path>/data/app/<package_name>/lib/arm64-v8a/
    

    And then try if it works .

    TLDR;

    Android keeps its native libraries well-organized within the APK they are extracted and used appropriately depending on the application being installed on the device.

    Below is the detail explanation of the whole process :

    1. Native Libraries inside APK

    As you proceed to develop your Android application further with Native Libraries, the Libraries get packaged with your APK file within the directory lib. The layout of that directory is as follows:

    <APK>
     └── lib/
         ├── armeabi-v7a/
         │   └── <native_lib_1>.so
         │   └── <native_lib_2>.so
         ├── arm64-v8a/
         │   └── <native_lib_1>.so
         │   └── <native_lib_2>.so
         ├── x86/
         │   └── <native_lib_1>.so
         │   └── <native_lib_2>.so
         └── x86_64/
             └── <native_lib_1>.so
             └── <native_lib_2>.so
    

    There is only one subdirectory reserved for the respective CPU architecture (ABI).

    2. Decompressing and Installing

    Process: In case of installation through any installer for any Android application:

    a). APK Parsing

    The Android device's Package Manager identifies the lib directory, it knows which libraries are required for the device's ABI.

    b). Unzipping

    At runtime, Package Manager will extract the native libraries that are for this device's ABI to some spot in the device's filesystem. Usually, that is in the private data directory of the application, for which class traditionally was:

    /data/data/<package_name>/lib/
    

    or for newer Android versions (6.0 and higher) within the split APK directories :

    /data/app/<package_name>-<identifier>/lib/<ABI>/
    

    b.1) Native libraries:

    Some Android applications include native libraries that have been coded in the likes of C/C++.

    • Performance gains in specific tasks for those libraries, but with the need for tuning into the device's architecture (ABI). extractNativeLibs Flag:

    The extractNativeLibs flag is set within an application's AndroidManifest.xml file.

    This establishes how the native libraries should be handled by a package manager during installation.

    Impact of extractNativeLibs:

    b.1.1) extractNativeLibs=true (Default for minSdkVersion < 21):
    • The package manager extracts all the native libraries from the APK and places them in a device-specific location, usually /data/data/<package_name>/lib/. This approach ensures compatibility with older versions of Android (below API Level 21);
    • This results in applications being given a larger installation size. The same libraries get duplicated for different ABIs.
    • Requires more time to be taken out during installation.
    b.1.2) extractNativeLibs=false (Default for minSdkVersion >= 21):
    • The native libraries are compressed inside APKs.
    • It extracts only the loadable libraries compatible with the ABI of the device, for instance, to locations like /data/app/<package_name>-< identifier>/.
    • This allows for a variety of advantages:
    • This reduces the installation size of the app only to the required libraries in order.
    • Quick installations by eliminating unnecessary extractions.
    • Makes application updates much more efficient.

    Note:

    For new applications that target API Level 21 and above, extractNativeLibs=false is preferred because of efficiency, among other things. You'll probably want to use extractNativeLibs=true if you need to support old Android versions or for other reasons, for example, making direct use of NDK functionalities in building native libraries aimed at specific ABIs. Overview Also, extractNativeLibs flags allow/disallow the extraction of native libraries upon installation of the application. Setting this field to false serves a value in higher versions of Android OS by ensuring the efficiency of the app's size, installation, and update.

    c). Loading Native Libraries

    These are then loaded at runtime, dynamically, whenever the application is run, by the Android Runtime (ART), or by the older versions of Dalvik VM. The system adds the path where these libraries are extracted to the native library search path for that application.

    3. Native Library Support

    Typically you would load a native library from your Java/Kotlin code using the System.loadLibrary method:

    System.loadLibrary("native_lib_1");
    

    It loads the respective .so file from scanning the directories where the native libraries would have extracted.

    4. Multi-APK and ABI Filters

    APK ABI filters If you are targeting more than one ABI with your application, you can use APK ABI filters in your APK to include only the desired ABIs:

    android {
        ...
        defaultConfig {
            ...
            ndk {
                abiFilters 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
            }
        }
        ...
    }
    

    5. APK Splits for Native Libraries

    Developers have the ability to create custom APK splits for various ABIs to optimize the delivery size of the application. As a result, even if we upload our application to the Play Store, the APK served by the Play Store contains only the ABI required by the device, and hence reduces the size of the APK. Can be done in the build.gradle:

    android {
        ...
        splits {
            abi {
                enable true
                reset()
                include 'armeabi-v7a', 'arm64-v8a', 'x86', 'x86_64'
                universalApk false
            }
        }
    }
    

    More information can be found here :