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に反応するインテントのように、アクティビティの起動時に割り込む処理を書きたいんだけどな。