様々なリソースをフィールドに注入する

Androidプラットホームであっても特定の分野のアプリケーション以外であればリフレクションは問題にならないだろうという観測を得たので、リフレクションを活用する機能を用意することにした。

Javaフレームワークでは依存性の解決に"DI:Dependency Injection"機能を持つ物が多いが、それらのフレームワークではオブジェクト同士の依存性を解決するために注入:インジェクションという処理をコンテナで行うことが可能だ。

例えばGuiceでは以下のようにPOJOのフィールドにアノテーションを記述することで、特定の値を注入することができる。

class Person {
    @Inject
    private String name;
    @Inject
    private Integer age;
}
Injector injector = Guice.createInjector(
    new AbstractModule() {
        @Override
        protected void configure() {
            bind(String.class).toInstance("Kazzz");
            bind(Integer.class).toInstance(0);
        }
    });
Person p = injector.getInstance(Person.class);

フルスタックのフレームワークと同等に扱うことはできないが、一般的によく書かれる処理を「注入」によって自動化し省力化を推し進めることはAndroidでも必要だろうということで、拙作のフレームワークでは一部の注入を実装することにした。

まず考えられるのは以前にも書いたことがあるが、外部XMLで定義されたViewをリソースIDを頼りにフィールドに注入するケース。

(以下、例外等細かいことを端折っていることをお断りしておく)

public class TimeActivity extends Activity {
    @Inject(R.id.txtEmployerCode)
    protected TextView txtEmployerCode;
    
    @Inject(R.id.txtEmployearName)
    protected TextView txtEmployearName;

    @Inject(R.id.txtdefaultWorkingTime)
    protected TextView txtdefaultWorkingTime;

    @Inject(R.id.txtdefaultOverTime)
    protected TextView txtdefaultOverTime;

このようにアノテーションを記述しておくことにより、内部的にフィールドをトラバースしながら

Field fields = FinderUtil.findAll(activity.getClass().getDeclaredFields()
  , new IPredicate(){
    @Override
    public boolean evaluate(Field input) {
        return (input.isAnnotationPresent(Inject.class)); 
    }});※
for ( Field field : fields ) {
    Annotation array = field.getAnnotations();
    for ( Annotation a : array ) {
        if ( a instanceof Inject ) {
            final int id = inj.value();
            final View view = activity.findViewById(id);
            if ( view != null 
                && field.getType().isAssignableFrom(view.getClass())) {
                field.setValue(action, view);
            }
        }
    }
}

このように対象のViewをリフレクションを介してactivityのフィールドに注入することができる。また、Androidは外部XMLで文字列や配列、画像のリソースを定義することもできるため、同様に

public class TimeActivity extends Activity {
    @Inject(R.string.guidance_exchange)
    protected String guidance_exchange;

    @Inject(R.array.atwork_classification_item_holiday)
    protected String[] classification_items;

    @Inject(R.drawable.btn_check_on)
    protected Drawable button_image;

等と記述し、内部では同様にフィールドをトラバースしつつ

Field fields = FinderUtil.findAll(activity.getClass().getDeclaredFields()
  , new IPredicate(){
    @Override
    public boolean evaluate(Field input) {
        return (input.isAnnotationPresent(Inject.class)); 
    }});

for ( Field field : fields ) {
    Annotation array = field.getAnnotations();
    for ( Annotation a : array ) {
        if ( a instanceof Inject ) {
            // @Injectを処理
            Inject inj = (Inject)a;
            final int id = inj.value();
            final Class t = field.getType();
            final Resources resources = 
                activity.getApplication().getResources();

            Object value = null;
            if (String.class.isAssignableFrom(t)) {
                value = resources.getString(id);
            } else if (Drawable.class.isAssignableFrom(t)) {
                value = resources.getDrawable(id);
            } else if (String.class.isAssignableFrom(t)) {
                value = resources.getStringArray(id);
            } else if (int.class.isAssignableFrom(t) 
                    || Integer[].class.isAssignableFrom(t)) {
                value = resources.getIntArray(id);
            }
            field.setValue(activity, value);
        }
   }
}

と、リソースの種別とフィールドの型を判定して同様にフィールドをリフレクションで設定する。

既にこれらの機能を組み込んで使っているが、某フレームワークのようにオートワイアリングを施したり、生成するインスタンスコンストラクタバリエーションまで考慮したり等、重くなりがちな処理を避ければ十分に実用になる手応えを感じている。

また以前に行ったリフレクションの性能検証で判明しているように、Androidのリフレクションではメソッドへのアクセスに比べてフィールド(アノテーションも同様だった)へのアクセスが格段に速いので、この辺は割り切ってメソッドレベルでのインジェクションは省略することにした。(JavaBeasnとしてのプロパティ、つまりgetter/setterのサポートは止めたということだ)

今後はこれらの機能に加えて、他のオブジェクトとのバインド機能(モデル<->アクティビティ)を追加していく予定だ。

※FinderUtilは以前に何度か紹介した述語論理を使ったファインダメソッド -> アノテーションを使って、GUIコンポーネントのTabOrderを設定する