第 1 章 駄目コードとその対策 6
1.10 Application に Service の実体を突っ込んでいる
動けばいいという意味ではラッパークラス作らず、そのまま書いてしまっても違いはありま せん。しかしコード後から見たときや修正するときに、キーやデータ型のように手で合わせな いといけないものがそのままになっていると、誤って記述した際に不具合の原因となってしま います。特にこれらのキーやデータ型は誤って記述してしまうとコンパイルエラーにもなら ないため非常に厄介です。これら避けるため、ラッパークラスを作成して問題点の責務をラッ パークラスに持たせ、扱う側が無駄に悩まなくていいようにしましょう。ラッパークラスは単 にラッピングしているのではなく、そういった責務も封じ込めているのです。
@Override
public void onDestroy() { super.onDestroy();
DameApplication app = (DameApplication) getApplication();
app.setDameService(null);
}
public String hoge() { return "hogehoge";
} }
この
Service
のhoge
メソッドから文字列を受け取り、画面に表示するだけの乱暴な実装の
Activity
がリスト1.31
になります。リスト1.31: 悪い例 ResidentDameActivity.java
public class ResidentDameActivity extends Activity implements View.OnClickListener {
private EditText editText1;
@Override
protected void onCreate(Bundle savedInstanceState) { /* 省略 */
}
@Override
public void onClick(View v) { if (v.getId() == R.id.button1) {
DameApplication app = (DameApplication) getApplication();
ResidentDameService service = app.getDameService();
if (service != null) {
editText1.setText(app.getDameService().hoge());
} } } }
こ の
Activity
はResidentDameService#onCreate
が 呼 び 出 さ れ た 後 な ら ば 動 く こ と は 動 き ま す 。し か しService
が 起 動 す る 前 や 再 起 動 し た 場 合 はDameApplica-tion.getDameService()
がnull
を返すため正常に動作しません。またActivity
側で対応し ようとしても、起動や再起動が発生したか否かのイベントが通知されないため、対応が行えません。これを回避するには
Application
に保持させたりせず、標準の手順にあるとおりServiceConnection
を使います。まず
onCreate
メソッド内でApplication
に自身を保持させたり、onDestroy
メソッド内で 解除したりするような乱暴な実装を削除します。リスト1.32: 良い例 ResidentRefineService.java
public class ResidentRefineService extends Service {
public class LocalBinder extends IResidentRefineService.Stub { public ResidentRefineService getService() {
return ResidentRefineService.this;
} }
private LocalBinder mBinder = new LocalBinder();
@Override
public IBinder onBind(Intent intent) { return mBinder;
}
public String hoge() { return "hogehoge";
} }
Activity
側もリスト1.33
のように、ServiceConnection
を作成し、標準の手続きにした がってService
をbind
するようにします。リスト1.33: 良い例 ResidentRefineActivity.java
public class ResidentRefineActivity extends Activity implements View.OnClickListener {
private EditText editText1;
private ResidentRefineService mService;
ServiceConnection mServiceConnection = new ServiceConnection() {
@Override
public void onServiceConnected
(ComponentName name, IBinder service) {
mService = ((LocalBinder) service).getService();
}
@Override
public void onServiceDisconnected(ComponentName name) { mService = null;
} };
@Override
protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.button1).setOnClickListener(this);
editText1 = (EditText) findViewById(R.id.editText1);
}
@Override
protected void onStart() { super.onStart();
Intent intent = new Intent(this, ResidentRefineService.class);
bindService(intent, mServiceConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onPause() { super.onPause();
unbindService(mServiceConnection);
}
@Override
public void onClick(View v) { if (v.getId() == R.id.button1) {
if (mService != null) {
editText1.setText(mService.hoge());
} } } }
一見面倒には見えますが、不安定な状態をハンドリングできるようにするために必要な手続 きです。これは
Activity
とService
にはライフサイクルの違いこそあれ、2つは対等な関係 であるからです。対等な2つを接続するということはそれなりの手順が必要になるというこ とです。なお
Bind
したService
は忘れずにUnbind
しましょう。これを忘れるとServiceCon-nection
のリークに繋がります。この例ではService
をbind/unbind
するタイミングはon-Start()/onStop()
を使用していますが、onCreate()/onDestroy()
やonPause()/onResume()
など、
Activity
のライフサイクル上で必ず通る組み合わせを使用するとリークを回避することができます。