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の考え方と同じです。
これなら、ある程度役割を分割できるのではないでしょうか。

2012年7月26日木曜日

AWS SDK for JAVA -S3 アップロード編-

昨日に続いて、今日はAWS SDK for JAVAを用いたAmazonS3へアップロード方法を紹介。

早速サンプルソース。

public void upload() {
    //AmazonS3Clientを作成
    final BasicAWSCredentials credentials =
               new BasicAWSCredentials("アクセスキー", "シークレットキー");
    final AmazonS3Client s3Client = new AmazonS3Client(credentials);

    // アップロード対象のオブジェクトを作成
    // 第一引数:アップロード先のバケット名
    // 第二引数:アップロード後のファイル名
    // 第三引数:アップロード対象のFileオブジェクト
    final PutObjectRequest por = 
               new PutObjectRequest("バケット名", "ファイル名", new File("ファイルパス"));

    // アップロード対象ファイルの権限を設定する
    // 以下のようにすると、誰でもHTTPでファイルを参照することができるようになる
    por.setCannedAcl(CannedAccessControlList.PublicRead);

    try {
        // アップロード
        s3Client.putObject(por);
    } catch(final AmazonClientException e) {
        e.printStackTrace();
    }
}


ちなみに、PutObjectRequestのコンストラクタの第二引数のファイル名に"/"を入れると、
AWS側ではそれを、特別な扱いをしてくれることでAWS Management Consoleで見た時に、
ディレクトリであるかのように表示してくれます。
ただし、実際にはあくまで"/"がファイル名に含まれているだけの単一のファイルになっています。

AWS SDK for JAVA -S3 参照編-

Amazon Web Service(AWS)のSimple Storage Service(S3)をJavaで操作する方法。 

まず準備として、http://aws.amazon.com/jp/sdkforjava/から「AWS SDK for JAVA」をDLする。
解凍したディレクトリのlib内にある「aws-java-sdk-xxx.jar」をビルドパスに追加する。
これ、AWSをJavaから操作する場合の必須準備。

で、サンプルソース。
まずは、バケット一覧の取得。

public void dispBucketList() {

    // AWSのHP内のセキュリティ証明書から確認できる「アクセスキー」と「シークレットキー」を
    // を用いて、AWSに接続するための認証オブジェクトを作成.
    final BasicAWSCredentials credentials = 
                      new BasicAWSCredentials("アクセスキー", "シークレットキー");
 
    // S3に接続するクライアントオブジェクトを作成.
    final AmazonS3Client s3Client = new AmazonS3Client(credentials);

    // バケット情報を取得
    final List<Bucket> bucketList = s3Client.listBuckets();

    for (final Bucket bucket : bucketList) {
        System.out.println("バケット名:" + bucket.getName());
    }
}


次に、バケット内のファイル情報取得。
// 引数については、1つ目のサンプル参照
public void dispItem(final AmazonS3Client s3Client, final String bucketName) {
    // 以下のようなおまじないを書くことでエラーが出なくなるそう
    System.setProperty("org.xml.sax.driver", "org.xmlpull.v1.sax2.Driver");

    // 指定したバケット内のファイル情報を取得
    final ObjectListing objectListing = s3Client.listObjects(bucketName);
    for (final S3ObjectSummary summary : objectListing.getObjectSummaries()) {
        // ファイル名を表示
        System.out.println("ファイル名:" + summary.getKey());
    }
}

他にも色々と情報が取得できるのですが、詳しくは以下を見てみてください。
APIリファレンス

2010年12月8日水曜日

Highcharts

最近、個人的に作っているWebアプリでグラフを書きたくなったので、
何かいいグラフ書くもの無いかなーと探してたら、見つけたのでちょっと紹介させていただきます。

Highcharts

