137 lines
5.5 KiB
Markdown
137 lines
5.5 KiB
Markdown
A Java-language API for doing compile time or runtime code generation targeting the Dalvik VM. Unlike
|
|
[cglib](http://cglib.sourceforge.net/) or [ASM](http://asm.ow2.org/), this library creates Dalvik `.dex`
|
|
files instead of Java `.class` files.
|
|
|
|
It has a small, close-to-the-metal API. This API mirrors the
|
|
[Dalvik bytecode specification](http://source.android.com/tech/dalvik/dalvik-bytecode.html) giving you tight
|
|
control over the bytecode emitted. Code is generated instruction-by-instruction; you bring your own abstract
|
|
syntax tree if you need one. And since it uses Dalvik's `dx` tool as a backend, you get efficient register
|
|
allocation and regular/wide instruction selection for free.
|
|
|
|
Class Proxies
|
|
-------------
|
|
|
|
Dexmaker includes a stock code generator for [class proxies](http://dexmaker.googlecode.com/git/javadoc/com/google/dexmaker/stock/ProxyBuilder.html).
|
|
If you just want to do AOP or class mocking, you don't need to mess around with bytecodes.
|
|
|
|
Mockito Mocks
|
|
-------------
|
|
|
|
Dexmaker includes class proxy support for [Mockito](http://code.google.com/p/mockito/). Add the mockito
|
|
and the dexmaker `.jar` files to your Android test project's `libs/` directory and you can use Mockito
|
|
in your Android unit tests.
|
|
|
|
This requires Mockito 1.10.5 or newer.
|
|
|
|
Runtime Code Generation
|
|
-----------------------
|
|
|
|
This example generates a class and a method. It then loads that class into the current process and invokes its method.
|
|
```
|
|
public final class HelloWorldMaker {
|
|
public static void main(String[] args) throws Exception {
|
|
DexMaker dexMaker = new DexMaker();
|
|
|
|
// Generate a HelloWorld class.
|
|
TypeId<?> helloWorld = TypeId.get("LHelloWorld;");
|
|
dexMaker.declare(helloWorld, "HelloWorld.generated", Modifier.PUBLIC, TypeId.OBJECT);
|
|
generateHelloMethod(dexMaker, helloWorld);
|
|
|
|
// 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 our newly-generated code in-process.
|
|
helloWorldClass.getMethod("hello").invoke(null);
|
|
}
|
|
|
|
/**
|
|
* Generates Dalvik bytecode equivalent to the following method.
|
|
* public static void hello() {
|
|
* int a = 0xabcd;
|
|
* int b = 0xaaaa;
|
|
* int c = a - b;
|
|
* String s = Integer.toHexString(c);
|
|
* System.out.println(s);
|
|
* return;
|
|
* }
|
|
*/
|
|
private static void generateHelloMethod(DexMaker dexMaker, TypeId<?> declaringType) {
|
|
// Lookup some types we'll need along the way.
|
|
TypeId<System> systemType = TypeId.get(System.class);
|
|
TypeId<PrintStream> printStreamType = TypeId.get(PrintStream.class);
|
|
|
|
// Identify the 'hello()' method on declaringType.
|
|
MethodId hello = declaringType.getMethod(TypeId.VOID, "hello");
|
|
|
|
// Declare that method on the dexMaker. Use the returned Code instance
|
|
// as a builder that we can append instructions to.
|
|
Code code = dexMaker.declare(hello, Modifier.STATIC | Modifier.PUBLIC);
|
|
|
|
// Declare all the locals we'll need up front. The API requires this.
|
|
Local<Integer> a = code.newLocal(TypeId.INT);
|
|
Local<Integer> b = code.newLocal(TypeId.INT);
|
|
Local<Integer> c = code.newLocal(TypeId.INT);
|
|
Local<String> s = code.newLocal(TypeId.STRING);
|
|
Local<PrintStream> localSystemOut = code.newLocal(printStreamType);
|
|
|
|
// int a = 0xabcd;
|
|
code.loadConstant(a, 0xabcd);
|
|
|
|
// int b = 0xaaaa;
|
|
code.loadConstant(b, 0xaaaa);
|
|
|
|
// int c = a - b;
|
|
code.op(BinaryOp.SUBTRACT, c, a, b);
|
|
|
|
// String s = Integer.toHexString(c);
|
|
MethodId<Integer, String> toHexString
|
|
= TypeId.get(Integer.class).getMethod(TypeId.STRING, "toHexString", TypeId.INT);
|
|
code.invokeStatic(toHexString, s, c);
|
|
|
|
// System.out.println(s);
|
|
FieldId<System, PrintStream> systemOutField = systemType.getField(printStreamType, "out");
|
|
code.sget(systemOutField, localSystemOut);
|
|
MethodId<PrintStream, Void> printlnMethod = printStreamType.getMethod(
|
|
TypeId.VOID, "println", TypeId.STRING);
|
|
code.invokeVirtual(printlnMethod, null, localSystemOut, s);
|
|
|
|
// return;
|
|
code.returnVoid();
|
|
}
|
|
}
|
|
```
|
|
|
|
Use it in your app
|
|
------------------
|
|
|
|
For Mockito support, download the latest .jar via Maven:
|
|
```xml
|
|
<dependency>
|
|
<groupId>com.linkedin.dexmaker</groupId>
|
|
<artifactId>dexmaker-mockito</artifactId>
|
|
<version>2.25.0</version>
|
|
<type>pom</type>
|
|
</dependency>
|
|
```
|
|
|
|
```
|
|
androidTestCompile 'com.linkedin.dexmaker:dexmaker-mockito:2.25.0'
|
|
```
|
|
|
|
Download [dexmaker-1.2.jar](http://search.maven.org/remotecontent?filepath=com/google/dexmaker/dexmaker/1.2/dexmaker-1.2.jar)
|
|
and [dexmaker-mockito-1.2.jar](http://search.maven.org/remotecontent?filepath=com/google/dexmaker/dexmaker-mockito/1.2/dexmaker-mockito-1.2.jar).
|
|
|
|
Run the Unit Tests
|
|
------------------
|
|
|
|
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:
|
|
|
|
```
|
|
$ 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/
|
|
```
|
|
|
|
Download [vogar.jar](https://vogar.googlecode.com/files/vogar.jar).
|