Paul Duffin | a13e8e9 | 2016-07-22 12:16:26 +0100 | [diff] [blame] | 1 | A Java-language API for doing compile time or runtime code generation targeting the Dalvik VM. Unlike |
| 2 | [cglib](http://cglib.sourceforge.net/) or [ASM](http://asm.ow2.org/), this library creates Dalvik `.dex` |
| 3 | files instead of Java `.class` files. |
| 4 | |
| 5 | It has a small, close-to-the-metal API. This API mirrors the |
| 6 | [Dalvik bytecode specification](http://source.android.com/tech/dalvik/dalvik-bytecode.html) giving you tight |
| 7 | control over the bytecode emitted. Code is generated instruction-by-instruction; you bring your own abstract |
| 8 | syntax tree if you need one. And since it uses Dalvik's `dx` tool as a backend, you get efficient register |
| 9 | allocation and regular/wide instruction selection for free. |
| 10 | |
| 11 | Class Proxies |
| 12 | ------------- |
| 13 | |
| 14 | Dexmaker includes a stock code generator for [class proxies](http://dexmaker.googlecode.com/git/javadoc/com/google/dexmaker/stock/ProxyBuilder.html). |
| 15 | If you just want to do AOP or class mocking, you don't need to mess around with bytecodes. |
| 16 | |
| 17 | Mockito Mocks |
| 18 | ------------- |
| 19 | |
| 20 | Dexmaker includes class proxy support for [Mockito](http://code.google.com/p/mockito/). Add the mockito |
| 21 | and the dexmaker `.jar` files to your Android test project's `libs/` directory and you can use Mockito |
| 22 | in your Android unit tests. |
| 23 | |
| 24 | This requires Mockito 1.10.5 or newer. |
| 25 | |
| 26 | Runtime Code Generation |
| 27 | ----------------------- |
| 28 | |
| 29 | This example generates a class and a method. It then loads that class into the current process and invokes its method. |
| 30 | ``` |
| 31 | public final class HelloWorldMaker { |
| 32 | public static void main(String[] args) throws Exception { |
| 33 | DexMaker dexMaker = new DexMaker(); |
| 34 | |
| 35 | // Generate a HelloWorld class. |
| 36 | TypeId<?> helloWorld = TypeId.get("LHelloWorld;"); |
| 37 | dexMaker.declare(helloWorld, "HelloWorld.generated", Modifier.PUBLIC, TypeId.OBJECT); |
| 38 | generateHelloMethod(dexMaker, helloWorld); |
| 39 | |
| 40 | // Create the dex file and load it. |
| 41 | File outputDir = new File("."); |
| 42 | ClassLoader loader = dexMaker.generateAndLoad(HelloWorldMaker.class.getClassLoader(), |
| 43 | outputDir, outputDir); |
| 44 | Class<?> helloWorldClass = loader.loadClass("HelloWorld"); |
| 45 | |
| 46 | // Execute our newly-generated code in-process. |
| 47 | helloWorldClass.getMethod("hello").invoke(null); |
| 48 | } |
| 49 | |
| 50 | /** |
| 51 | * Generates Dalvik bytecode equivalent to the following method. |
| 52 | * public static void hello() { |
| 53 | * int a = 0xabcd; |
| 54 | * int b = 0xaaaa; |
| 55 | * int c = a - b; |
| 56 | * String s = Integer.toHexString(c); |
| 57 | * System.out.println(s); |
| 58 | * return; |
| 59 | * } |
| 60 | */ |
| 61 | private static void generateHelloMethod(DexMaker dexMaker, TypeId<?> declaringType) { |
| 62 | // Lookup some types we'll need along the way. |
| 63 | TypeId<System> systemType = TypeId.get(System.class); |
| 64 | TypeId<PrintStream> printStreamType = TypeId.get(PrintStream.class); |
| 65 | |
| 66 | // Identify the 'hello()' method on declaringType. |
| 67 | MethodId hello = declaringType.getMethod(TypeId.VOID, "hello"); |
| 68 | |
| 69 | // Declare that method on the dexMaker. Use the returned Code instance |
| 70 | // as a builder that we can append instructions to. |
| 71 | Code code = dexMaker.declare(hello, Modifier.STATIC | Modifier.PUBLIC); |
| 72 | |
| 73 | // Declare all the locals we'll need up front. The API requires this. |
| 74 | Local<Integer> a = code.newLocal(TypeId.INT); |
| 75 | Local<Integer> b = code.newLocal(TypeId.INT); |
| 76 | Local<Integer> c = code.newLocal(TypeId.INT); |
| 77 | Local<String> s = code.newLocal(TypeId.STRING); |
| 78 | Local<PrintStream> localSystemOut = code.newLocal(printStreamType); |
| 79 | |
| 80 | // int a = 0xabcd; |
| 81 | code.loadConstant(a, 0xabcd); |
| 82 | |
| 83 | // int b = 0xaaaa; |
| 84 | code.loadConstant(b, 0xaaaa); |
| 85 | |
| 86 | // int c = a - b; |
| 87 | code.op(BinaryOp.SUBTRACT, c, a, b); |
| 88 | |
| 89 | // String s = Integer.toHexString(c); |
| 90 | MethodId<Integer, String> toHexString |
| 91 | = TypeId.get(Integer.class).getMethod(TypeId.STRING, "toHexString", TypeId.INT); |
| 92 | code.invokeStatic(toHexString, s, c); |
| 93 | |
| 94 | // System.out.println(s); |
| 95 | FieldId<System, PrintStream> systemOutField = systemType.getField(printStreamType, "out"); |
| 96 | code.sget(systemOutField, localSystemOut); |
| 97 | MethodId<PrintStream, Void> printlnMethod = printStreamType.getMethod( |
| 98 | TypeId.VOID, "println", TypeId.STRING); |
| 99 | code.invokeVirtual(printlnMethod, null, localSystemOut, s); |
| 100 | |
| 101 | // return; |
| 102 | code.returnVoid(); |
| 103 | } |
| 104 | } |
| 105 | ``` |
| 106 | |
| 107 | Use it in your app |
| 108 | ------------------ |
| 109 | |
Garfield Tan | 40a3dd8 | 2019-09-10 15:29:53 -0700 | [diff] [blame] | 110 | For Mockito support, download the latest .jar via Maven: |
| 111 | ```xml |
| 112 | <dependency> |
| 113 | <groupId>com.linkedin.dexmaker</groupId> |
| 114 | <artifactId>dexmaker-mockito</artifactId> |
| 115 | <version>2.25.0</version> |
| 116 | <type>pom</type> |
| 117 | </dependency> |
| 118 | ``` |
Paul Duffin | a13e8e9 | 2016-07-22 12:16:26 +0100 | [diff] [blame] | 119 | |
| 120 | ``` |
Garfield Tan | 40a3dd8 | 2019-09-10 15:29:53 -0700 | [diff] [blame] | 121 | androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.25.0' |
Paul Duffin | a13e8e9 | 2016-07-22 12:16:26 +0100 | [diff] [blame] | 122 | ``` |
| 123 | |
| 124 | Download [dexmaker-1.2.jar](http://search.maven.org/remotecontent?filepath=com/google/dexmaker/dexmaker/1.2/dexmaker-1.2.jar) |
| 125 | and [dexmaker-mockito-1.2.jar](http://search.maven.org/remotecontent?filepath=com/google/dexmaker/dexmaker-mockito/1.2/dexmaker-mockito-1.2.jar). |
| 126 | |
| 127 | Run the Unit Tests |
| 128 | ------------------ |
| 129 | |
| 130 | The unit tests for dexmaker must be run on a dalvikvm. In order to do this, you can use [Vogar](https://code.google.com/p/vogar/) in the following fashion: |
| 131 | |
| 132 | ``` |
| 133 | $ java -jar vogar.jar --mode device --sourcepath /path/to/dexmaker/dexmaker/src/test/java --sourcepath /path/to/dexmaker/dexmaker/src/main/java --sourcepath /path/to/dexmaker/dx/src/main/java --device-dir /data/dexmaker /path/to/dexmaker/dexmaker/src/test/ |
| 134 | ``` |
| 135 | |
| 136 | Download [vogar.jar](https://vogar.googlecode.com/files/vogar.jar). |