Slide 1

Slide 1 text

@auditty +AudreyTroutt Android Metaprogramming Audrey Troutt

Slide 2

Slide 2 text

@auditty +AudreyTroutt What is metaprogramming? Writing code that reads, generates, analyzes, or transforms other code.

Slide 3

Slide 3 text

@auditty +AudreyTroutt Why use metaprogramming? // BEFORE class AndroidWay extends Activity { TextView name; ImageView thumbnail; LocationManager loc; Drawable icon; String myName; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (TextView) findViewById(R.id.name); thumbnail = (ImageView) findViewById(R.id.thumbnail); loc = (LocationManager) getSystemService(Activity. LOCATION_SERVICE); icon = getResources().getDrawable(R.drawable.icon); myName = getString(R.string.app_name); name.setText( "Hello, " + myName ); } } // AFTER @ContentView(R.layout.main) class RoboWay extends RoboActivity { @InjectView(R.id.name) TextView name; @InjectView(R.id.thumbnail) ImageView thumbnail; @InjectResource(R.drawable.icon) Drawable icon; @InjectResource(R.string.app_name) String myName; @Inject LocationManager loc; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); name.setText( "Hello, " + myName ); } } // example borrowed from RoboGuice :) https://github. com/roboguice/roboguice/wiki

Slide 4

Slide 4 text

@auditty +AudreyTroutt Why use metaprogramming? // BEFORE class AndroidWay extends Activity { TextView name; ImageView thumbnail; LocationManager loc; Drawable icon; String myName; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.main); name = (TextView) findViewById(R.id.name); thumbnail = (ImageView) findViewById(R.id.thumbnail); loc = (LocationManager) getSystemService(Activity. LOCATION_SERVICE); icon = getResources().getDrawable(R.drawable.icon); myName = getString(R.string.app_name); name.setText( "Hello, " + myName ); } } // AFTER @ContentView(R.layout.main) class RoboWay extends RoboActivity { @InjectView(R.id.name) TextView name; @InjectView(R.id.thumbnail) ImageView thumbnail; @InjectResource(R.drawable.icon) Drawable icon; @InjectResource(R.string.app_name) String myName; @Inject LocationManager loc; public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); name.setText( "Hello, " + myName ); } } // example borrowed from RoboGuice :) https://github. com/roboguice/roboguice/wiki

Slide 5

Slide 5 text

@auditty +AudreyTroutt Why use metaprogramming? ● You are a library developer, probably

Slide 6

Slide 6 text

@auditty +AudreyTroutt #import "UILabel+TextMagic.h" // This is a category @implementation UILabel (TextMagic) - (CGFloat)bottomYCoordinate { return self.frame.origin.y + self.frame.size.height; } - (CGFloat)rightXCoordinate { return self.frame.origin.x + self.frame.size.width; } @end // from https://github.com/ArtisanMobile/ArtisanCategoryMagic Metaprogramming is easy* on iOS

Slide 7

Slide 7 text

@auditty +AudreyTroutt #import // This is method swizzling + (void)replaceOriginalInstanceMethod:(SEL)originalMethod from:(id)original withInstanceMethod:(SEL)replacementMethod from:(id)replacementObject { Method originalMethod = class_getInstanceMethod([original class], originalMethod); Method mockMethod = class_getInstanceMethod([replacementObject class], replacementMethod); method_exchangeImplementations(originalMethod, mockMethod); } // Don’t do this in production code! It’s more polite to always call the original code. Metaprogramming is easy* on iOS

Slide 8

Slide 8 text

@auditty +AudreyTroutt // You can perform any selector, even hidden ones. id myClone = [anObject performSelector:@selector(copy)]; Metaprogramming is easy* on iOS

Slide 9

Slide 9 text

@auditty +AudreyTroutt Reflection Useful parts: ● Runtime tool ● Inspect classes, their annotations, fields and methods ● Invoke methods ● Change the values of fields

Slide 10

Slide 10 text

