Loading tools/layoutlib/create/README.txt +164 −36 Original line number Original line Diff line number Diff line Loading @@ -4,68 +4,196 @@ - Description - - Description - --------------- --------------- makeLayoutLib generates a library used by the Eclipse graphical layout editor Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform layout. to perform layout. - Usage - - Usage - --------- --------- ./makeLayoutLib path/to/android.jar destination.jar ./layoutlib_create path/to/android.jar destination.jar - Design Overview - ------------------- Layoutlib_create uses the "android.jar" containing all the Java code used by Android as 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: - it 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 replaced by Java 2D calls in Eclipse. - some of the classes that need to be changed are final and/or we need access to their private internal state. Consequently this tool: - parses the input JAR, - modifies some of the classes directly using some bytecode manipulation, - filters some packages and removes some that we don't want to end in the output JAR, - injects some new classes, - and generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform rendering. 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 configuration is done in the main() method and the CreateInfo structure is expected to change with the Android 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 platform, that provides all the necessary missing implementation for rendering graphics in Eclipse. - Implementation Notes - - Implementation Notes - ------------------------ ------------------------ The goal of makeLayoutLib is to list all the classes from the input jar and create a new The tool works in two phases: jar that only keeps certain classes and create stubs for all their dependencies. - first analyze the input jar (AsmAnalyzer class) - then generate the output jar (AsmGenerator class), - Analyzer ---------- The goal of the analyzer is to create a graph of all the classes from the input JAR with their 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 that derives from these is kept. Currently the one such class is android.view.View: since we want to render layouts, anything that is sort of the view needs to be kept. The analyzer is also given a list of class names to keep in the output. This is done using shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we almost but not quite request the inclusion of full packages. With this information, the analyzer parses the input zip to find all the classes. All classes deriving from the requested bases classes are kept. All classes which name matched the glob pattern are kept. The analysis then finds all the dependencies of the classes that are to be kept using an ASM visitor on the class, the field types, the method types and annotations types. Classes that belong to the current JRE are excluded. The output of the analyzer is a set of ASM ClassReader instances which are then fed to the generator. - Generator ----------- First the input jar is parsed to find all the classes defined. The generator is constructed from a CreateInfo struct that acts as a config file and lists: - the classes to inject in the output JAR -- these classes are directly implemented 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 remove based on their return type. - specific classes to rename. In the Main(), the following list of classes are hardcoded (TODO config file later): Each of these are specific strategies we use to be able to modify the Android code - keep all classes that derive from android.view.View. to fit within the Eclipse renderer. These strategies are explained beow. - keep all classes in the android.view and android.widget packages (sub-packages excluded). - keep specific classes such as android.policy.PhoneLayoutInflater. For each class to keep, their dependencies are examined using BCEL. The core method of the generator is transform(): it takes an input ASM ClassReader A dependency is defined as a class needed to instantiate the given class that should be kept, and modifies it to produce a byte array suitable for the final JAR file. directly or indirectly. So a dependency is a class that is used by the input class, that is defined in the input jar and that is not part of the current JRE. Dependencies are computed recursively. The first step of the transformation is changing the name of the class in case we requested the class to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in methods and types. Note that other classes are not transformed and keep referencing the original name. Once all dependencies are found, the final jar can be created. The TransformClassAdapter is then used to process the potentially renamed class. There are three kind of classes to write: All protected or private classes are market as public. - classes that are to be kept as-is. They are just dumped in the new jar unchanged. All classes are made non-final. - classes that are to be kept yet contain native methods or fields. Interfaces are left as-is. - classes that are just dependencies. We don't want to expose their implementation in the final jar. The implementation of native methods and all methods of mock classes is replaced by a stub If a method has a return type that must be erased, the whole method is skipped. that throws UnsupportedOperationException. Methods are also changed from protected/private to public. The code of the methods is then kept as-is, except for native methods which are replaced by a stub. Methods that are to be overridden are also replaced by a stub. Incidentally, the access level of native and mock classes needs to be changed in order for Finally fields are also visited and changed from protected/private to public. native methods to be later overridden. Methods that are "final private native" must become non-final, non-native and at most protected. Package-default access is changed to public. Classes that are final are made non-final. Abstract methods are left untouched. - Method stubs -------------- ---- As indicated above, all native and overridden methods are replaced by a stub. 20080617 Replace Class We don't have the code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register listeners from these overridden method calls based on the method signatures. Some classes are basically wrappers over native objects. The listeners are currently pretty basic: we only pass the signature of the Subclassing doesn't work as most methods are either static or we don't method being called, its caller object and a flag indicating whether the control object creation. In this scenario the idea is to be able to method was native. We do not currently provide the parameters. The listener replace classes in the final jar. can however specify the return value of the overridden method. Example: android.graphics.Paint would get renamed to OriginalPaint An extension being worked on is to actually replace these listeners by in the generated jar. Then in the bridge we'll introduce a replacement direct calls to a delegate class, complete with parameters. Paint class that derives from OriginalPaint. - Strategies ------------ We currently have 4 strategies to deal with overriding the rendering code and make it run in Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) and the generator. 1- Class Injection This is the easiest: we currently inject 4 classes, namely: - OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to some specific methods that are stubbed out and change their return value. - CreateInfo class, which configured the generator. Not used yet, but could in theory help us track what the generator changed. 2- Overriding methods As explained earlier, the creator doesn't have any replacement code for methods to override. Instead it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The bridge then registers a listener on the method signature and can provide an implementation. 3- Renaming classes This simply changes the name of a class in its definition, as well as all its references in internal inner classes and methods. Calls from other classes are not modified -- they keep referencing the original class name. This allows the bridge to literally replace an implementation. An example will make this easier: android.graphics.Paint is the main drawing class that we need to replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own replacement version of Paint which will be used by the rest of the Android stack. The replacement version of Paint can still use (either by inheritance or delegation) all the original non-native code of _original_Paint if it so desires. Some of the Android classes are basically wrappers over native objects and since we don't have the native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't work as some native methods are static and 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- Method erasure based on return type This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and all the original implementation is native. In this case we have a strategy that tells the generator that anything returning, for example, the inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own implementation. -- end Loading
tools/layoutlib/create/README.txt +164 −36 Original line number Original line Diff line number Diff line Loading @@ -4,68 +4,196 @@ - Description - - Description - --------------- --------------- makeLayoutLib generates a library used by the Eclipse graphical layout editor Layoutlib_create generates a JAR library used by the Eclipse graphical layout editor to perform layout. to perform layout. - Usage - - Usage - --------- --------- ./makeLayoutLib path/to/android.jar destination.jar ./layoutlib_create path/to/android.jar destination.jar - Design Overview - ------------------- Layoutlib_create uses the "android.jar" containing all the Java code used by Android as 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: - it 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 replaced by Java 2D calls in Eclipse. - some of the classes that need to be changed are final and/or we need access to their private internal state. Consequently this tool: - parses the input JAR, - modifies some of the classes directly using some bytecode manipulation, - filters some packages and removes some that we don't want to end in the output JAR, - injects some new classes, - and generates a modified JAR file that is suitable for the Android plugin for Eclipse to perform rendering. 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 configuration is done in the main() method and the CreateInfo structure is expected to change with the Android 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 platform, that provides all the necessary missing implementation for rendering graphics in Eclipse. - Implementation Notes - - Implementation Notes - ------------------------ ------------------------ The goal of makeLayoutLib is to list all the classes from the input jar and create a new The tool works in two phases: jar that only keeps certain classes and create stubs for all their dependencies. - first analyze the input jar (AsmAnalyzer class) - then generate the output jar (AsmGenerator class), - Analyzer ---------- The goal of the analyzer is to create a graph of all the classes from the input JAR with their 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 that derives from these is kept. Currently the one such class is android.view.View: since we want to render layouts, anything that is sort of the view needs to be kept. The analyzer is also given a list of class names to keep in the output. This is done using shell-like glob patterns that filter on the fully-qualified class names, for example "android.*.R**" ("*" does not matches dots whilst "**" does, and "." and "$" are interpreted as-is). In practice we almost but not quite request the inclusion of full packages. With this information, the analyzer parses the input zip to find all the classes. All classes deriving from the requested bases classes are kept. All classes which name matched the glob pattern are kept. The analysis then finds all the dependencies of the classes that are to be kept using an ASM visitor on the class, the field types, the method types and annotations types. Classes that belong to the current JRE are excluded. The output of the analyzer is a set of ASM ClassReader instances which are then fed to the generator. - Generator ----------- First the input jar is parsed to find all the classes defined. The generator is constructed from a CreateInfo struct that acts as a config file and lists: - the classes to inject in the output JAR -- these classes are directly implemented 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 remove based on their return type. - specific classes to rename. In the Main(), the following list of classes are hardcoded (TODO config file later): Each of these are specific strategies we use to be able to modify the Android code - keep all classes that derive from android.view.View. to fit within the Eclipse renderer. These strategies are explained beow. - keep all classes in the android.view and android.widget packages (sub-packages excluded). - keep specific classes such as android.policy.PhoneLayoutInflater. For each class to keep, their dependencies are examined using BCEL. The core method of the generator is transform(): it takes an input ASM ClassReader A dependency is defined as a class needed to instantiate the given class that should be kept, and modifies it to produce a byte array suitable for the final JAR file. directly or indirectly. So a dependency is a class that is used by the input class, that is defined in the input jar and that is not part of the current JRE. Dependencies are computed recursively. The first step of the transformation is changing the name of the class in case we requested the class to be renamed. This uses the RenameClassAdapter to also rename all inner classes and references in methods and types. Note that other classes are not transformed and keep referencing the original name. Once all dependencies are found, the final jar can be created. The TransformClassAdapter is then used to process the potentially renamed class. There are three kind of classes to write: All protected or private classes are market as public. - classes that are to be kept as-is. They are just dumped in the new jar unchanged. All classes are made non-final. - classes that are to be kept yet contain native methods or fields. Interfaces are left as-is. - classes that are just dependencies. We don't want to expose their implementation in the final jar. The implementation of native methods and all methods of mock classes is replaced by a stub If a method has a return type that must be erased, the whole method is skipped. that throws UnsupportedOperationException. Methods are also changed from protected/private to public. The code of the methods is then kept as-is, except for native methods which are replaced by a stub. Methods that are to be overridden are also replaced by a stub. Incidentally, the access level of native and mock classes needs to be changed in order for Finally fields are also visited and changed from protected/private to public. native methods to be later overridden. Methods that are "final private native" must become non-final, non-native and at most protected. Package-default access is changed to public. Classes that are final are made non-final. Abstract methods are left untouched. - Method stubs -------------- ---- As indicated above, all native and overridden methods are replaced by a stub. 20080617 Replace Class We don't have the code to replace with in layoutlib_create. Instead the StubMethodAdapter replaces the code of the method by a call to OverrideMethod.invokeX(). When using the final JAR, the bridge can register listeners from these overridden method calls based on the method signatures. Some classes are basically wrappers over native objects. The listeners are currently pretty basic: we only pass the signature of the Subclassing doesn't work as most methods are either static or we don't method being called, its caller object and a flag indicating whether the control object creation. In this scenario the idea is to be able to method was native. We do not currently provide the parameters. The listener replace classes in the final jar. can however specify the return value of the overridden method. Example: android.graphics.Paint would get renamed to OriginalPaint An extension being worked on is to actually replace these listeners by in the generated jar. Then in the bridge we'll introduce a replacement direct calls to a delegate class, complete with parameters. Paint class that derives from OriginalPaint. - Strategies ------------ We currently have 4 strategies to deal with overriding the rendering code and make it run in Eclipse. Most of these strategies are implemented hand-in-hand by the bridge (which runs in Eclipse) and the generator. 1- Class Injection This is the easiest: we currently inject 4 classes, namely: - OverrideMethod and its associated MethodListener and MethodAdapter are used to intercept calls to some specific methods that are stubbed out and change their return value. - CreateInfo class, which configured the generator. Not used yet, but could in theory help us track what the generator changed. 2- Overriding methods As explained earlier, the creator doesn't have any replacement code for methods to override. Instead it removes the original code and replaces it by a call to a specific OveriddeMethod.invokeX(). The bridge then registers a listener on the method signature and can provide an implementation. 3- Renaming classes This simply changes the name of a class in its definition, as well as all its references in internal inner classes and methods. Calls from other classes are not modified -- they keep referencing the original class name. This allows the bridge to literally replace an implementation. An example will make this easier: android.graphics.Paint is the main drawing class that we need to replace. To do so, the generator renames Paint to _original_Paint. Later the bridge provides its own replacement version of Paint which will be used by the rest of the Android stack. The replacement version of Paint can still use (either by inheritance or delegation) all the original non-native code of _original_Paint if it so desires. Some of the Android classes are basically wrappers over native objects and since we don't have the native code in Eclipse, we need to provide a full alternate implementation. Sub-classing doesn't work as some native methods are static and 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- Method erasure based on return type This is mostly an implementation detail of the bridge: in the Paint class mentioned above, some inner static classes are used to pass around attributes (e.g. FontMetrics, or the Style enum) and all the original implementation is native. In this case we have a strategy that tells the generator that anything returning, for example, the inner class Paint$Style in the Paint class should be discarded and the bridge will provide its own implementation. -- end