Android アプリケーションは一つ以上の Activity、Service を含みます。Service は UI をもたず、長時間かかる処理をバックグラウンドで行うためのコンポーネントです。基本的な使い方をまとめます。
関連する公式ドキュメント
単純な Service (非同期処理なし)
Android アプリケーションにおいて、既定ではすべてのアプリケーションのスレッドはアプリケーション個別のプロセス内に生成されます。プロセス内のスレッドには、UI を操作する処理を実行する main スレッド (UI スレッド) や、バックグラウンド処理を行うための worker スレッドが存在します。以下の MyService は Service を直接継承しており、MainActivity から startService()
すると、main スレッドで onStartCommand()
の処理が実行されます。そのため、以下のサンプルコードに実用性はなく、一般の用途であれば、worker スレッドを利用する、後述の IntentService を継承した MyIntentService を利用すべきです。
MyService.java
onStartCommand()
で main スレッドを 5 秒間占有してみます。また、onStartCommand()
の返り値で、システムによって MyService が強制終了させられたときの挙動を指定できます。IntentService の場合と同様に、START_STICKY
によって、サービスが Android システムによって kill されたらサービスを再作成して起動するように設定しています。
package com.example.mycompany.myapp;
import android.app.Service;
import android.content.Intent;
import android.os.IBinder;
import android.support.annotation.Nullable;
import android.util.Log;
public class MyService extends Service {
private final String TAG = "MyService";
@Override
public void onCreate() {
super.onCreate();
Log.d(TAG, "created");
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
Log.d(TAG, "started");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "started/done");
return START_STICKY;
}
@Nullable
@Override
public IBinder onBind(Intent intent) {
// https://developer.android.com/guide/components/bound-services.html
return null;
}
@Override
public void onDestroy() {
super.onDestroy();
Log.d(TAG, "destroyed");
}
}
MainActivity.java
MainActivity の myStartButton/myStopButton を押すとサービスが起動/停止します。それぞれ、MyService をクラス名として設定した Explicit intent を引数にして startService()
および stopService()
を実行しています。
package com.example.mycompany.myapp;
import android.content.Intent;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button startButton = (Button)findViewById(R.id.myStartButton);
startButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "start service");
Intent intent = new Intent(MainActivity.this, MyService.class);
startService(intent);
Log.d(TAG, "start service/done");
}
}
);
Button stopButton = (Button)findViewById(R.id.myStopButton);
stopButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "stop service");
Intent intent = new Intent(MainActivity.this, MyService.class);
stopService(intent);
Log.d(TAG, "stop service/done");
}
}
);
}
}
AndroidManifest.xml
Activity の場合と同様に、Service コンポーネントの存在を Android システムに認識させるためには AndroidManifest.xml
を編集する必要があります。
...
<manifest... package="com.example.mycompany.myapp">
<application... >
<activity android:name=".MainActivity">
...
</activity>
<service android:name=".MyService" /> ←追記
</application>
</manifest>
実行例
UI がフリーズするため、警告がログに出力されています。
08-06 21:58:00.637 1153-1153/com.example.mycompany.myapp D/MainActivity: start service
08-06 21:58:00.653 1153-1153/com.example.mycompany.myapp D/MainActivity: start service/done
08-06 21:58:00.687 1153-1153/com.example.mycompany.myapp D/MyService: created
08-06 21:58:00.689 1153-1153/com.example.mycompany.myapp D/MyService: started ←ここから
08-06 21:58:05.690 1153-1153/com.example.mycompany.myapp D/MyService: started/done ←ここまで 5 秒間 main スレッドを占有するため UI がフリーズします。そのため↓の警告が出ています。
08-06 21:58:05.690 1153-1153/com.example.mycompany.myapp I/Choreographer: Skipped 300 frames! The application may be doing too much work on its main thread.
08-06 21:58:11.319 1153-1153/com.example.mycompany.myapp D/MainActivity: stop service
08-06 21:58:11.320 1153-1153/com.example.mycompany.myapp D/MainActivity: stop service/done
08-06 21:58:11.321 1153-1153/com.example.mycompany.myapp D/MyService: destroyed
非同期処理ありの IntentService
MainActivity.java
および AndroidManifest.xml
は、前述「単純な Service (非同期処理なし)」の場合と以下の差分を除いて同じです。Service を直接継承した場合と異なり、start リクエストによって渡されたインテントを main スレッドではなく worker スレッドによってキューから一つずつ取り出して処理するため UI がフリーズすることはありません。
MainActivity.java
- Intent intent = new Intent(MainActivity.this, MyService.class);
+ Intent intent = new Intent(MainActivity.this, MyIntentService.class);
AndroidManifest.xml
+ <service android:name=".MyIntentService" />
MyIntentService.java
package com.example.mycompany.myapp;
import android.app.IntentService;
import android.content.Intent;
import android.util.Log;
public class MyIntentService extends IntentService {
private final String TAG = "MyIntentService";
public MyIntentService() {
super("MyIntentService"); // name for the worker thread
}
@Override
protected void onHandleIntent(Intent intent) {
Log.d(TAG, "handled");
try {
Thread.sleep(5000);
} catch (InterruptedException e) {
e.printStackTrace();
}
Log.d(TAG, "handled/done");
}
}
実行例
08-07 17:41:36.202 2675-2675/com.example.mycompany.myapp D/MainActivity: start service ←ボタンをタップ (一回目)
08-07 17:41:36.206 2675-2675/com.example.mycompany.myapp D/MainActivity: start service/done
08-07 17:41:36.231 2675-9955/com.example.mycompany.myapp D/MyIntentService: handled
08-07 17:41:37.445 2675-2675/com.example.mycompany.myapp D/MainActivity: start service ←ボタンをタップ (二回目)
08-07 17:41:37.451 2675-2675/com.example.mycompany.myapp D/MainActivity: start service/done
08-07 17:41:41.234 2675-9955/com.example.mycompany.myapp D/MyIntentService: handled/done ←5秒後、一つ目のリクエスト処理が完了
08-07 17:41:41.235 2675-9955/com.example.mycompany.myapp D/MyIntentService: handled ←キューから次のインテントを取り出して処理開始
08-07 17:41:46.236 2675-9955/com.example.mycompany.myapp D/MyIntentService: handled/done ←5秒後、処理完了
サービスの処理完了時に結果を返す
サービスで行う処理のうち、特に結果を返したい場合はブロードキャストや通知を利用します。
ブロードキャスト
前述の「非同期処理ありの IntentService」のサンプルコードをもとに、以下のように変更します。また、ブロードキャストでは受信したとしても Activity がバックグラウンドの場合はフォアグラウンドにはなりません。ユーザーへの完了通知を目的とする場合は後述の「通知」を利用します。
Constants.java
今回のサンプルで必要になる定数を定義します。
package com.example.mycompany.myapp;
public final class Constants {
// 様々なブロードキャストがなされる中で、特に今回検証のために実行するブロードキャストの識別子
public static final String MY_BROADCAST_ACTION = "com.example.mycompany.myapp.MY_BROADCAST";
// インテントに putExtra して格納するデータを取り出すためのキー名
public static final String EXTRA_MESSAGE = "com.example.mycompany.myapp.MESSAGE";
}
MyIntentService.java
onHandleIntent()
内でメッセージをブロードキャストします。
package com.example.mycompany.myapp;
import android.app.IntentService;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
public class MyIntentService extends IntentService {
public MyIntentService() {
super("MyIntentService"); // name for the worker thread
}
@Override
protected void onHandleIntent(Intent intent) {
Intent localIntent = new Intent(Constants.MY_BROADCAST_ACTION);
localIntent.putExtra(Constants.EXTRA_MESSAGE, "my message from MyIntentService");
// 本アプリケーションに制限して、レシーバにインテントをブロードキャスト
LocalBroadcastManager.getInstance(this).sendBroadcast(localIntent);
}
}
MyReceiver.java
受信時にメッセージをログに出力します。
package com.example.mycompany.myapp;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
public class MyReceiver extends BroadcastReceiver {
private final String TAG = "MyReceiver";
@Override
public void onReceive(Context context, Intent intent) {
String msg = intent.getStringExtra(Constants.EXTRA_MESSAGE);
Log.d(TAG, "received: " + msg);
}
}
MainActivity.java
レシーバとフィルタを Android システムに登録します。
package com.example.mycompany.myapp;
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.View;
import android.widget.Button;
public class MainActivity extends AppCompatActivity {
private static final String TAG = "MainActivity";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
// ここから追加>>>>>>>>>>
// MyIntentService がブロードキャストする想定のインテントを受信するレシーバー。
MyReceiver myReceiver = new MyReceiver();
// MyIntentService がブロードキャストする想定のインテントだけをフィルタリングするためのもの。
IntentFilter myIntentFilter = new IntentFilter(Constants.MY_BROADCAST_ACTION);
// レシーバとフィルタをセットにして Android システムに登録します。
LocalBroadcastManager.getInstance(this).registerReceiver(myReceiver, myIntentFilter);
// <<<<<<<<<<ここまで追加
Button startButton = (Button)findViewById(R.id.myStartButton);
startButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "start service");
Intent intent = new Intent(MainActivity.this, MyIntentService.class);
startService(intent);
Log.d(TAG, "start service/done");
}
}
);
Button stopButton = (Button)findViewById(R.id.myStopButton);
stopButton.setOnClickListener(
new View.OnClickListener() {
@Override
public void onClick(View v) {
Log.d(TAG, "stop service");
Intent intent = new Intent(MainActivity.this, MyIntentService.class);
stopService(intent);
Log.d(TAG, "stop service/done");
}
}
);
}
}
実行例
08-07 23:47:53.668 27760-27760/com.example.mycompany.myapp D/MainActivity: start service
08-07 23:47:53.674 27760-27760/com.example.mycompany.myapp D/MainActivity: start service/done
08-07 23:47:53.684 27760-27760/com.example.mycompany.myapp D/MyReceiver: received: my message from MyIntentService
通知
前述の「非同期処理ありの IntentService」のサンプルコードをもとに MyIntentService.java
を以下のように変更します。通知をタップすると MainActivity
が起動します。
package com.example.mycompany.myapp;
import android.app.IntentService;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.content.Intent;
import android.support.v7.app.NotificationCompat;
public class MyIntentService extends IntentService {
// 「通知」に後からアクセスする際に必要となる ID です。
private static final int notificationId = 001;
// 通知の管理者
private NotificationManager notificationManager;
public MyIntentService() {
super("MyIntentService"); // name for the worker thread
}
@Override
protected void onHandleIntent(Intent intent) {
// 通知をタップした際に start する Activity を指定してインテントを生成します。
Intent resultIntent = new Intent(this, MainActivity.class);
// インテントを PendingIntent にラップします。
PendingIntent resultPendingIntent = PendingIntent.getActivity(
this,
0,
resultIntent,
PendingIntent.FLAG_UPDATE_CURRENT);
// 通知を生成する builder に情報を設定します。
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(this);
notificationBuilder.setSmallIcon(R.drawable.my_icon)
.setContentTitle("MyTitle")
.setContentText("MyText")
.setContentIntent(resultPendingIntent);
// 通知します。
notificationManager = (NotificationManager) getSystemService(NOTIFICATION_SERVICE);
notificationManager.notify(notificationId, notificationBuilder.build());
}
}
関連記事
- Spring Security フォームログインのサンプルコードSpring フレームワークによる Web アプリケーション開発で、ログイン処理を実装する際は Spring Security が便利です。ここでは特に Spring Boot で Web アプリケーションを開発する場合を対象とし、フォームによる ID/Password ログインを行うためのサンプルコードをまとめます。 公式ドキュメント [Spring Security チュートリアル](http...
- Java配列の宣言方法 (C/C++との違い)Javaの配列 Javaの配列宣言方法はC/C++と似ているようで若干異なる。 初期化しない場合 C/C++の int array[10]; はJavaでは int array[] = new int[10]; となる。同様にC/C++の int array[3][3]; はJavaでは int array[][] = new int[3][3]; となる。 初期化
- PlantUML による UML 図の描き方PlantUML はテキスト形式で表現されたシーケンス図やクラス図といった UML (Unified Modeling Language) 図の情報から画像を生成するためのツールです。簡単な使い方をまとめます。 インストール方法の選択 Atom や Eclipse のプラグインをインストールしてエディタから利用する方法、JAR をダウンロードして Java コマンドで実行する方法、Redmine ...
- Akka HTTP サンプルコード (Scala)Akka アクターを用いて実装された汎用 HTTP フレームワークです。Spray の後継です。コアモジュールである akka-http-core は 2016/2/17 に experimental が外れました。akka-http などのいくつかのサブモジュールは 2016/3/1 現在 experimental のままですが、基本的な
- Kestrel の使用例Kestrel は Message Queue (MQ) の実装のひとつです。一般に MQ はアプリケーション間やプロセス間、スレッド間で非同期に通信するために用いられます。メッセージの送信側は MQ に書き込めば受信側の応答を待たずに次の処理に非同期に進むことができます。Kestrel はわずか 2500 行程の Scala で実装されており JVM で動作します。MQ 自体はメモリ上に存在する...