@auditty +AudreyTroutt import java.lang.reflect.Method; private void logPropertyValueForObject(Object t, String accessorMethodName) { try { Method m = t.getClass().getDeclaredMethod(accessorMethodName); // (1) m.setAccessible(true); // (2) just in case this is private/not accessible Object o = m.invoke(t); // (3) using reflection to invoke an arbitrary method Log.d("META", accessorMethodName + " value is " + o); } catch (Exception e) { Log.e("META", "uh oh! Something went wrong!", e); } } protected void onStop() { super.onStop(); logPropertyValueForObject(this, "timeOnScreen"); // (4) } private long timeOnScreen() { return new Date().getTime() - activityStartTime; } Reflection

Slide 11

Slide 11 text

@auditty +AudreyTroutt import java.lang.reflect.Method; private void logPropertyValueForObject(Object t, String accessorMethodName) { try { Method m = t.getClass().getDeclaredMethod(accessorMethodName); // (1) m.setAccessible(true); // (2) just in case this is private/not accessible Object o = m.invoke(t); // (3) using reflection to invoke an arbitrary method Log.d("META", accessorMethodName + " value is " + o); } catch (Exception e) { Log.e("META", "uh oh! Something went wrong!", e); } } protected void onStop() { super.onStop(); logPropertyValueForObject(this, "timeOnScreen"); // (4) } private long timeOnScreen() { return new Date().getTime() - activityStartTime; } Reflection

Slide 12

Slide 12 text

@auditty +AudreyTroutt import java.lang.reflect.Method; private void logPropertyValueForObject(Object t, String accessorMethodName) { try { Method m = t.getClass().getDeclaredMethod(accessorMethodName); // (1) m.setAccessible(true); // (2) just in case this is private/not accessible Object o = m.invoke(t); // (3) using reflection to invoke an arbitrary method Log.d("META", accessorMethodName + " value is " + o); } catch (Exception e) { Log.e("META", "uh oh! Something went wrong!", e); } } protected void onStop() { super.onStop(); logPropertyValueForObject(this, "timeOnScreen"); // (4) } private long timeOnScreen() { return new Date().getTime() - activityStartTime; } Reflection

Slide 13

Slide 13 text

@auditty +AudreyTroutt import java.lang.reflect.Method; private void logPropertyValueForObject(Object t, String accessorMethodName) { try { Method m = t.getClass().getDeclaredMethod(accessorMethodName); // (1) m.setAccessible(true); // (2) just in case this is private/not accessible Object o = m.invoke(t); // (3) using reflection to invoke an arbitrary method Log.d("META", accessorMethodName + " value is " + o); } catch (Exception e) { Log.e("META", "uh oh! Something went wrong!", e); } } protected void onStop() { super.onStop(); logPropertyValueForObject(this, "timeOnScreen"); // (4) } private long timeOnScreen() { return new Date().getTime() - activityStartTime; } Reflection

Slide 14

Slide 14 text

@auditty +AudreyTroutt import java.lang.reflect.Method; private void logPropertyValueForObject(Object t, String accessorMethodName) { try { Method m = t.getClass().getDeclaredMethod(accessorMethodName); // (1) m.setAccessible(true); // (2) just in case this is private/not accessible Object o = m.invoke(t); // (3) using reflection to invoke an arbitrary method Log.d("META", accessorMethodName + " value is " + o); } catch (Exception e) { Log.e("META", "uh oh! Something went wrong!", e); } } protected void onStop() { super.onStop(); logPropertyValueForObject(this, "timeOnScreen"); // (4) } private long timeOnScreen() { return new Date().getTime() - activityStartTime; } Reflection

Slide 15

Slide 15 text

@auditty +AudreyTroutt Reflection Limitations: ● is slow ● does not change code behavior

Slide 16

Slide 16 text

@auditty +AudreyTroutt Annotations Useful parts: ● Define metadata about code

Slide 17

Slide 17 text

@auditty +AudreyTroutt @Background // An annotation! void translateInBackground(String textToTranslate) { String translatedText = callGoogleTranslate(textToTranslate); showResult(translatedText); } // example from AndroidAnnotations https://github.com/excilys/androidannotations Annotations

Slide 18

Slide 18 text

@auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod { String eventName(); String logProperty() default ""; } // SAMPLE USAGE IN MY ACTIVITY @Override @WatchedMethod(eventName = "disappear", logProperty = "timeOnScreen") protected void onStop() { super.onStop(); } Annotations

Slide 19

Slide 19 text

@auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod { String eventName(); String logProperty() default ""; } // SAMPLE USAGE IN MY ACTIVITY @Override @WatchedMethod(eventName = "disappear", logProperty = "timeOnScreen") protected void onStop() { super.onStop(); } Annotations

Slide 20

Slide 20 text

@auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod { String eventName(); String logProperty() default ""; } // SAMPLE USAGE IN MY ACTIVITY @Override @WatchedMethod(eventName = "disappear", logProperty = "timeOnScreen") protected void onStop() { super.onStop(); } Annotations

Slide 21

Slide 21 text

@auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod { String eventName(); String logProperty() default ""; } // SAMPLE USAGE IN MY ACTIVITY @Override @WatchedMethod(eventName = "disappear", logProperty = "timeOnScreen") protected void onStop() { super.onStop(); } Annotations

Slide 22

Slide 22 text

@auditty +AudreyTroutt import java.lang.annotation.*; @Target(ElementType.METHOD) @Retention(RetentionPolicy.RUNTIME) public @interface WatchedMethod { String eventName(); String logProperty() default ""; } // SAMPLE USAGE IN MY ACTIVITY @Override @WatchedMethod(eventName = "disappear", logProperty = "timeOnScreen") protected void onStop() { super.onStop(); } Annotations

Slide 23

Slide 23 text

@auditty +AudreyTroutt ● Before compilation ● Inspect annotations, generate source Annotation Processors

Slide 24

Slide 24 text

@auditty +AudreyTroutt Limitations: ● Can’t modify existing implementations ● Requires developer binding ● Method counts Annotation Processors

Slide 25

Slide 25 text

@auditty +AudreyTroutt Bytecode Manipulation Useful parts: ● Can both modify and add methods and classes

Slide 26

Slide 26 text

@auditty +AudreyTroutt Android build process

Slide 27

Slide 27 text

@auditty +AudreyTroutt Libraries: ● ASM ○ Working directly with bytecode ○ http://asm.ow2.org/ ● DexMaker ○ Enables runtime manipulation on android ○ https://github.com/crittercism/dexmaker ● Javaassist ○ higher-level coding ○ http://jboss-javassist.github.io/javassist ● AspectJ ○ the easiest to use solution* Bytecode Manipulation

Slide 28

Slide 28 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 29

Slide 29 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 30

Slide 30 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 31

Slide 31 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 32

Slide 32 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 33

Slide 33 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 34

Slide 34 text

@auditty +AudreyTroutt // Examples of using javaassist // (1) add superclass ClassPool pool = ClassPool.getDefault(); CtClass cc = pool.get("test.Child"); cc.setSuperclass(pool.get("test.Parent")); // (2) insert code into a method body before existing code CtMethod m = cc.getDeclaredMethod("say"); m.insertBefore("{ Log.d(\"META\",\"So META!\"); }"); // (3) wrap the existing method body with a try/catch CtClass etype = ClassPool.getDefault().get("java.io.IOException"); m.addCatch("{ Log.e(\"META\",\"There was an error\", $e); }", etype); // (4) write the modified class back out cc.writeFile(); Bytecode Manipulation

Slide 35

Slide 35 text

@auditty +AudreyTroutt Limitations: ● Danger! ● Jack & Jill? ● Long developer ramp-up Bytecode Manipulation

Slide 36

Slide 36 text

@auditty +AudreyTroutt AspectJ Useful parts: ● Can both modify and add methods and classes ● Easy to use*

Slide 37

Slide 37 text

@auditty +AudreyTroutt AspectJ ● aspect ● pointcut ● join point ● advice

Slide 38

Slide 38 text

@auditty +AudreyTroutt // (1) define pointcut pointcut onViewClicked(View view) : execution(* *.*(View)) && args(view) && (within(Activity+ || View.OnClickListener+)); // (2) Inject code before/after/around the existing method(s) before(View view) : onViewClicked(view) { TrackEvent.onViewClicked(view); } // inspired by Artisan's TrackEvent.aj AspectJ

Slide 39

Slide 39 text

@auditty +AudreyTroutt // (1) define pointcut pointcut onViewClicked(View view) : execution(* *.*(View)) && args(view) && (within(Activity+ || View.OnClickListener+)); // (2) Inject code before/after/around the existing method(s) before(View view) : onViewClicked(view) { TrackEvent.onViewClicked(view); } // inspired by Artisan's TrackEvent.aj AspectJ

Slide 40

Slide 40 text

@auditty +AudreyTroutt // (1) define pointcut pointcut onViewClicked(View view) : execution(* *.*(View)) && args(view) && (within(Activity+ || View.OnClickListener+)); // (2) Inject code before/after/around the existing method(s) before(View view) : onViewClicked(view) { TrackEvent.onViewClicked(view); } // inspired by Artisan's TrackEvent.aj AspectJ

Slide 41

Slide 41 text

@auditty +AudreyTroutt // (1) define pointcut pointcut onViewClicked(View view) : execution(* *.*(View)) && args(view) && (within(Activity+ || View.OnClickListener+)); // (2) Inject code before/after/around the existing method(s) before(View view) : onViewClicked(view) { TrackEvent.onViewClicked(view); } // inspired by Artisan's TrackEvent.aj AspectJ

Slide 42

Slide 42 text

@auditty +AudreyTroutt // (1) define pointcut pointcut onViewClicked(View view) : execution(* *.*(View)) && args(view) && (within(Activity+ || View.OnClickListener+)); // (2) Inject code before/after/around the existing method(s) before(View view) : onViewClicked(view) { TrackEvent.onViewClicked(view); } // inspired by Artisan's TrackEvent.aj AspectJ

Slide 43

Slide 43 text

@auditty +AudreyTroutt // (3) set superclass with inter-type declarations declare parents: com.audreytroutt.demo.activity.MainActivity extends AudreyInjectedActivity; // (4) add a method public void com.audreytroutt.demo.activity.MainActivity. injectedMethodInYourActivity() { Log.d("META", "I'm in your code, changing your bytes!"); } AspectJ

Slide 44

Slide 44 text

@auditty +AudreyTroutt // (3) set superclass with inter-type declarations declare parents: com.audreytroutt.demo.activity.MainActivity extends AudreyInjectedActivity; // (4) add a method public void com.audreytroutt.demo.activity.MainActivity. injectedMethodInYourActivity() { Log.d("META", "I'm in your code, changing your bytes!"); } AspectJ

Slide 45

Slide 45 text

@auditty +AudreyTroutt // (3) set superclass with inter-type declarations declare parents: com.audreytroutt.demo.activity.MainActivity extends AudreyInjectedActivity; // (4) add a method public void com.audreytroutt.demo.activity.MainActivity. injectedMethodInYourActivity() { Log.d("META", "I'm in your code, changing your bytes!"); } AspectJ

Slide 46

Slide 46 text

@auditty +AudreyTroutt AspectJ Limitations: ● Can’t modify Android base classes ● Android developers hated it!

Slide 47

Slide 47 text

@auditty +AudreyTroutt First Metaprogramming Steps ● Leverage existing libraries ● Peek inside and see how they work ● Thank a library developer

Slide 48

Slide 48 text

@auditty +AudreyTroutt Android Metaprogramming Audrey Troutt