J’ai eu la chance d’assister à la présentation sur la manipulation du byte code sous Android à la Droidcon 2014.
Deux librairies ont été présentées:
Afterburner
Une librairie qui permet d’éviter le code redondant lors de l’écriture de librairies. On peut ainsi injecter un morceau de code à des endroits bien précis, aussi précisément que « dans telle méthode de telle classe et après l’appel de telle méthode ». Par exemple, avec une classe qui ressemble à ça:
public class A {
private int foo;
public void bar() {}
public void foo() {
bar();
}
}
On peut donner une valeur à la variable « int foo », dans la méthode « foo() », après l’appel de la méthode « bar() »:
InsertableMethod.Builder builder = new InsertableMethod.Builder( new AfterBurner() );
CtClass classToInsertInto = ClassPool.getDefaultPool().get(A.class.getName());
builder
.insertIntoClass(A.class.getName())
.inMethodIfExists("foo")
.afterACallTo("bar")
.withBody("this.foo = 2;")
.elseCreateMethodIfNotExists("public void foo() { this.foo = 2; }")
.doIt();
Cela reviendrait à écrire:
public class A {
private int foo;
public void bar() {}
public void foo() {
bar();
//ADDED by AfterBurner
this.foo = 2;
}
}
Alors bien sûr, à priori, on ne voit pas forcément l’intérêt de la chose. Pourquoi s’embêter à injecter quand on peut écrire soi même « this.foo = 2; » dans le corps de sa méthode? Il s’avère que c’est très pratique!
Un petit projet d’implémentation a été présenté sous Android, il permet d’afficher dans les logs tous les appels aux méthode du cycle de vie des activités. Il s’agit de Loglifecycle. Avec une seule annotation sur les activités que l’on veut observer, on obtient dans les logs tous les appels aux méthodes onCreate, onContentChanged, onResume etc.
On peut imaginer tout un tas d’autres cas d’applications pour cette librairie, peut-être même la modification d’un code dont on n’a pas les sources (mais dont on connaît les classes/méthodes)!
Mimic
Mimic est une librairie qui permet, en quelque sorte, d’accéder à l’héritage multiple sous Java/Android. Le principe est de copier coller tous les attributs, constructeurs et méthodes d’une classe A dans une classe B.
Par exemple,
public class A{
public void foo() {
}
}
@Mimic(sourceClass = B.class,
mimicMethods = {@MimicMethod(methodName="foo",mode=MimicMode.AT_BEGINNING)}
)
public class Aprime extends A{
@Override
public void foo() {
super.foo();
}
}
public class B{
public void foo() {
System.out.println("Hello B !");
}
}
La méthode foo() de Aprime ne fait qu’appeler la méthode foo() de A. Or, comme on a appelé Mimic sur A et qu’on lui a demandé de remplacer « foo », c’est bien « Hello B! » qui sera appelé!