Dexmaker Documentation

repository·main·Indexed 24 days ago

https://github.com/linkedin/dexmaker

A Java-language API for generating Dalvik bytecode (.dex files) for the Dalvik VM. Dexmaker provides a low-level, instruction-by-instruction API for runtime code generation and is primarily used to enable Mockito in Android instrumentation tests via dexmaker-mockito and dexmaker-mockito-inline. The inline library supports mocking final classes and methods on Android P (API level 28) and above using a JvmtiAgent.

Tokens
1.6K
Snippets
5
Records
10
Agent score
84%

What's inside Dexmaker

  1. Perform runtime Dalvik bytecode generation with DexMaker

    main

    Dexmaker provides a low-level API for generating Dalvik .dex files instruction-by-instruction. This mirrors the Dalvik bytecode specification.

    To generate and load code at runtime:

    1. Instantiate DexMaker.
    2. Use TypeId to define types (e.g., TypeId.get("LHelloWorld;")).
    3. Use dexMaker.declare(...) to declare classes and methods.
    4. Use the returned Code object to append instructions (e.g., loadConstant, op, invokeStatic, returnVoid).
    5. Call dexMaker.generateAndLoad(...) to create the dex file and load it into a ClassLoader.
    DexMaker dexMaker = new DexMaker();
    
    // Generate a class
    TypeId<?> helloWorld = TypeId.get("LHelloWorld;");
    dexMaker.declare(helloWorld, "HelloWorld.generated", Modifier.PUBLIC, TypeId.OBJECT);
    
    // Generate a method and append instructions
    MethodId hello = helloWorld.getMethod(TypeId.VOID, "hello");
    Code code = dexMaker.declare(hello, Modifier.STATIC | Modifier.PUBLIC);
    Local<Integer> a = code.newLocal(TypeId.INT);
    code.loadConstant(a, 0xabcd);
    // ... more instructions ...
    code.returnVoid();
    
    // Create the dex file and load it
    File outputDir = new File(".");
    ClassLoader loader = dexMaker.generateAndLoad(HelloWorldMaker.class.getClassLoader(), outputDir, outputDir);
    Class<?> helloWorldClass = loader.loadClass("HelloWorld");
    
    // Execute
    helloWorldClass.getMethod("hello").invoke(null);
  2. Use Dexmaker snapshot builds

    main

    To test the latest unreleased changes, add the Sonatype snapshot repository to your Gradle scripts. You can find the latest snapshot version in the project's gradle.properties file.

    repositories {
        maven {
            url "https://oss.sonatype.org/content/repositories/snapshots/"
        }
    }
  3. Install dexmaker-mockito via Maven or Gradle

    main

    To use Dexmaker for Mockito support, add the following dependency to your project. Note that Mockito is included transitively.

    <!-- Maven -->
    <dependency>
      <groupId>com.linkedin.dexmaker</groupId>
      <artifactId>dexmaker-mockito</artifactId>
      <version>2.28.6</version>
      <type>pom</type>
    </dependency>
    // Gradle
    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.28.6'
  4. Repackage the dispatcher APK into a JAR

    main

    The dispatcher.jar used in this project is the classes.dex file extracted from the APK generated by dexmaker-mockito-inline-dispatcher, repackaged into a JAR format. To manually prepare this file, unzip the release APK, extract classes.dex, and package it into a JAR.

    unzip dexmaker-mockito-inline-dispatcher/build/outputs/apk/release/dexmaker-mockito-inline-dispatcher-release-unsigned.apk classes.dex
    jar -cf dexmaker-mockito-inline/src/main/resources/dispatcher.jar classes.dex
    rm classes.dex
  5. Mock final classes and methods with dexmaker-mockito-inline

    main

    If your tests run on a device or emulator with Android P or above, you can mock final classes and methods using the dexmaker-mockito-inline library.

    Important:

    • Use dexmaker-mockito-inline instead of dexmaker-mockito (do not include both).
    • This requires OS APIs introduced in Android P and will not work on older Android versions.
  6. Use Dexmaker for Mockito in Android Instrumentation tests

    main

    Dexmaker allows you to use the Mockito mocking library in Android projects by generating Dalvik bytecode class proxies. To enable this, add dexmaker-mockito as an androidTestImplementation dependency. The major and minor versions of Dexmaker will typically match the version of Mockito it targets.

    androidTestImplementation 'com.linkedin.dexmaker:dexmaker-mockito:2.28.6'
  7. Enable inline mocking via JvmtiAgent

    main

    To enable inline mocking capabilities (such as mocking final classes and methods) on Android, you must instantiate the JvmtiAgent. This process attaches a native JVM TI agent to the process.

    Requirements:

    • Android API Level: Requires API level 28 (Android P) or higher.
    • ClassLoader: The JvmtiAgent class must be loaded by a BaseDexClassLoader.

    Important Note: If multiple agents are performing class transformations, other agents might remove transformations added by this agent.

  8. Request class re-transformation with requestTransformClasses()

    main

    Once the JvmtiAgent is initialized, you can trigger the transformation of existing classes by calling requestTransformClasses(Class<?>[] classes). This instructs the native agent to extract the bytecode of the specified classes and pass them through any registered ClassTransformer instances.

    Throws:

    • UnmodifiableClassException: If one of the requested classes cannot be transformed.