Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Improve space savings with Android App Bundles (AAB) #1198

Open
mhsmith opened this issue Jul 5, 2024 · 0 comments
Open

Improve space savings with Android App Bundles (AAB) #1198

mhsmith opened this issue Jul 5, 2024 · 0 comments

Comments

@mhsmith
Copy link
Member

mhsmith commented Jul 5, 2024

Apart from libpython and its supporting libraries, which are in the JNI libs directory, other ABI-specific files are currently packaged as assets. This means all ABIs will be installed on all devices, even when using the AAB format.

There's a workaround in the FAQ that uses product flavors, but it's a bit awkward.

As far as I can see, there's no way for assets or resources to be ABI-specific, only libs subdirectories:

There are two ways we could move these files to the libs folder:


We could move the native modules into the lib folder as individual files. This is probably the only possible solution if a future version of Android blocks loading libraries from the data directory (#585).

Can we create subdirectories in lib? If not, libraries with identical names would need to be renamed to something like chaquopy-pkg1-pkg11-modname.so, which would cause problems with API level 22 and older which doesn't properly implement SONAME. However, those versions always extract library files, so we can probably work around it by loading through a symlink in the data directory, or if that doesn't work, just copy the file to the data directory and load it from there.

When the lib directory isn't extracted, we should load libraries directly from the APK using the "!" syntax described here. The documentation says extractNativeLibs defaults to false in Android Gradle plugin 3.6 and newer, and people say this is also the case whenever building APKs from an AAB (1, 2). However, the libraries were still extracted last time I checked (API level 30 emulator, Android Studio 4.0). Maybe it only defaults to false in release builds, or maybe Android Studio arranges for them to be extracted to speed up incremental installs. Either way, we'd need to be able to handle both the extracted and non-extracted case.

There will be complications with packages which load .so files directly, or have one .so file linked against another which isn't in the chaquopy/lib directory. We currently intercept most of these by overriding find_library and CDLL, but it's worth checking whether dlopen will accept a symlink whose target path uses the "!" syntax. However, even if it does, it isn't documented, so we shouldn't rely on it unless absolutely necessary.

Although the lib directory may be on the LD_LIBRARY_PATH on API level 18 or higher, this can't be relied on, because every call to System.loadLibrary uses the undocumented libdl function android_update_LD_LIBRARY_PATH to set the dynamic linker's internal copy of the path to point at the the lib directory of the caller's APK. For example, WebView uses a native library from a different APK, so after WebView is loaded into your process, your own APK's lib directory is no longer on the path!

So when calling dlopen from native code, the only safe approaches are:

  • Use the undocumented android_update_LD_LIBRARY_PATH function to set the path before calling dlopen. We were forced to do this on 64-bit devices before API level 23, but such devices are rare, and I'd prefer not to extend this dependency on an undocumented function to all devices. Also, after API level 23 we'd have to deal with the possibility that libraries aren't extracted from the APK, so simply adding the filesystem lib directory to the path might not be enough.
  • Or to manually pre-load all needed libraries in the correct order, as we currently do.

Or we could move the existing ABI-specific ZIP assets to the lib directory. However, when the directory isn't extracted, this would probably make it impossible to deal with #585, because I doubt the "!" syntax can go through 2 levels of ZIPs.

Even if #585 isn't extended to unprivileged apps, copying the libraries from the APK to the data directory might still be awkward. The Java AssetManager has a openNonAssetFd method, but I don't see anything similar in the native asset API which we currently use. See how the Java API is implemented. If all else fails, we could try accessing the APK directly, but there may be more than one of them.

The ZIPs may need to be renamed, since last time I checked, the Gradle plugin ignores any files in the lib directory whose names don't end with ".so". Alternatively, we might be able to use the resources/lib directory, though this is so poorly documented that it's probably not a good idea to rely on it.

There may be problems if the Gradle plugin tries to strip the files during the build. That could be worked around by giving them a valid ELF header, which wouldn't actually stop them from being a valid ZIP file, because the ZIP file "header" is actually at the end of the file (self-extracting executable ZIPs work in the same way).

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Projects
None yet
Development

No branches or pull requests

1 participant