2012年8月14日火曜日

AndroidAnnotations -Resources-

今回はリソースの扱い方について。
androidではstring.xml等の設定ファイルに定数を定義して
それを各モジュールで使用するのんだけど、それの取得方法が意外と面倒くさい。
通常の取得方法はこんな感じになります。
public class SampleActivity extends Activity {

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        // string.xmlから"hoge"で定義されている文字列を取得する
        final String hoge = getString(R.string.hoge);
    }
}
これを、AndroidAnnotationsで書くと
@EActivity
public class SampleActivity extends Activity {

    // 引数を指定しない場合は、フィールド名から取得する
    @StringRes
    protected String hoge;

    // 引数を指定した場合は指定したものを取得する
    @StringRes(R.string.hello)
    protected String fuga;

}
上記の他にも、@ColorResアノテーションでcolorのint値だったりが取得できます。
設定ファイルで定義しておけるものはほとんど定義できますので詳しくは、 https://github.com/excilys/androidannotations/wiki/Resourcesを参照してください。

2012年8月13日月曜日

AndroidAnnotations -開始-

今日からはAndroidAnnotationsの使い方を書いていこうと思います。
まず、AndroidAnnotationsって何かというと、
Androidのコーディングをしていると誰でも思うであろう、
「またこのコード書くのか・・・。」とか「内部クラスうぜぇ」とか
「書くこと多いな」とかをアノテーションで解決していこうっていうライブラリです。
AndroiAnnotations

まずはここからzipをDL!
https://github.com/excilys/androidannotations/wiki/Download
解凍すると、以下のファイルがあると思います。

  • androidannotations-xxx-api.jar
  • androidannotations-xxx.jar
    • 「xxx」はバージョン番号です
次からの操作はeclipseで行う想定で。
  1. androidannotations-xxx-api.jarをAndroidProjectのlibsディレクトリに配置。(libsディレクトリがない場合は自分で作成)
  2. 1で追加したandroidannotations-xxx-api.jarをビルドパスに追加。
  3. androidannotations-xxx.jarをAndroidProjectのext-libsディレクトリに配置。(libsディレクトリで無ければ別名でもOK)
  4. AndroidProjectを右クリックして「Properties」→「Java Compiler」→「Annotation Processing」と選択して、「Enable project specific settings」にチェックを入れる。また、「Enable annotation processing」にもチェックを付ける。
  5. AndroidProjectを右クリックして「Properties」→「Java Compiler」→「Annotation Processing」→「Factory Path」と移動して、3で追加したandroidannotations-xxx.jarを追加する。
以上で基本準備は完了。後は普通にコーディングすればOKです。

せっかくなのでサンプルを一つ。
まずは、AndroidAnnotationsを使用しないパターン。
使用するレイアウトは以下の「my_activity.xml」。
&lt?xml version="1.0" encoding="utf-8"?&gt
&ltLinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
 &gt
    &ltEditText
     android:id="@+id/myInput"
     android:layout_width="fill_parent"
     android:layout_height="wrap_content"
    /gt
    &ltButton
    android:id="@+id/myButton"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="Click me!"
    /&gt
    &ltTextView
    android:id="@+id/myTextView"
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    /&gt
&lt/LinearLayout&gt
で、Activity。
public class MyActivity extends Activity {

    protected EditText myInput;

    protected TextView myTextView;

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.my_activity);

        myInput = (EditText)findViewById(R.id.myInput);
        myTextView = (TextView)findViewById(R.id.myTextView);

        final Button myButton = (Button)findViewById(R.id.myButton);
        myButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                final String name = myInput.getText().toString();
                myTextView.setText("Hello " + name);
            }
        );
    }
}
画面には入力欄とボタンが1つずつあって、ボタンをタップすると入力欄に入力された文字列を
加工して別のエリアに表示するだけのプログラムです。
これを、AndroidAnnotationsを使用して書くと、以下のようになります。
使用するレイアウトを同じものでOKです。
@EActivity(R.layout.my_activity)
public class MyActivity extends Activity {

    @ViewById(R.id.myInput)
    protected EditText myInput;

    @ViewById(R.id.myTextView)
    protected TextView myTextView;

    @Click
    protected void myButton() {
        final String name = myInput.getText().toString();
        myTextView.setText("Hello " + name);
    }
}

これだけでOKです。
各アノテーションの説明を。

  • @EActivity
    • Activityクラスの付けるアノテーションです。Activityで使用するレイアウトxmlを指定します。
  • @ViewById
    • フィールドに付けるアノテーションです。指定したidのviewの参照をフィールド変数にセットします。
  • @Click
    • メソッドに付けるアノテーションです。メソッド名とレイアウト内のidを同一にする必要があります。このアノテーションをつけたメソッドは対象のidのviewがクリックされた時の動作になります。
書き忘れていましたが、AndroidAnnotationsを使用するActivityを、
AndroidManifest.xmlに記述する場合は、acticity名を以下のようにする必要があります。
ActivityがMyActivityの場合、android:name=".MyActivity_"とします。
"_"を付けるってことです。

次回から、いろいろ紹介していこうと思います。

2012年8月5日日曜日

AndroidのAsyncTaskについて

今回は、AndroidのAsyncTaskについて。
知らない人のためにまずは、AsyncTaskの説明を。

