|
|||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |
java.lang.Object com.google.dexmaker.DexMaker
public final class DexMaker
Generates a Dalvik EXecutable (dex) file for execution on Android. Dex files define classes and interfaces, including their member methods and fields, executable code, and debugging information. They also define annotations, though this API currently has no facility to create a dex file that contains annotations.
This library is intended to satisfy two use cases:
package com.publicobject.fib;
public class Fibonacci {
public static int fib(int i) {
if (i < 2) {
return i;
}
return fib(i - 1) + fib(i - 2);
}
}
We start by creating a TypeId
to identify the generated Fibonacci
class. DexMaker identifies types by their internal names like
Ljava/lang/Object;
rather than their Java identifiers like java.lang.Object
.
TypeId<?> fibonacci = TypeId.get("Lcom/google/dexmaker/examples/Fibonacci;");
Next we declare the class. It allows us to specify the type's source file
for stack traces, its modifiers, its superclass, and the interfaces it
implements. In this case, Fibonacci
is a public class that extends
from Object
:
String fileName = "Fibonacci.generated";
DexMaker dexMaker = new DexMaker();
dexMaker.declare(fibonacci, fileName, Modifier.PUBLIC, TypeId.OBJECT);
It is illegal to declare members of a class without also declaring the class
itself.
To make it easier to go from our Java method to dex instructions, we'll
manually translate it to pseudocode fit for an assembler. We need to replace
control flow like if()
blocks and for()
loops with labels and
branches. We'll also avoid performing multiple operations in one statement,
using local variables to hold intermediate values as necessary:
int constant1 = 1;
int constant2 = 2;
if (i < constant2) goto baseCase;
int a = i - constant1;
int b = i - constant2;
int c = fib(a);
int d = fib(b);
int result = c + d;
return result;
baseCase:
return i;
We look up the MethodId
for the method on the declaring type. This
takes the method's return type (possibly TypeId.VOID
), its name and
its parameters types. Next we declare the method, specifying its modifiers by
bitwise ORing constants from Modifier
. The declare
call returns a Code
object, which we'll use to define the method's
instructions.
MethodId<?, Integer> fib = fibonacci.getMethod(TypeId.INT, "fib", TypeId.INT);
Code code = dexMaker.declare(fib, Modifier.PUBLIC | Modifier.STATIC);
One limitation of DexMaker
's API is that it requires all local
variables to be created before any instructions are emitted. Use newLocal()
to create a new local variable. The method's
parameters are exposed as locals using getParameter()
. For non-static methods the this
pointer is exposed
using getThis()
. Here we declare all of the local
variables that we'll need for our fib()
method:
Local<Integer> i = code.getParameter(0, TypeId.INT);
Local<Integer> constant1 = code.newLocal(TypeId.INT);
Local<Integer> constant2 = code.newLocal(TypeId.INT);
Local<Integer> a = code.newLocal(TypeId.INT);
Local<Integer> b = code.newLocal(TypeId.INT);
Local<Integer> c = code.newLocal(TypeId.INT);
Local<Integer> d = code.newLocal(TypeId.INT);
Local<Integer> result = code.newLocal(TypeId.INT);
Notice that Local
has a type parameter of Integer
. This is
useful for generating code that works with existing types like String
and Integer
, but it can be a hindrance when generating code that
involves new types. For this reason you may prefer to use raw types only and
add @SuppressWarnings("unsafe")
on your calling code. This will yield
the same result but you won't get IDE support if you make a type error.
We're ready to start defining our method's instructions. The Code
class catalogs the available instructions and their use.
code.loadConstant(constant1, 1);
code.loadConstant(constant2, 2);
Label baseCase = new Label();
code.compare(Comparison.LT, baseCase, i, constant2);
code.op(BinaryOp.SUBTRACT, a, i, constant1);
code.op(BinaryOp.SUBTRACT, b, i, constant2);
code.invokeStatic(fib, c, a);
code.invokeStatic(fib, d, b);
code.op(BinaryOp.ADD, result, c, d);
code.returnValue(result);
code.mark(baseCase);
code.returnValue(i);
We're done defining the dex file. We just need to write it to the
filesystem or load it into the current process. For this example we'll load
the generated code into the current process. This only works when the current
process is running on Android. We use generateAndLoad()
which takes the class loader that will be used as our
generated code's parent class loader. It also requires a directory where
temporary files can be written.
ClassLoader loader = dexMaker.generateAndLoad(
FibonacciMaker.class.getClassLoader(), getDataDirectory());
Finally we'll use reflection to lookup our generated class on its class
loader and invoke its fib()
method: Class<?> fibonacciClass = loader.loadClass("com.google.dexmaker.examples.Fibonacci");
Method fibMethod = fibonacciClass.getMethod("fib", int.class);
System.out.println(fibMethod.invoke(null, 8));
Constructor Summary | |
---|---|
DexMaker()
Creates a new DexMaker instance, which can be used to create a
single dex file. |
Method Summary | |
---|---|
void |
declare(FieldId<?,?> fieldId,
int flags,
Object staticValue)
Declares a field. |
Code |
declare(MethodId<?,?> method,
int flags)
Declares a method or constructor. |
void |
declare(TypeId<?> type,
String sourceFile,
int flags,
TypeId<?> supertype,
TypeId<?>... interfaces)
Declares type . |
byte[] |
generate()
Generates a dex file and returns its bytes. |
ClassLoader |
generateAndLoad(ClassLoader parent,
File dexDir)
Generates a dex file and loads its types into the current process. |
Methods inherited from class java.lang.Object |
---|
clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait |
Constructor Detail |
---|
public DexMaker()
DexMaker
instance, which can be used to create a
single dex file.
Method Detail |
---|
public void declare(TypeId<?> type, String sourceFile, int flags, TypeId<?> supertype, TypeId<?>... interfaces)
type
.
flags
- a bitwise combination of Modifier.PUBLIC
, Modifier.FINAL
and Modifier.ABSTRACT
.public Code declare(MethodId<?,?> method, int flags)
flags
- a bitwise combination of Modifier.PUBLIC
, Modifier.PRIVATE
, Modifier.PROTECTED
, Modifier.STATIC
,
Modifier.FINAL
and Modifier.SYNCHRONIZED
.
Warning: the Modifier.SYNCHRONIZED
flag
is insufficient to generate a synchronized method. You must also use
Code.monitorEnter(com.google.dexmaker.Local>)
and Code.monitorExit(com.google.dexmaker.Local>)
to acquire
a monitor.
public void declare(FieldId<?,?> fieldId, int flags, Object staticValue)
flags
- a bitwise combination of Modifier.PUBLIC
, Modifier.PRIVATE
, Modifier.PROTECTED
, Modifier.STATIC
,
Modifier.FINAL
, Modifier.VOLATILE
, and Modifier.TRANSIENT
.staticValue
- a constant representing the initial value for the
static field, possibly null. This must be null if this field is
non-static.public byte[] generate()
public ClassLoader generateAndLoad(ClassLoader parent, File dexDir) throws IOException
All parameters are optional; you may pass null
and suitable
defaults will be used.
If you opt to provide your own dexDir
, take care to ensure
that it is not world-writable, otherwise a malicious app may be able
to inject code into your process. A suitable parameter is:
getApplicationContext().getDir("dx", Context.MODE_PRIVATE);
parent
- the parent ClassLoader to be used when loading
our generated typesdexDir
- the destination directory where generated and
optimized dex files will be written.
IOException
|
|||||||||
PREV CLASS NEXT CLASS | FRAMES NO FRAMES | ||||||||
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD |