dyso

repository·master·Indexed 18 days ago

https://github.com/testplanb/dyso

An Android library for the dynamic loading of native SO libraries at runtime from custom paths. It provides tools to reduce APK size by excluding bundled SO files, manual loading via DynamicSoLauncher, and automatic loading using the @DynamicLoad annotation via the lib_sillyplugin. The library includes functionality to parse ELF files for recursive dependency loading and a method to inject custom library paths into the native system.

Tokens
1.5K
Snippets
6
Records
6
Agent score
13%

What's inside dyso

  1. Install the lib_sillyplugin for automatic SO loading

    master

    To use the @DynamicLoad annotation, which automatically intercepts System.loadLibrary calls via bytecode instrumentation, you must apply the lib_sillyplugin in your app's build.gradle file.

    apply plugin: 'com.plugins.core'
  2. Remove local SO libraries from APK

    master

    To ensure your app uses dynamically loaded SO libraries instead of the ones bundled in the APK, you can exclude them during the build process using one of two methods.

    Method 1: Using packagingOptions

    Add exclude rules to your packagingOptions in the build.gradle file to prevent specific architectures or libraries from being packaged.

    Method 2: Using a custom Gradle task

    You can use a custom task to delete specific SO files from the merged native libs directory. This allows for more granular control, such as matching partial names.

    Note: The provided task example uses an ext block to define deleteSoName and hooks into the mergeDebugNativeLibs and stripDebugDebugSymbols tasks via afterEvaluate to ensure the deletion happens at the correct stage of the build lifecycle.

    ext {
        deleteSoName = ["libnativecpptwo.so","libnativecpp.so"]
    }
    
    task(dynamicSo) {
        // ... logic to delete files matching deleteSoName from build/intermediates/merged_native_libs/debug/out/lib
    }.doLast {
        // ...
    }
    
    afterEvaluate {
        def customer = tasks.findByName("dynamicSo")
        def merge = tasks.findByName("mergeDebugNativeLibs")
        def strip = tasks.findByName("stripDebugDebugSymbols")
        if (merge != null || strip != null) {
            customer.mustRunAfter(merge)
            strip.dependsOn(customer)
        }
    }
  3. Initialize DynamicSoLauncher

    master

    Before loading dynamic SO libraries, you must initialize the configuration using initDynamicSoConfig. This sets the base path where your downloaded SO files are located and provides a callback for custom logic (like version validation).

    Parameters:

    1. context: The Android Context.
    2. path: The directory path where the downloaded SO files reside. The app must have write permissions for this path.
    3. callback: A lambda/callback that receives a boolean. If it returns true, the library loading logic proceeds. If false, loading is aborted. This is useful for implementing version checks or security validations.
    DynamicSoLauncher.INSTANCE.initDynamicSoConfig(this, path, s -> {
        // Handle custom logic (e.g., version validation)
        return true;
    });
  4. Load SO libraries dynamically

    master

    There are two ways to trigger the dynamic loading of SO libraries: manual loading and annotation-based loading.

    1. Manual Loading

    Replace System.loadLibrary(String) with DynamicSoLauncher.INSTANCE.loadSoDynamically(File). The File object must point to an SO file located within the path specified during initialization.

    2. Annotation-based Loading (Automatic)

    To automatically replace all System.loadLibrary calls in a class with the dynamic loading logic, annotate the class with @DynamicLoad. This requires the lib_sillyplugin plugin to be applied to your project, as it uses bytecode instrumentation to perform the replacement.

    // Manual loading
    DynamicSoLauncher.INSTANCE.loadSoDynamically(file);
    
    // Annotation-based loading (requires lib_sillyplugin)
    //@DynamicLoad
    public class MainActivity extends AppCompatActivity {
        // System.loadLibrary calls here will be intercepted
    }
  5. Inject a library path into the native system

    master

    To ensure that System.loadLibrary() can find custom .so files located in non-standard directories, use insertPathToNativeSystem(Context context, File file). This method uses LoadLibraryUtils to install the specified file's path into the application's ClassLoader, making the libraries within that directory available to the standard system loading mechanism.

    // Inject a directory containing .so files into the ClassLoader
    File libraryDir = new File("/data/data/com.example/files/libs");
    DynamicSo.insertPathToNativeSystem(context, libraryDir);
  6. Load native SO libraries dynamically

    master

    Use loadSoDynamically(File soFile, String path) to load a shared object (.so) file along with its dependencies. The method parses the ELF file to identify required dependencies. If a dependency is found within the provided path, it is loaded recursively. If a dependency is not found in the path, the method attempts to load it using System.loadLibrary(), assuming it is a standard system library (like liblog.so) that has been made available via insertPathToNativeSystem.

    // Example usage
    File mySoFile = new File("/data/data/com.example/files/libnative-lib.so");
    String searchPath = "/data/data/com.example/files/";
    DynamicSo.loadSoDynamically(mySoFile, searchPath);