-
Notifications
You must be signed in to change notification settings - Fork 354
2.08. 非同期処理
GitHub Pagesへ移行しましたmixi-inc.github.ioへお願いします。
wikiの方はしばらくしたら消していきます
この章では、Android での非同期処理について解説します。
参考:Services | Android Developers
参考:Loaders | Android Developers
参考:Processes and Threads | Android Developers
-
Service
- [Service の状態](#Service の状態)
- [Service のライフサイクル](#Service のライフサイクル)
- [Service を構築するクラス](#Service を構築するクラス)
- [Service の呼び出し](#Service の呼び出し)
- Loader
- AsyncTask
Service とは、バックグラウンドで動作する Android のコンポーネントです。
Service
は、その呼び出し方によって 2 つのいずれかの状態をとります。
Context#startService(Intent)
によって呼び出された時の状態です。
Context#startService(Intent)
を呼び出したコンポーネントとは別のライフサイクルを持つので、そのコンポーネントが死んでも生きていることがあります。
通常、この呼出で呼び出されたService
は、結果を呼び出し元に返すことはありません。Service
の処理内容が終わったら、自身でService
のライフサイクルを終わらせる必要があります。
Context#bindService(Intent)
によって呼び出された時の状態です。
Service
をバインドすることによって、呼び出し元のコンポーネントとService
の間に、クライアント-サーバの関係が構築されます。
よって、バインドされたService
と連携したり、リクエストを送ったり、結果を取得したり、プロセス間通信をも行うことが可能となります。
バインドされたService
のライフサイクルは、バインドしたコンポーネントのライフサイクルに合わせられます。
複数のコンポーネントがService
をバインドすることも可能ですので、全てのバインドしたコンポーネントが破棄された時に、Service
のライフサイクルも終了します。
Service
がどのように呼び出されたかによって、ライフサイクルが異なります。
Context#startService(Intent)
によって開始されたSerivce
は、自身が終了を宣言するか、誰かが終了を命令するまで生存し続けます。
Context#bindService(Intent)
によってバインドされたService
は、バインドしている呼び出し元が全員バインドの解除を行うまで生存し続けます。
一般的な Service を作成するひな形クラスです。
バックグラウンドで動作するものではありますが、その実動作しているスレッドは、Service を動かしているプロセスのメインスレッドです。
つまり、Service 内でブロックする処理を記述すると、メインスレッドの処理がブロックします。よって、独自のプロセスで動かしたい場合は、その旨 AndroidManifest で宣言する必要があり、また、ブロックする処理を実行する場合は、自分でスレッドを新しく立てる必要があります。
また、サービスの状態(開始、またはバインド)によって、プログラムの書き方に若干の違いがあることに注意してください。
下記に、その例を示します。
/**
* Service を継承したクラス。
* {@link Context#startService(Intent)}で呼び出すためのサンプル。
*/
public class StartedService extends Service {
public static final String TAG = StartedService.class.getSimpleName();
/**
* {@link Service} のライフサイクルの開始。
*/
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "onCreate");
}
// バインド用のメソッド。今回は特に必要ないので null を返す。
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG, "onBind");
return null;
}
/**
* {@link Context#startService(Intent)} の呼び出しで呼ばれる。
* このメソッドの処理は、{@link Context#startService(Intent)}を呼び出したスレッドと同じスレッドで実行されるので
* メインスレッドで {@link Service} を起動した場合に、ここでネットワーク通信などスレッドをブロックする処理をしてしまうと
* UI の処理がブロックされ AND となる。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* {@link Service} のライフサイクルの終了。
*/
@Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
super.onDestroy();
}
}
/**
* バインドするサービス。
*/
public class BoundService extends Service {
public static final String TAG = BoundService.class.getSimpleName();
private final IBinder mBinder = new ServiceBinder();
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "onCreate");
}
// 最初にバインドした時のコールバック
@Override
public IBinder onBind(Intent intent) {
return mBinder;
}
// 再度バインドした時のコールバック
@Override
public void onRebind(Intent intent) {
super.onRebind(intent);
Log.v(TAG, "onRebind");
}
// バインドを解除された時のコールバック
@Override
public boolean onUnbind(Intent intent) {
Log.v(TAG, "onUnbind");
return super.onUnbind(intent);
}
@Override
public void onDestroy() {
super.onDestroy();
Log.v(TAG, "onDestroy");
}
// サービスをバインドした後、バインドしたサービスのインスタンスそのものを得るためのインタフェース
public class ServiceBinder extends Binder {
public BoundService getService() {
return BoundService.this;
}
}
}
開始するサービスも、バインドするサービスも、AndroidManifest には以下のように記述します。
<application
android:allowBackup="true"
android:icon="@drawable/ic_launcher"
android:label="@string/app_name"
android:theme="@style/AppTheme" >
<service
android:name="jp.mixi.sample.service.BoundService"/>
<service
android:name="jp.mixi.sample.service.StartedService"/>
</application>
Intent による呼び出しで開始される Service であり、かつ、Service の開始要求を 1 つずつ順に処理するワーカスレッド上で動作する特別な Service です。 一度に複数の処理を並列して行う必要がない場合は、IntentService を利用するほうが実装が簡単です。
public class MyIntentService extends IntentService {
public static final String TAG = MyIntentService.class.getSimpleName();
public MyIntentService() {
this(MyIntentService.class.getSimpleName());
}
public MyIntentService(String name) {
super(name);
}
/**
* {@link Service} のライフサイクルの開始。
*/
@Override
public void onCreate() {
super.onCreate();
Log.v(TAG, "onCreate");
}
/**
* 親クラスで必要な処理がひと通り揃っているため、通常は Override の必要はない。
*/
@Override
public IBinder onBind(Intent intent) {
Log.v(TAG, "onBind");
return super.onBind(intent);
}
/**
* 親クラスで必要な処理がひと通り揃っているため、通常は Override の必要はない。
*/
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.v(TAG, "onStartCommand");
return super.onStartCommand(intent, flags, startId);
}
/**
* {@link Context#startService(Intent)} によって呼び出される。
* ワーカスレッド上で実行されるため、ネットワーク通信等のスレッドをブロックする処理を直接記述しても問題ない。
*/
@Override
protected void onHandleIntent(Intent intent) {
Log.v(TAG, "onHandleIntent");
}
/**
* {@link Service} のライフサイクルの終了。
* {@link IntentService} では、1 回の {@link Context#startService(Intent)} の呼び出しで
* 1 つのライフサイクルが回るように作られている。
*/
@Override
public void onDestroy() {
Log.v(TAG, "onDestroy");
super.onDestroy();
}
}
こちらも、AndroidMafifest には、Service
と同じ宣言を行います。
開始するサービスと、IntentService
は、以下のように呼び出します。
開始するサービスは、サービス自身で終了の命令を実行しない場合、サービスを止めるメソッドを呼び出します(IntentService
は自分で終了するため必要ありません)。
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do something ...
Intent intent = new Intent(this, StartedService.class);
startService(intent);
}
@Override
protected void onStop() {
Intent intent = new Intent(this, StartedService.class);
stopService(intent);
super.onStop();
}
}
バインドするサービスは、以下のように呼び出します。
呼び出した後、バインドしたサービスのインスタンスを受け取って、直接そのインスタンスを操作する事ができるようになります。
バインドするサービスは、バインドするコンポーネントのライフサイクルにしたがって管理する必要があるので、コンポーネントのライフサイクルの終わりにサービスのバインドを解除する必要があります。
public class MyActivity extends Activity {
private BoundService mBoundService;
// BoundService を扱う時のインタフェース
private ServiceConnection mConnection = new ServiceConnection() {
@Override
public void onServiceDisconnected(ComponentName name) {
Log.v(TAG, "onServiceDisconnected");
mBoundService = null;
}
@Override
public void onServiceConnected(ComponentName name, IBinder service) {
Log.v(TAG, "onServiceConnected");
mBoundService = ((BoundService.ServiceBinder) service).getService();
}
};
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do something ...
// サービスのバインド
// バインド時の、バインド元とバインド先サービスの橋渡しをするための ServiceConnection インスタンスを一緒に渡す
// Context.BIND_AUTO_CREATE によって、バインド時に自動で Service のライフサイクルが始まるようになる
bindService(new Intent(MainActivity.this, BoundService.class), mConnection, Context.BIND_AUTO_CREATE);
}
@Override
protected void onStop() {
unbindService(mConnection);
super.onStop();
}
}
非同期に処理を行うための新しいフレームワークです。特に、ネットワークやファイル I/O を介してデータを読み出すためのフレームワークとして設計されているものです。
Activity や Fragment のライフサイクルと、非同期処理を分離する目的で作られており、後述するAsyncTask
の改良版とも言えます。
Loader
は Honeycomb 以後から導入されました。
SupportPackage にも含まれているので、2.x 系の OS でも利用することができます。
ネットワーク通信やその他のファイル I/O などによってデータを読み出す場合は、このクラスを拡張して非同期処理を記述します。
// Support Package のものを利用する
import android.support.v4.content.AsyncTaskLoader;
public class MyAsyncTaskLoader extends AsyncTaskLoader<String> {
public static final String TAG = MyAsyncTaskLoader.class.getSimpleName();
private String mCachedData;
public MyAsyncTaskLoader(Context context) {
super(context);
}
// 非同期処理の中身
@Override
public String loadInBackground() {
try {
Thread.sleep(1000L);
} catch (InterruptedException e) {
Log.e(TAG, "interrupted!: ", e);
}
return "hogehoge";
}
@Override
public void deliverResult(String data) {
// ローダがリセットされ、そのローダのライフサイクルが終了となる場合
if (isReset()) {
// キャッシュデータがある場合は、キャッシュを削除して、メモリから破棄可能にする
if (mCachedData != null) {
mCachedData = null;
}
return;
}
// 得られたデータをキャッシュする
mCachedData = data;
// ローダが開始されている場合、親にデータが得られたことを通知する
if (isStarted()) {
super.deliverResult(data);
}
}
@Override
protected void onStartLoading() {
// キャッシュがある場合はそちらを返す
if (mCachedData != null) {
deliverResult(mCachedData);
return;
}
// データソースに変更があったり、キャッシュデータがない場合は loadInBackground() に行くようにする
if (takeContentChanged() || mCachedData == null) {
forceLoad();
}
}
// ローダの非同期処理がストップする時のコールバック
@Override
protected void onStopLoading() {
cancelLoad();
super.onStopLoading();
}
// ローダがリセットされる時のコールバック
@Override
protected void onReset() {
onStopLoading();
super.onReset();
}
}
ローダとのやりとりは、LoaderManager
とLoaderCallbacks
を用いて行います。
public class MainActivity extends FragmentActivity implements LoaderCallbacks<String> {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ローダの管理をするオブジェクト
LoaderManager manager = getSupportLoaderManager();
Bundle argsForLoader = new Bundle();
// ローダを初期化して非同期処理を開始する
manager.initLoader(0, argsForLoader, MainActivity.this);
}
// id に対応した Loader のインスタンスを作って返す
// args は Loader に渡したい引数を Bundle に詰めたもの
@Override
public Loader<String> onCreateLoader(int id, Bundle args) {
switch (id) {
case 0:
return new MyAsyncTaskLoader(this);
default:
return null;
}
}
// 結果を受け取るコールバック
// メインスレッドで動作する
@Override
public void onLoadFinished(Loader<String> loader, String result) {
Toast.makeText(this, result, Toast.LENGTH_LONG).show();
}
// ローダがリセットされる時のコールバック
@Override
public void onLoaderReset(Loader<String> loader) {}
}
別の章で解説する、データベースからのデータの読み出しに特化したクラスです。
名前の通り、非同期処理のためのクラスです。
非同期に実行したい処理と、処理前、処理後のメインスレッド上での処理を記述するため、このクラスを継承して使います。
AsyncTask
では、内部でスレッドプールを持っています(Donut までは単一のスレッドで動作していた)。
デフォルトでは 5 つのスレッドがプールされており、内部で並列して複数の非同期処理を実行できるようになっています(Honeycomb 以降は、スレッドの並列実行における諸問題を回避するため単一スレッドで動作するように戻っている)。
最大でプールされるスレッド数は 128 です。
AsyncTask
オブジェクトは、使い回しができません。一度処理が完了すると、その後にもう一度非同期処理の実行を依頼した時点で、例外が投げられます。
よって、再度非同期処理を実行したい場合は、新たにAsyncTask
オブジェクトを作成する必要があります。
/**
* 非同期処理を実行するためのネストクラス。
*
* ジェネリクスの仕組みを用いて、非同期処理に渡す引数の型、進捗を監視するコールバック用の型、非同期処理の結果を表す型を指定する。
*
* Activity や Fragment のライフサイクルに合わせて、自分で AsyncTask をコントロールする必要があり、これを行わないと
* 特に {@link AsyncTask#onPostExecute()} で、参照するオブジェクトが既にメモリから破棄されていて NullPointerException となることが起こりえる。
*/
public class MyAsyncTask extends AsyncTask<Void, Void, Void> {
private Context mApplicationContext;
public MyAsyncTask(Context applicationContext) {
super();
mApplicationContext = applicationContext;
}
/**
* 非同期処理を実行する前に UI スレッドで実行する処理を書く
*/
@Override
protected void onPreExecute() {
super.onPreExecute();
Toast.makeText(mContext, "onPreExecute", Toast.LENGTH_SHORT).show();
}
/**
* 非同期処理の進捗を受け取るコールバック。
*/
@Override
protected void onProgressUpdate(Void... values) {
super.onProgressUpdate(values);
Toast.makeText(mContext, "onProgressUpdate", Toast.LENGTH_SHORT).show();
}
/**
* 非同期処理の本体
* 引数は非同期処理内容に渡すためのパラメータ配列。
*/
@Override
protected Void doInBackground(Void... params) {
// 2 秒おきに進捗を通知する
try {
publishProgress();
Thread.sleep(2000L);
publishProgress();
Thread.sleep(2000L);
publishProgress();
Thread.sleep(2000L);
publishProgress();
Thread.sleep(2000L);
publishProgress();
Thread.sleep(2000L);
publishProgress();
} catch (InterruptedException e) {
Log.e(MyAsyncTask.class.getSimpleName(), "thread interrupted: ", e);
}
return null;
}
/**
* 非同期処理の実行後に、UI スレッドで実行する処理。
* 引数は {@link AsyncTask#execute(Object...)} の返り値。
*/
@Override
protected void onPostExecute(Void result) {
super.onPostExecute(result);
Toast.makeText(mContext, "onPostExecute", Toast.LENGTH_SHORT).show();
}
}
public class MyActivity extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
// do something...
// 非同期処理の開始
new MyAsyncTask(getApplicationContext()).execute();
}
}
- (実習) サンプルプロジェクト (ServiceSample) に、サービスのライフサイクルをログに出力する実装が格納されています。このプロジェクトをビルドし、ログがどのように出力されているかをレポートしてください。
- (課題) SharedPreferences にデータを書き込むための IntentService を作成してください。
- (実習) サンプルプロジェクト (LoaderSample) に、AsyncTaskLoader のライフサイクルをログに出力する実装が格納されています。このプロジェクトをビルドし、ログがどのように出力されているかをレポートしてください。
- (課題) SharedPreferences からデータを読み出すための AsyncTaskLoader を作成してください(プロジェクトは Service の課題と同じ物を用いること)。
- (実習)
AsyncTask#doInBackground()
で、TextView の文字を変更するような、UI の処理を実行するとどうなるか、理由を添えてレポートしてください。
Portions of this page are reproduced from work created and shared by the Android Open Source Project and used according to terms described in the Creative Commons 2.5 Attribution License.