Merge "Update layoutlib create README [DO NOT MERGE]" into klp-modular-dev

This commit is contained in:
Deepanshu Gupta
2014-07-22 00:58:56 +00:00
committed by Android (Google) Code Review

View File

@@ -4,46 +4,45 @@
- Description - - Description -
--------------- ---------------
Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform
to perform layout. layout.
- Usage - - Usage -
--------- ---------
./layoutlib_create path/to/android.jar destination.jar ./layoutlib_create destination.jar path/to/android1.jar path/to/android2.jar
- Design Overview - - Design Overview -
------------------- -------------------
Layoutlib_create uses the "android.jar" containing all the Java code used by Android Layoutlib_create uses a few jars from the framework containing the Java code used by Android as
as generated by the Android build, right before the classes are converted to a DEX format. generated by the Android build, right before the classes are converted to a DEX format.
The Android JAR can't be used directly in Eclipse: These jars can't be used directly in Eclipse as:
- it contains references to native code (which we want to avoid in Eclipse), - they contains references to native code (which we want to avoid in Eclipse),
- some classes need to be overridden, for example all the drawing code that is - some classes need to be overridden, for example all the drawing code that is replaced by Java 2D
replaced by Java 2D calls in Eclipse. calls in Eclipse.
- some of the classes that need to be changed are final and/or we need access - some of the classes that need to be changed are final and/or we need access to their private
to their private internal state. internal state.
Consequently this tool: Consequently this tool:
- parses the input JAR, - parses the input JAR,
- modifies some of the classes directly using some bytecode manipulation, - modifies some of the classes directly using some bytecode manipulation,
- filters some packages and removes those we don't want in the output JAR, - filters some packages and removes those we don't want in the output JAR,
- injects some new classes, - injects some new classes,
- generates a modified JAR file that is suitable for the Android plugin - generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform
for Eclipse to perform rendering. rendering.
The ASM library is used to do the bytecode modification using its visitor pattern API. The ASM library is used to do the bytecode modification using its visitor pattern API.
The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the The layoutlib_create is *NOT* generic. There is no configuration file. Instead all the configuration
configuration is done in the main() method and the CreateInfo structure is expected to is done in the main() method and the CreateInfo structure is expected to change with the Android
change with the Android platform as new classes are added, changed or removed. platform as new classes are added, changed or removed.
The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the The resulting JAR is used by layoutlib_bridge (a.k.a. "the bridge"), also part of the platform, that
platform, that provides all the necessary missing implementation for rendering graphics provides all the necessary missing implementation for rendering graphics in Eclipse.
in Eclipse.
@@ -58,97 +57,96 @@ The tool works in two phases:
- Analyzer - Analyzer
---------- ----------
The goal of the analyzer is to create a graph of all the classes from the input JAR The goal of the analyzer is to create a graph of all the classes from the input JAR with their
with their dependencies and then only keep the ones we want. dependencies and then only keep the ones we want.
To do that, the analyzer is created with a list of base classes to keep -- everything To do that, the analyzer is created with a list of base classes to keep -- everything that derives
that derives from these is kept. Currently the one such class is android.view.View: from these is kept. Currently the one such class is android.view.View: since we want to render
since we want to render layouts, anything that is sort of a view needs to be kept. layouts, anything that is sort of a view needs to be kept.
The analyzer is also given a list of class names to keep in the output. The analyzer is also given a list of class names to keep in the output. This is done using
This is done using shell-like glob patterns that filter on the fully-qualified shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**"
class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does, ("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we
and "." and "$" are interpreted as-is). almost but not quite request the inclusion of full packages.
In practice we almost but not quite request the inclusion of full packages.
The analyzer is also given a list of classes to exclude. A fake implementation of these The analyzer is also given a list of classes to exclude. A fake implementation of these classes is
classes is injected by the Generator. injected by the Generator.
With this information, the analyzer parses the input zip to find all the classes. With this information, the analyzer parses the input zip to find all the classes. All classes
All classes deriving from the requested bases classes are kept. deriving from the requested bases classes are kept. All classes whose name match the glob pattern
All classes which name matched the glob pattern are kept. are kept. The analysis then finds all the dependencies of the classes that are to be kept using an
The analysis then finds all the dependencies of the classes that are to be kept ASM visitor on the class, the field types, the method types and annotations types. Classes that
using an ASM visitor on the class, the field types, the method types and annotations types. belong to the current JRE are excluded.
Classes that belong to the current JRE are excluded.
The output of the analyzer is a set of ASM ClassReader instances which are then The output of the analyzer is a set of ASM ClassReader instances which are then fed to the
fed to the generator. generator.
- Generator - Generator
----------- -----------
The generator is constructed from a CreateInfo struct that acts as a config file The generator is constructed from a CreateInfo struct that acts as a config file and lists:
and lists: - the classes to inject in the output JAR -- these classes are directly implemented in
- the classes to inject in the output JAR -- these classes are directly implemented layoutlib_create and will be used to interface with the renderer in Eclipse.
in layoutlib_create and will be used to interface with the renderer in Eclipse.
- specific methods to override (see method stubs details below). - specific methods to override (see method stubs details below).
- specific methods for which to delegate calls. - specific methods for which to delegate calls.
- specific methods to remove based on their return type. - specific methods to remove based on their return type.
- specific classes to rename. - specific classes to rename.
- specific classes to refactor. - specific classes to refactor.
Each of these are specific strategies we use to be able to modify the Android code Each of these are specific strategies we use to be able to modify the Android code to fit within the
to fit within the Eclipse renderer. These strategies are explained beow. Eclipse renderer. These strategies are explained beow.
The core method of the generator is transform(): it takes an input ASM ClassReader The core method of the generator is transform(): it takes an input ASM ClassReader and modifies it
and modifies it to produce a byte array suitable for the final JAR file. to produce a byte array suitable for the final JAR file.
The first step of the transformation is to implement the method delegates. The first step of the transformation is to implement the method delegates.
The TransformClassAdapter is then used to process the potentially renamed class. The TransformClassAdapter is then used to process the potentially renamed class. All protected or
All protected or private classes are market as public. private classes are market as public. All classes are made non-final. Interfaces are left as-is.
All classes are made non-final.
Interfaces are left as-is.
If a method has a return type that must be erased, the whole method is skipped. If a method has a return type that must be erased, the whole method is skipped. Methods are also
Methods are also changed from protected/private to public. changed from protected/private to public. The code of the methods is then kept as-is, except for
The code of the methods is then kept as-is, except for native methods which are native methods which are replaced by a stub. Methods that are to be overridden are also replaced by
replaced by a stub. Methods that are to be overridden are also replaced by a stub. a stub.
Finally fields are also visited and changed from protected/private to public. Finally fields are also visited and changed from protected/private to public.
The next step of the transformation is changing the name of the class in case The next step of the transformation is changing the name of the class in case we requested the class
we requested the class to be renamed. This uses the RenameClassAdapter to also rename to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in
all inner classes and references in methods and types. Note that other classes are methods and types. Note that other classes are not transformed and keep referencing the original
not transformed and keep referencing the original name. name.
The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but The class is then fed to RefactorClassAdapter which is like RenameClassAdapter but updates the
updates the references in all classes. This is used to update the references of classes references in all classes. This is used to update the references of classes in the java package that
in the java package that were added in the Dalvik VM but are not a part of the standard were added in the Dalvik VM but are not a part of the standard JVM. The existing classes are
JVM. The existing classes are modified to update all references to these non-standard modified to update all references to these non-standard classes. An alternate implementation of
classes. An alternate implementation of these (com.android.tools.layoutlib.java.*) is these (com.android.tools.layoutlib.java.*) is injected.
injected.
The ClassAdapters are chained together to achieve the desired output. (Look at section RenameClassAdapter and RefactorClassAdapter both inherit from AbstractClassAdapter which changes the
2.2.7 Transformation chains in the asm user guide, link in the References.) The order of class version (version of the JDK used to compile the class) to 50 (corresponding to Java 6), if the
execution of these is: class was originally compiled with Java 7 (version 51). This is because we don't currently generate
the StackMapTable correctly and Java 7 VM enforces that classes with version greater than 51 have
valid StackMapTable. As a side benefit of this, we can continue to support Java 6 because Java 7 on
Mac has horrible font rendering support.
The ClassAdapters are chained together to achieve the desired output. (Look at section 2.2.7
Transformation chains in the asm user guide, link in the References.) The order of execution of
these is:
ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] -> ClassReader -> [DelegateClassAdapter] -> TransformClassAdapter -> [RenameClassAdapter] ->
RefactorClassAdapter -> ClassWriter RefactorClassAdapter -> ClassWriter
- Method stubs - Method stubs
-------------- --------------
As indicated above, all native and overridden methods are replaced by a stub. As indicated above, all native and overridden methods are replaced by a stub. We don't have the
We don't have the code to replace with in layoutlib_create. code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the
Instead the StubMethodAdapter replaces the code of the method by a call to method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register
OverrideMethod.invokeX(). When using the final JAR, the bridge can register
listeners from these overridden method calls based on the method signatures. listeners from these overridden method calls based on the method signatures.
The listeners are currently pretty basic: we only pass the signature of the The listeners are currently pretty basic: we only pass the signature of the method being called, its
method being called, its caller object and a flag indicating whether the caller object and a flag indicating whether the method was native. We do not currently provide the
method was native. We do not currently provide the parameters. The listener parameters. The listener can however specify the return value of the overridden method.
can however specify the return value of the overridden method.
This strategy is now obsolete and replaced by the method delegates. This strategy is now obsolete and replaced by the method delegates.
@@ -156,97 +154,89 @@ This strategy is now obsolete and replaced by the method delegates.
- Strategies - Strategies
------------ ------------
We currently have 6 strategies to deal with overriding the rendering code We currently have 6 strategies to deal with overriding the rendering code and make it run in
and make it run in Eclipse. Most of these strategies are implemented hand-in-hand Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse)
by the bridge (which runs in Eclipse) and the generator. and the generator.
1- Class Injection 1- Class Injection
This is the easiest: we currently inject the following classes: This is the easiest: we currently inject the following classes:
- OverrideMethod and its associated MethodListener and MethodAdapter are used - OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to
to intercept calls to some specific methods that are stubbed out and change some specific methods that are stubbed out and change their return value.
their return value. - CreateInfo class, which configured the generator. Not used yet, but could in theory help us track
- CreateInfo class, which configured the generator. Not used yet, but could what the generator changed.
in theory help us track what the generator changed. - AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new classes are
- AutoCloseable and Objects are part of Java 7. To enable us to still run on Java 6, new injected. The implementation for these classes has been taken from Android's libcore
classes are injected. The implementation for these classes has been taken from (platform/libcore/luni/src/main/java/java/...).
Android's libcore (platform/libcore/luni/src/main/java/java/...). - Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. They are
- Charsets, IntegralToString and UnsafeByteSequence are not part of the standard JAVA VM. added to the Dalvik VM for performance reasons. An implementation that is very close to the
They are added to the Dalvik VM for performance reasons. An implementation that is very original (which is at platform/libcore/luni/src/main/java/...) is injected. Since these classees
close to the original (which is at platform/libcore/luni/src/main/java/...) is injected. were in part of the java package, where we can't inject classes, all references to these have been
Since these classees were in part of the java package, where we can't inject classes, updated (See strategy 4- Refactoring Classes).
all references to these have been updated (See strategy 4- Refactoring Classes).
2- Overriding methods 2- Overriding methods
As explained earlier, the creator doesn't have any replacement code for As explained earlier, the creator doesn't have any replacement code for methods to override. Instead
methods to override. Instead it removes the original code and replaces it it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The
by a call to a specific OveriddeMethod.invokeX(). The bridge then registers bridge then registers a listener on the method signature and can provide an implementation.
a listener on the method signature and can provide an implementation.
This strategy is now obsolete and replaced by the method delegates. This strategy is now obsolete and replaced by the method delegates (See strategy 6- Method
See strategy 5 below. Delegates).
3- Renaming classes 3- Renaming classes
This simply changes the name of a class in its definition, as well as all its This simply changes the name of a class in its definition, as well as all its references in internal
references in internal inner classes and methods. inner classes and methods. Calls from other classes are not modified -- they keep referencing the
Calls from other classes are not modified -- they keep referencing the original original class name. This allows the bridge to literally replace an implementation.
class name. This allows the bridge to literally replace an implementation.
An example will make this easier: android.graphics.Paint is the main drawing An example will make this easier: android.graphics.Paint is the main drawing class that we need to
class that we need to replace. To do so, the generator renames Paint to _original_Paint. replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own
Later the bridge provides its own replacement version of Paint which will be used replacement version of Paint which will be used by the rest of the Android stack. The replacement
by the rest of the Android stack. The replacement version of Paint can still use version of Paint can still use (either by inheritance or delegation) all the original non-native
(either by inheritance or delegation) all the original non-native code of _original_Paint code of _original_Paint if it so desires.
if it so desires.
Some of the Android classes are basically wrappers over native objects and since Some of the Android classes are basically wrappers over native objects and since we don't have the
we don't have the native code in Eclipse, we need to provide a full alternate native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't
implementation. Sub-classing doesn't work as some native methods are static and work as some native methods are static and we don't control object creation.
we don't control object creation.
This won't rename/replace the inner static methods of a given class. This won't rename/replace the inner static methods of a given class.
4- Refactoring classes 4- Refactoring classes
This is very similar to the Renaming classes except that it also updates the reference in This is very similar to the Renaming classes except that it also updates the reference in all
all classes. This is done for classes which are added to the Dalvik VM for performance classes. This is done for classes which are added to the Dalvik VM for performance reasons but are
reasons but are not present in the Standard Java VM. An implementation for these classes not present in the Standard Java VM. An implementation for these classes is also injected.
is also injected.
5- Method erasure based on return type 5- Method erasure based on return type
This is mostly an implementation detail of the bridge: in the Paint class This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some
mentioned above, some inner static classes are used to pass around inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and
attributes (e.g. FontMetrics, or the Style enum) and all the original implementation all the original implementation is native.
is native.
In this case we have a strategy that tells the generator that anything returning, for In this case we have a strategy that tells the generator that anything returning, for example, the
example, the inner class Paint$Style in the Paint class should be discarded and the inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own
bridge will provide its own implementation. implementation.
6- Method Delegates 6- Method Delegates
This strategy is used to override method implementations. This strategy is used to override method implementations. Given a method SomeClass.MethodName(), 1
Given a method SomeClass.MethodName(), 1 or 2 methods are generated: or 2 methods are generated:
a- A copy of the original method named SomeClass.MethodName_Original(). a- A copy of the original method named SomeClass.MethodName_Original(). The content is the original
The content is the original method as-is from the reader. method as-is from the reader. This step is omitted if the method is native, since it has no Java
This step is omitted if the method is native, since it has no Java implementation. implementation.
b- A brand new implementation of SomeClass.MethodName() which calls to a b- A brand new implementation of SomeClass.MethodName() which calls to a non-existing static method
non-existing static method named SomeClass_Delegate.MethodName(). named SomeClass_Delegate.MethodName(). The implementation of this 'delegate' method is done in
The implementation of this 'delegate' method is done in layoutlib_brigde. layoutlib_brigde.
The delegate method is a static method. The delegate method is a static method. If the original method is non-static, the delegate method
If the original method is non-static, the delegate method receives the original 'this' receives the original 'this' as its first argument. If the original method is an inner non-static
as its first argument. If the original method is an inner non-static method, it also method, it also receives the inner 'this' as the second argument.
receives the inner 'this' as the second argument.