とりあえず、サンプルソースはこちら
<html>
 <head>
  <meta http-equiv="Content-Type" content="text/html; charset=utf-8">
  <title>Highcharts Example</title>
  
  <script type="text/javascript" src="../../js/jquery-1.4.2.min.js"></script>
  // highcharts.js読み込み
  <script type="text/javascript" src="../../js/highcharts.js"></script>
  // デザイン用js読み込み。無くてもOK
  <script type="text/javascript" src="../../js/themes/gray.js"></script>
  // 詳しくは見れてないですが、コレも必要
  <script type="text/javascript" src="../../js/modules/exporting.js"></script>
  <script type="text/javascript">
  
   var chart;
   $(function() {
    chart = new Highcharts.Chart({
     chart: {
      // グラフを表示する領域を指定
      renderTo: 'container',
      // グラフのタイプを指定
      defaultSeriesType: 'bar'
     },
     title: {
      text: 'Historic World Population by Region'
     },
     subtitle: {
      text: 'Source: Wikipedia.org'
     },
     xAxis: {
      // グラフの縦軸の項目名を指定
      categories: ['Africa', 'America', 'Asia', 'Europe', 'Oceania'],
      title: {
       text: null
      }
     },
     yAxis: {
      min: 0,
      title: {
       text: 'Population (millions)',
       align: 'high'
      }
     },
     tooltip: {
      // グラフにマウスを乗せたときのtooltipの表示を指定
      formatter: function() {
       return ''+
         this.series.name +': '+ this.y +' millions';
      }
     },
     plotOptions: {
      bar: {
       dataLabels: {
        enabled: true
       }
      }
     },
     legend: {
      layout: 'vertical',
      align: 'right',
      verticalAlign: 'top',
      x: -100,
      y: 100,
      floating: true,
      borderWidth: 1,
      backgroundColor: '#FFFFFF',
      shadow: true
     },
     credits: {
      enabled: false
     },
      // 表示したいデータをJSON形式で指定
     series: [{
      name: 'Year 1800',
      data: [107, 31, 635, 203, 2]
     }, {
      name: 'Year 1900',
      data: [133, 156, 947, 408, 6]
     }, {
      name: 'Year 2008',
      data: [973, 914, 4025, 732, 34]
     }]
    });
   });
  </script>
 </head>
 <body>
  <div id="container" style="width: 800px; height: 400px; margin: 0 auto"></div>
 </body>
</html>

デモはこちら

こんな感じに、簡単にできます。
オプションの簡単な説明をすると、まず

22行目
グラフのタイプを指定します。
"line"と指定すると折れ線グラフになったり、"colum"と指定すると上下の棒グラフになります。

73行目
seriesというオブジェクトにJSON形式でデータを定義してあげます。
nameとdataというKEY値で指定してあげてください。


横軸のメモリとかは、データの大きさによって自動でスケールアウトしたり、
グラフの項目名をクリックすると、その項目を非表示にできたりとかなりいろいろできるので
おすすめです。今回は紹介していないですが、自動で時間経過に応じてグラフを描画するなんてことも
できるので、かなりUI的に良いものができそうです。今度はそっちの紹介でもしようかなー。

2010年11月26日金曜日

JSONのValue値

JSONの書き方について、最近知ったことを紹介させていただきます。
JSONを使い慣れている人にとっては、常識かもしれないけど、知ったときは
「へぇ~~」ってなったのでね。

