ViewからFieldへの代入をアノテーションで自動化する
AndroidのGUIはXMLを使用した宣言的な構成を可能としており、そのためのコードが不要なのは素晴らしいが、それでもアプリケーション内でGUIを参照しようとすると、以下のようなコードを書く必要がある。
public class FooActivity extends Activity { protected TextView a; protected TextView b; protected TextView c; protected TextView d; protected TextView e; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.main); : this.a = ((TextView)findViewById(R.id.a)); this.b = ((TextView)findViewById(R.id.b)); this.c = ((TextView)findViewById(R.id.c)); this.d = ((TextView)findViewById(R.id.d)); this.e = ((TextView)findViewById(R.id.e)); : : }
ならばと対象になっているActivityのフィールドに対して、宣言的にコンテキストやリソースからオブジェクトを注入できると、後々便利だろうと表題の件を考えてみた。
対象はAndroidで使用可能な全てのリソース(Androidリソースはハンドルでアクセスする一種のリポジトリともいえる)と言いたい所だが、最初からあまり大げさなものを書くのも嫌なので、まずはViewだけを対象にする。
Viewフィールドに対して、Rクラスのidを直接指定するためだけのアノテーションを用意する
- Inject.java
@Documented @Inherited @Target({ElementType.FIELD}) @Retention(RetentionPolicy.RUNTIME) public @interface Inject { int id() default 0; }
今回は必要最低限の属性であるidだけに留めたが、一般的なプロパティ(テキスト内容、桁数、文字色等)を設定できるように、属性に追加するのも良いだろう。
以下、実際にアノテーションをパースしてリソースからビュー・フィールドへ参照を注入する"ViewInjector"クラスの実装の叩き台だ。
- ViewFieldInjector.java
public final class ViewFieldInjector implements IInjectable { protected final Activity activity; public ViewFieldInjector(Activity activity) { this.activity = activity; } public void inject() { this.collectAnnotations(this.activity); } private void collectAnnotations() { Field[] fields = this.activity.getClass().getFields(); for ( Field field : fields ) { field.setAccessible(true); try { Inject inj = field.getAnnotation(Inject.class); if ( inj != null ) { if ( inj.id() != 0 ) { //idあり this.injectViewToTarget(field, inj.id()); } } } catch (Exception e) { Log.e(this.getClass().getName(), e.toString()); } } } private void injectViewToTarget( Field field, int handle) throws IllegalArgumentException, IllegalAccessException { View view = this.activity.findViewById(handle); if ( view != null ) { field.set(activity, view); } } }
処理的には簡単で、Activityの型情報を利用してフィールドに記述されているアノテーションを処理しているだけ。リフレクションを使うため、性能では不利になる。
最終的には、Activityクラスのフィールドを以下のように記述することを想定している。
public class FooActivity extends Activity { @Inject(id=R.id.a) protected TextView a; @Inject(id=R.id.b) protected TextView b; @Inject(id=R.id.c) protected TextView c; @Inject(id=R.id.d) protected TextView d; @Inject(id=R.id.e) protected TextView e; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.main); }
コードは上記のようにかなりすっきりする。
問題はこのViewFieldInjectorをどの時点で動作させるかだ。
ActivityのonCreateハンドラが一番無難なのだろうが、このような陰の仕組みにはアプリケーションコードを依存させたくない。
- これはできれば避けたい
public class FooActivity extends Activity { : @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); this.setContentView(R.layout.main); //インジェクタを生成 ViewFieldInjector injector = ViewFieldInjector(this); injector.inject; }
可能であればACTION_BOOT_COMPLETEDに反応するインテントのように、アクティビティの起動時に割り込む処理を書きたいんだけどな。