Androidはメインスレッド内でDB接続等の時間のかかる処理を行うとその間、
画面はフリーズしてしまいます。
ボタンクリック時にDBへの登録処理を行い、
その間画面にはプログレスバー(くるくるするやつ)を表示しておきたいとします。
この時、メインスレッド(GUIスレッド)でプログレスバーを表示、そのまま同じスレッドで
登録処理を行うと、画面に表示されているプログレスバーは止まってしまいます。
これを解消するには、登録処理等のバックグラウンドで行いたい処理はメインスレッドとは
別のスレッドで行う必要があります。
この時、気をつけないと行けないのがバックグラウンドで動かしているスレッドから
UI操作を行うとエラーが発生してしまいます。
上記のような動作を簡単に実装できるのがAsyncTaskです。
以下に簡単なサンプルを。
public class SampleActivity extends Activity {

    /* ダイアログ(くるくるするやつ) */
    private ProgressDialog dialog;

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample);

        final Button submitButton = (Button)findViewById(R.id.submitButton);
        submitButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // くるくるを表示
                dialog = new ProgressDialog(SampleActivity.this);
                dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                dialog.setMessage("登録中");
                dialog.show();

                // AsyncTaskを開始
                final SampleTask task = new SampleTask();
                task.execute();
            }
        });
    }

    /**
     * バックグラウンド処理を行うクラス
     */
    class SampleTask extends AsyncTask&ltVoid, Void, Void&gt {

        /**
         * executeが実行された後に実行される。
         */
        @Override
        protected void doInBackground(Void... params) {
            // DB登録等のUIに関与しない処理
        }

        /**
         * doInBackgroundの後に実行される。
         * このメソッド内ではUIを操作できる。
         */
        @Override
        public void onPostExecute(Void... params) {
            // くるくるを消去
            dialog.dismess();
        }
    }
}

こんな感じになります。
ただAsyncTaskを内部クラスにするのがいやっていうか、だいたいここで行われる処理っていうのは
ビシネスロジックになることが多いので、Activityとは分離しておきたいので
別クラスにしたいところです。
ただ、そうするとAsyncTaskからUIを操作するために
AsyncTaskにActivityの参照を渡す必要が出てきてしまいます。
まあ、DB等にアクセスするためにはActivityが必要だったりするんですけど
画面個別のUI処理が入ってくる場合はどうしても、
画面固有のActivity(XxxxActivityみたいな)の型の参照を渡さなくてはいけなくて嫌な感じです。
できるだけ、Activityとして渡すに留めたい。そこで以下のような感じはどうでしょう?
まずは、以下のようなインターフェースを用意。
public interface Callback {

    /** 成功時のレスポンスコード */
    public static final int SUCCESS = 0;

    /** 失敗時のレスポンスコード */
    public static final int ERROR = -1;

    /**
     * コールバックメソッド
     */
    public void callback(final int responseCode, final int requestCode,
                                                    final Map<String, Object> resultMap);

}

次にActivity。
public class SampleActivity extends BaseNoMapActivity implements Callback {
   /* ダイアログ(くるくるするやつ) */
    private ProgressDialog dialog;

    private static final String REQUEST_CODE = "sample";

    @Override
    public void onCreate(final Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.sample);

        final Button submitButton = (Button)findViewById(R.id.submitButton);
        submitButton.setOnClickListener(new OnClickListener() {
            @Override
            public void onClick(final View v) {
                // くるくるを表示
                dialog = new ProgressDialog(SampleActivity.this);
                dialog.setProgressStyle(ProgressDialog.STYLE_SPINNER);
                dialog.setMessage("登録中");
                dialog.show();

                // AsyncTaskを開始
                final SampleTask task = new SampleTask(SampleActivity.this, REQUEST_CODE);
                task.execute();
            }
        });
    }

    /**
     * コールバックメソッド。
     * バックグラウンド処理終了後に呼び出される。
     */
    public void callback(final int responseCode, final int requestCode,
                                                    final Map<String, Object> resultMap) {

        if (REQUEST_CODE.equals(requestCode)) {
            dialog.dismess();
        }
    }
}

そして、AsyncTask。
    class SampleTask extends AsyncTask&ltVoid, Void, Void&gt {

        /** コールバック */
        private Callback callback;

        /** リクエストコード */
        private String requestCode;

        /** コールバックの引数に渡す結果データ */
        private Map<String, Object> resultMap;

        /**
         * コンストラクタ.
         */
        public SampleTask(final Callback callback, final String requestCode) {
           this.callback = callback;
           this.requestCode = requestCode;

            resultMap = new HashMap<String, Object>();
        }

        /**
         * executeが実行された後に実行される。
         */
        @Override
        protected void doInBackground(Void... params) {
            // DB登録等のUIに関与しない処理
            // resuletMapにActivityに渡した値を詰めたり
        }

        /**
         * コールバックメソッドを実行する。
         */
        @Override
        public void onPostExecute(Void... params) {
            callback.callback(Callback.OK, requestCode, resultMap);
        }
    }
こんな感じです。
まずは、Callbackインターフェースですが、これでAsyncTackからActivityに処理を戻せるようにしています。
callbackメソッドによりActivityは「どのTaskを実行したのか」「成功したのか?」「結果データは?」と
といったものを受け取れるので、それに応じたUI処理を行えば良くなります。
考え方的にには、ActivityのstartActivityForResultとonActivityResultの考え方と同じです。
これなら、ある程度役割を分割できるのではないでしょうか。