そもそも、JSONって何かと言うとJavaScriptでよく扱われるデータの書式のことです
(合ってるかな??

一般的な形は以下のとおり。

{"KEY":'VALUE',"KEY":'VALUE'}

みたいな形で、KeyとValueの形でデータを表します。
ちなみに、これはクライアント側だけではなく、サーバ側でも使えます。
だいぶ前に紹介したJSONIC
なんていうのは、JSON形式の文字列をJavaの
クラスにエンコードしたり、逆にクラスをJSON形式の文字列にデコードしたりしてくれる
ライブラリです。

それで、注目して欲しいのが、VALUEを「'」で囲っている所。
なんとなく、自分は今まで「'」を使ってたんだけど、コレだと問題があるのです。
例えば、value値に「"」を含みたい場合は、
{"KEY":'"hoge"'}
で、問題無くvalue値が「"hoge"」として認識されます。
「'」をvalue値に含みたい場合は、
// パースできない
{"KEY":''hoge''}
// こっちもパースできない
{"KEY":'\'hoge\''}
上記みたいに記述したらパースJSONICがパース出来ずにExceptionをはいてしまいました。

で、代わりに下記の記述方法を試してみます。
{"KEY":"VALUE"}
key値・value値ともに、「"」を囲み文字として使用してみます。
この方法だと、
{"KEY":"'hoge'"}
{"KEY":"\"hoge\""}
上記の様にすることで、それぞれvalue値に「"」「'」を含める事ができます。
もちろん問題なくJSONICパース出来ます。

どうやら、正式にはJSONのvalue値は["]で囲ってやるのが正しい形のようです。
今までは、なんとなく「'」を使用していたので、今後は「"」を使用していこうと思います。

2010年11月23日火曜日

Slim3のGlobalTransaction

今更感がすごいあるけど、Slim3のトランザクションについてメモ。
まずは、普通のトランザクション。

public void update(final Key key) {
    // トランザクション開始
    final Transaction tx = Datastore.beginTransaction();
    fianl Model model = Datastore.get(tx, Model.class, key);

    mode.setName("hoge");
    Datastore.put(model)

    // コミット
    tx.commit();
}

GAEはデフォルトのトランザクションだと、単一のエンティティにしかトランザクションを貼れない。
でも、複数のエンティティを一緒に更新したい時ってのがあるわけで、そういう時に
使用するのが、グローバルトランザクション。

public void update(final Key hogeKey, final Key fugaKey) {
    // トランザクション開始
    final GlobalTransaction gtx = Datastore.beginGlobalTransaction();
    fianl HogeModel hogeModel = gtx.get(HogeModel.class, hogeKey);
    fianl FugaModel fugaModel = gtx.get(FugaModel.class, fugaKey);

    hogeModel.setName("hoge");
    fugaModel.setName("fuga");
    gtx.put(hogeModel, fugaModel)

    // コミット
    gtx.commit();
}

上記の様にすれば、複数のエンティティにまたがる処理を同一トランザクションで処理出来ます。
ちなみに、上記で使用しているputメソッドの引数は可変長引数なので、
2つ以上のエンティティの更新が可能です。

ここで、注意して欲しいのがqueryメソッドはトランザクションのサポート外ってところ。
なので、エンティティのプロパティにKeyを生成するための文字列を持たせておくのが
ベターでしょうか。(更新時等にKeyでエンティティを取得できるようにするため)

2010年10月3日日曜日

NicoBot ~Chrome Extensions~[内部的な事]

今回は、前回紹介させていただいたChrome Extensions「NicoBot」を
作成するにあたって使用した技術について書かせていただきます。

まずは、なんといってもChrome Extensionsで
クライアント側の処理を記述してます。
サーバ側はGoogle app engine(slim3)を使ってます。
Twitterへのアクセスには、Twitter4jを使用してます。

処理の流れは以下のとおり。

  1. Extensionのアイコンのクリックイベントで、ユーザを一意に特定できるようにする為に現在日付とホスト名を取得し、それを合わせた文字列(①)をサーバ側に渡す。
  2. サーバ側では、リクエストを受け取ったらtwitterにアプリケーションの認証ページへのリダイレクト処理を行う。
  3. ユーザが認証を許可した場合、サーバ側でtwitterから発行されたoAuth情報と先程クライアント側から受け取った文字列(①)を紐付けてBigTableに登録する。そして、認証完了のペーシで画面遷移を行う。
  4. クライアント側では、認証完了ページへの遷移をハンドルして先程の作成した文字列(①)をlocalStorageに保存し、Extensionのアイコンを認証完了後のアイコンに変更する。
  5. この状態で、Extensionのアイコンをクリックすると、ON ⇔ OFF の切り替えが行えるようになる。
  6. ちなみに、このON ⇔ OFF の状態もlocalStorageに保存する。
  7. ONの状態で、ニコニコ動画のURLに遷移すると、クライアント側ではURLからニコニコ動画の動画のID部分を抜き出し、ユーザを特定するための文字列(①)とID部分(②)をサーバ側に渡す。
  8. サーバ側では、受け取った動画ID(②)を元に、ニコニコ動画APIを使用して動画情報を取得する。
  9. その後は、ユーザ情報(①)でBigTableを検索し、TwitterのoAuth情報を取得しTwitterにPOSTする。
ただ、今のままだとExtensionをアンインストールしてもlocalStorageに
残ったままになってしまうので、オプションページを作成して、
そこからlocalStorageの情報を削除できるようにしたいなぁー。


そうそう、実は地味にアップデートさせました。
Twitterに投稿した際に、画面に右下にポップアップウインドウ出るように
してみました。これで、誤爆にも気付けるはず!

以下、参考サイト