= Bacon Rank Android App = ※これは以下のブログの翻訳である。 * [[http://www.heikkitoivonen.net/blog/2010/05/13/bacon-rank-android-app-details/]] Bacon Rank Android App Details May 13, 2010, 10:11 pm ベーコンランクはUIとバックグラウンド処理という観点から見て、僕が最も野心的に取り組んでいるプロジェクトだ。 これはカスタムなUIコントロールをもち、長時間のネットワークオペレーションをうまく処理する。 まずは、最初にカスタムなSeekBarを見てみよう。これは君がベーコンを何切れ食べたかを選択するものなんだ。 タッチスクリーンがあるなら、特に物理的なキーボードが無いのであれば、ユーザに何かをタイプさせるのは避けたいよね。 一回にそれほど多くのベーコンを食べる人なんていないから、このSeekBarはとてもうまく動作してくれるんんだ。 まずはカスタムなイメージでSeekBarを表示させるために、bacon_seekbar.xmlをdrawableフォルダに作成する。 {{{ }}} progress_* というDrawableはPNG画像だよ。 メインレイアウトで、SeekBarを使い、カスタムなサムイメージを使う(skilletのPNG画像だ)。 {{{ }}} SeekBarは三つの状態を持つ(バックグラウンドも入れるとするなら)、だけど二つしか必要ではない。 バックグラウンドと進捗状態を使おうとしたけど、おかしなことにうまく動かないんだ。 だからそのかわりに、SecondaryProgressをバックグラウンドとして使うことにした。バックグラウンドは見えないようにしてある。 onCreateでSeekbarをセットアップする。 {{{ mSeekBar = (SeekBar)findViewById(R.id.SEEKBAR); mSeekBar.setOnSeekBarChangeListener(this); mSeekBar.setMax(PROGRESS_MAX); }}} onProgressChangedメソッドで、UIの「量」を変更する。 とても素敵に見えるんだけど、でももっと3Dっぽくすればよりよくなるかも。 アプリケーションのもっともトリッキーな部分は、スクリーンの向きの変更中にネットワーク通信を扱う部分だ。 (Androidの)チュートリアルや、stackoverflowの質問と答えのどれもこれもうまくいかない(まぁ、僕が理解できていない可能性もあるけど)。 問題というのはこうだ。Activityの中でAsyncTaskを使ってネットワークとの通信を行うとしよう。 実際のところ、これはどこでだって推奨されてる方法だよね。 ところがだ。ユーザがスクリーンの向きを変えたらどうなる? デフォルトではAndroidは現在のActivityを破棄して、新しいActivityを作成する。 AsyncTaskは動作し続けていて、新しいActivityのことなんか知らないんだ。 逆に、新しいActivityはAsyncTaskのことなんか知らない。どうすりゃいいの? そこで、カスタムなアプリケーションオブジェクトを作ればいいってわけ。 これはプロセスが走っている限り存在し続けるからね。 だから、ActivityがAsyncTaskをスタートさせたら、アプリケーションにそれへの参照を持たせる。 AsyncTaskはスクリーンに描画中のActivityへの参照が必要なんだけど、それを常に最新のものにするってわけ。 つまり、AsyncTaskの生成時にこれをセットし、スクリーンの回転時Activtyが破棄されたときにnullにし、Activityが再生成されたときに復帰する。 なおかつ、ネットワーク通信が進行中には進捗インジケータも表示させたいよね。 こんな感じだよ(関係の無い部分ば省略してるよ)。 {{{ public class BaconApp extends Application { public BaconRank.SyncTask mSyncTask = null; // ... } public class BaconRank extends Activity implements SeekBar.OnSeekBarChangeListener { // ... @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); requestWindowFeature(Window.FEATURE_INDETERMINATE_PROGRESS); // ... } BaconRank.SyncTask getSyncTask() { return ((BaconApp)getApplication()).mSyncTask; } void sync(String action) { ((BaconApp)getApplication()).mSyncTask = (SyncTask) new SyncTask(this).execute(getUserAgent(), mUserId, mBaconStrips.getText().toString(), action); } @Override public void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); SyncTask syncTask = getSyncTask(); if (syncTask != null) { syncTask.setActivity(null); } } @Override public void onRestoreInstanceState(Bundle inState) { super.onRestoreInstanceState(inState); SyncTask syncTask = getSyncTask(); if (syncTask != null) { setProgressBarIndeterminateVisibility(true); syncTask.setActivity(this); } } @Override public void onStop() { super.onStop(); SyncTask syncTask = getSyncTask(); if (syncTask != null) { syncTask.setActivity(null); } } public class SyncTask extends AsyncTask { // ... public SyncTask(BaconRank activity) { super(); mActivity = activity; } public void setActivity(BaconRank activity) { mActivity = activity; } protected void onPreExecute() { if (mActivity != null) { mActivity.setProgressBarIndeterminateVisibility(true); } } protected void onCancelled() { // mActivity cannot be null mActivity.setProgressBarIndeterminateVisibility(false); clearActivity(); } void clearActivity() { // call in ui thread only ((BaconApp)mActivity.getApplication()).mSyncTask = null; } protected void onPostExecute(JSONObject result) { if (mActivity != null) { // Update UI } clearActivity(); } protected JSONObject doInBackground(String... params) { // Do the network stuff } // ... } }}} そうそう、カスタムアプリケーションオブジェクトをAndroidMnifest.xmlで定義するのを忘れないようにね。 {{{ }}}