Serviceはバックグラウンドで動作してくれるので別スレッドで実行しているのかと思ってしまいます。バックグラウンドという特性からすると別スレッドでと思うのですが実際はメインスレッドを使っているのですね。
IntentServiceは最初からそれを想定して作られています。またワークキューを使った順次処理を行えます。(API 30から非推奨)
API 29
IntentService
以下、古いバージョンのサポートのために残しておきます
Googleのドキュメントにはこのようにあります。
サービスは、そのホスト プロセスのメインスレッドで実行します。サービスが自身のスレッドを作成することはなく、別のプロセスで実行されることもありません(別の方法を指定しない限り)。 つまり、サービスが CPU を集中的に使ったり、ブロック操作を行ったりするような場合(MP3 の再生やネットワーク作業)は、サービス内に新しいスレッドを作成してその作業を行う必要があります。
Ref: サービス | Android Developers
簡単に別スレッドを使うことができるのがIntentServiceです。
更にワークキューを使って要求されたタスクを一つづつ実行し、
全てが終わると自ら終了してくれます。
別スレッドとワークキュー
IntentServiceは簡単には以下のようになります。
コンストラクタが必要でスレッドの名前を設定します。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 |
public class HelloIntentService extends IntentService { /** * A constructor is required, and must call the super IntentService(String) * constructor with a name for the worker thread. */ public HelloIntentService() { super("HelloIntentService"); } /** * The IntentService calls this method from the default worker thread with * the intent that started the service. When this method returns, IntentService * stops the service, as appropriate. */ @Override protected void onHandleIntent(Intent intent) { try { Thread.sleep(5000); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } } |
別スレッドでの処理はあくまでキューに入れられたIntentの順番
作業が終わってもServiceではstopSelf() や stopService() を呼び出して自身でサービスを停止しないと行けない場合がありますが、IntentServiceでは必要がありませんが、これは楽ですね。
サンプルコード
にある例を元に簡単なIntentServiceを作ってみます。
MainActivity.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 |
package your.package.name; import androidx.appcompat.app.AppCompatActivity;import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; public class MainActivity extends AppCompatActivity { private Intent intent; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // 開始ボタン Button buttonStart = findViewById(R.id.button_start); buttonStart.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { intent = new Intent(MainActivity.this, TestIntentService.class); startService(intent); } }); } } |
IntentServiceの記述
1秒のスリープを入れて10回カウントしたら終了させます。
onCreate(), onStartCommand(), onDestroy() などは特にこのケースでは必要ないのですが
ワーカー スレッドの生存状態を正しく処理できるよう、必ず super を実装しないといけないようです。
TestIntentService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 |
package your.package.name; import android.app.IntentService; import android.content.Intent; import android.util.Log; public class TestIntentService extends IntentService { public TestIntentService() { super("TestIntentService"); Log.d("debug", "TestIntentService"); } @Override public void onCreate() { super.onCreate(); Log.d("debug", "onCreate"); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("debug", "onStartCommand"); return super.onStartCommand(intent,flags,startId); } @Override protected void onHandleIntent(Intent intent) { Log.d("debug", "onHandleIntent"); int count = 10; try { for(int i=0 ; i< count ; i++) { Thread.sleep(1000); Log.d("debug", "sleep: " + String.valueOf(i)); } } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @Override public void onDestroy() { Log.d("debug", "onDestroy"); super.onDestroy(); } } |
Serviceの登録も忘れずに
AndroidManifest.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
<application ... <activity android:name=".MainActivity"> <intent-filter> <action android:name="android.intent.action.MAIN" /> <category android:name="android.intent.category.LAUNCHER" /> </intent-filter> </activity> <service android:name=".TestIntentService" /> </application> </manifest> |
レイアウトとリソースです
activity_main.xml
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 |
<?xml version="1.0" encoding="utf-8"?> <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" android:orientation="vertical" android:gravity="center" android:background="#def" tools:context=".MainActivity"> <Button android:id="@+id/button_start" android:text="@string/start" android:layout_width="wrap_content" android:layout_height="wrap_content" /> </LinearLayout> |
strings.xml
1 2 3 4 |
<resources> <string name="app_name">YourAppName</string> <string name="start">Start</string> </resources> |
IntentServiceのログをみると確かにタスクが終わると終了しているようです。
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
04-17 05:56:46.652 25097-25097/com.example.testintentservice D/debug: TestIntentService 04-17 05:56:46.656 25097-25097/com.example.testintentservice D/debug: onCreate 04-17 05:56:46.662 25097-25097/com.example.testintentservice D/debug: onStartCommand 04-17 05:56:46.665 25097-25233/com.example.testintentservice D/debug: onHandleIntent 04-17 05:56:47.667 25097-25233/com.example.testintentservice D/debug: sleep: 0 04-17 05:56:48.669 25097-25233/com.example.testintentservice D/debug: sleep: 1 04-17 05:56:49.674 25097-25233/com.example.testintentservice D/debug: sleep: 2 04-17 05:56:50.676 25097-25233/com.example.testintentservice D/debug: sleep: 3 04-17 05:56:51.680 25097-25233/com.example.testintentservice D/debug: sleep: 4 04-17 05:56:52.682 25097-25233/com.example.testintentservice D/debug: sleep: 5 04-17 05:56:53.685 25097-25233/com.example.testintentservice D/debug: sleep: 6 04-17 05:56:54.688 25097-25233/com.example.testintentservice D/debug: sleep: 7 04-17 05:56:55.690 25097-25233/com.example.testintentservice D/debug: sleep: 8 04-17 05:56:56.695 25097-25233/com.example.testintentservice D/debug: sleep: 9 04-17 05:56:56.698 25097-25097/com.example.testintentservice D/debug: onDestroy |
Gameの効果音
例えばゲームで効果音を出したいときに、メインスレッドで重い画像処理などをしているケースでは別スレッドでやりたいところです。
IntentServiceを使うとどうでしょうか、SoundPoolを使って効果音の再生を試してみましょう。
SoundPoolの使い方はこちらを参考にして、このコードをIntentServiceに組み込んでみます。
注意点は、MediaPlayerでもそうですが、音声データがダウンロードされてから再生を始めるという点です。
ロードが終わったかどうかを調べるのには、リスナーをセットしてonLoadCompleteから返されるステータスが0の時です。
SoundPool.OnLoadCompleteListener
MainActivity.javaおよびレイアウトなどは同じです。
TestIntentService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 |
package your.package.name; import android.app.IntentService; import android.content.Intent; import android.media.AudioAttributes; import android.media.SoundPool; import android.util.Log; public class TestIntentService extends IntentService { private SoundPool soundPool; private int soundOne; private boolean flg =false; public TestIntentService() { super("TestIntentService"); Log.d("debug", "TestIntentService"); } @Override public void onCreate() { super.onCreate(); Log.d("debug", "onCreate"); AudioAttributes audioAttributes = new AudioAttributes.Builder() .setUsage(AudioAttributes.USAGE_GAME) .setContentType(AudioAttributes.CONTENT_TYPE_SPEECH) .build(); soundPool = new SoundPool.Builder() .setAudioAttributes(audioAttributes) .setMaxStreams(1) .build(); // one.wav をロードしておく soundOne = soundPool.load(this, R.raw.one, 1); // load が終わったか確認する場合 soundPool.setOnLoadCompleteListener(new SoundPool.OnLoadCompleteListener() { @Override public void onLoadComplete(SoundPool soundPool, int sampleId, int status) { // status が0でロードsuccess if(status == 0){ flg = true; } } }); } @Override public int onStartCommand(Intent intent, int flags, int startId) { Log.d("debug", "onStartCommand"); return super.onStartCommand(intent,flags,startId); } @Override protected void onHandleIntent(Intent intent) { Log.d("debug", "onHandleIntent"); int cnt = 0; try { // ロードが終わるまで適当に時間を潰す do { // スリープの時間はロードするファイルサイズで適宜調整 Thread.sleep(10); Log.d("debug", "sleep: " + String.valueOf(cnt)); cnt++; } while(!flg); Log.d("debug", "start sound pool"); // 再生です soundPool.play(soundOne, 1.0f, 1.0f, 0, 0, 1); Log.d("debug", "end sound pool"); } catch (InterruptedException e) { Thread.currentThread().interrupt(); } } @Override public void onDestroy() { Log.d("debug", "onDestroy"); super.onDestroy(); } } |
SoundPoolのR.raw.oneはone.wavをresource\raw 以下に配置したものです。
関連ページ: