= 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で定義するのを忘れないようにね。
{{{
}}}