ReLinker v1.2 is Here!

Hilal Alsibai
Keepsafe Engineering
5 min readMar 20, 2016
Credit to Jeff Young for the awesome logo!

Back in November of 2015, we released ReLinker as a small library to fix older versions of Android’s inability to reliably load native libraries. You can read more about that story in our previous blog post.

Today, I’m happy to say that ReLinker v1.2 has been released and is available for use by everyone.

Additionally, I wanted to write about all of the new features coming with the 1.2 release, as many things have been added.

Asynchronous Loading

ReLinker can now load libraries asynchronously, allowing you to offload work off of your main thread and deal with the results later. Here’s a sample of how you would load libmylibrary asynchronously:

Asynchronous loading

By passing a ReLinker.LoadListener to loadLibrary, ReLinker will automatically offload the re-linking process to a new thread. The thread runs with the default priority defined by the system. If any problems arise within the thread then failure will be called on your listener. The throwable passed into failure will be whatever exception ReLinker encounters when attempting to link your library. Otherwise, success will be called.

Recursive Loading

ReLinker can now resolve and link intra-library dependencies for you. The resolution of intra-library dependency happens recursively until all dependencies are properly linked. All that is needed is for you to specify that you would like ReLinker to recursively load libraries:

Specifically speaking, ReLinker will attempt to load any libraries defined as DT_NEEDED within your shared object file. You can check what libraries ReLinker will attempt to recursively load by executing

$ readelf -d lib.so | grep NEEDED

Which, for libhellojni.so (bundled with ReLinker’s sample), outputs:

 0x00000001 (NEEDED)                 Shared library: [libhello.so]
0x00000001 (NEEDED) Shared library: [libstdc++.so]
0x00000001 (NEEDED) Shared library: [libm.so]
0x00000001 (NEEDED) Shared library: [libc.so]
0x00000001 (NEEDED) Shared library: [libdl.so]

Recursive loading exists to resolve an issue with Android’s native library linker on older versions of Android (< API 18) where the linker fails to resolve any intra-library dependencies not bundled with the system.

On Android, shared libraries utilize the ELF format. Within the library file, the ELF format defines a list of dependencies (other libraries) that the library relies on. Typically you will see very common libraries such as libc, libm, and libdl defined as dependencies for your library. Each of these libraries are typically provided by the system itself and do not need to be bundled with your app.

The problem arises when you define your own supplied library as a dependency in another library. On versions of Android < API 18, your supplied library is not added to the library load path, and thus will not be found by the system’s linker. This is because the system’s linker does not consider your app’s library directory for libraries and looks in a static list of directories for libraries. This can be seen here in Bionic’s linker source code for Gingerbread.

Unfortunately, this means that you need to explicitly load libraries in reverse order of dependence manually on versions of Android < API 18. If you don’t, then you get a lovely exception similar to:

E/AndroidRuntime(632): FATAL EXCEPTION: main
E/AndroidRuntime(632): java.lang.UnsatisfiedLinkError: Cannot load library: link_image[1967]: 180 could not load needed library ‘libhello.so’ for ‘libhellojni.so’ (load_library[1109]: Library ‘libhello.so’ not found)
at java.lang.Runtime.loadLibrary(Runtime.java:434)
at java.lang.System.loadLibrary(System.java:554)

Logging

To help facilitate effective debugging for any future UnsatisfiedLinkErrors, ReLinker will now log detailed information about the re-linking process to a ReLinker.Logger instance you provide.

In this example, myLogger will receive the following debug messages:

D/ReLinker: Beginning load of mylibrary...
D/ReLinker: mylibrary was not loaded normally, re-linking...
D/ReLinker: Looking for lib/x86/libmylibrary.so in APK...
D/ReLinker: Found lib/x86/libmylibrary.so! Extracting...
D/ReLinker: mylibrary was re-linked!

It would be highly appreciated if logging information could be provided in any reported ReLinker issues.

Updating Shared Libraries

It is inevitable that your code will change, and with that, ReLinker should be able to support updating your shared libraries on disk with your App’s next update.

Force Re-Linking

When you force a re-link with ReLinker the target library is extracted unconditionally, even if it already exists on disk. This is the easiest way to ensure that your library code is up-to-date, but it is also less efficient in contrast to versioning.

To force re-link your library, simply call force(), like so:

Versioning

If you do not wish to re-link your libraries each time, you may specify a version instead. Specifying a version will make ReLinker only look for that specific version of your library. If it is found, its loaded normally. However, if it is not found then it is re-linked and all other versions of the same library that may exist are automatically cleaned up.

If the system’s library loader functions correctly, then the version specified is “ignored” in the sense that ReLinker does not do anything with it. This is because each shared library is replaced / updated if needed when an app is updated.

ReLinker follows the standard naming convention for shared object files. If your library is called foo and you specify version x.y.z then ReLinker will look for and utilize a file named libfoo.so.x.y.z. You can read more about the standard here (section 3.1.1)

For example, heres how to specify version 1.0 for mylibrary:

ReLinker will look for and load libmylibrary.so.1.0

Builder Pattern

With the 1.2 release, ReLinker is adopting a builder pattern for configured calls. Each call to loadLibrary can be augmented by a few preceding calls.

log()

log() will log information useful debugging information about the re-linking process.

force()

force() will ensure that the re-linking process occurs, even if the library was already re-linked previously.

recursively()

recursively() will cause ReLinker to consider and re-link any dependencies defined by your library.

Each of these calls can be chained or mixed. Here are a few examples:

I’m excited to release these new features for ReLinker and I hope they greatly benefit your codebase! If you encounter any issues while using ReLinker, please do not hesitate to file an issue at ReLinker’s repository!

Happy programming!

Interested in solving challenging problems? Why not check out our open positions?

--

--