Loading [MathJax]/extensions/tex2jax.js

2020年7月23日木曜日

Android Foreground Serviceのメモ

やりたいこと
ボタンを押すと、フォアグラウンドサービスを起動するやり方を記録しておきます。

環境
OS : Windwos10
Android Studio 4.0
言語 : java

参考にしたサイト
■Foreground Serviceの基本
https://qiita.com/naoi/items/03e76d10948fe0d45597
おおむねこちらのページを参考にしてます。

■公式情報
https://developer.android.com/guide/components/services?hl=ja
公式情報も参考にしました。

背景
※ここは読み飛ばしていただいて結構です。※
充電ケーブルが接続されたかどうかを検知して、何か処理するというアプリを検討中なのですが、通常のアプリだと裏側にいると勝手にOSによって終了させられてしまいます。

Androidの公式ページでこのように記載されてます。
「システムは RAM を解放する必要がある場合にプロセスを強制終了します。システムが特定のプロセスを強制終了する可能性は、...」
https://developer.android.com/guide/components/activities/activity-lifecycle?hl=ja

また、Android7まではマニフェストに「イベントを検知するよ。」という一文を書いておくと、アプリのプロセスがいなくても、イベントが発生すると再起動してくれたのですが、Android8以降はアプリのプロセスがいないと、イベントがひろえなくなってしまいました。
(イベントを検知する仕組みの詳細はこちらを参考にしてください。)

じゃあどうするかというと、アプリをServiceとして裏側で生かしておいて、常にイベントを検知できるようすればよいということになります。

それで終わりかというと、Android8以降ではまたルール改正が発生しており、Serviceをバックグラウンド(通知とかなにもださずに)で動作させていると、強制終了させられるというルールが追加されました。
詳しくはこちらを参考にしてください。


ルール改正がはげしい...


じゃあどうするかというと、その答えがForegroundServiceとなります。

ということで、前置きが長くなりましたがForegroundServiceの勉強をしたので、メモしておきます。
ざっくり言うと
ざっくりいうと以下の手順となります。
・serviceクラス作成
   この中で自分がForegroundであることを知らせるための処理をごちゃごちゃいれる。
・通知用のアイコン準備 ・Manifestにserviceクラスを登録
・MainActivityからserviceを起動

それでは、細かく書いていきます。

Step1.準備
1.Android StudioでEmpty Activityでプロジェクトを作成する。
Name : MyApp
Package name : test.test ← 適当につけました。
Minimum SDK : Api 16 (android 4.1) ←デフォルトのまま

2.ボタンを1個配置して、OnClickListenerを作る。
後でこのボタンを押すとServiceが起動するようにします。
大事なところはまだですが、ここまではこんな感じのコードになります。
  1. package test.test;  
  2.   
  3. import androidx.annotation.NonNull;  
  4. import androidx.appcompat.app.AppCompatActivity;  
  5.   
  6. import android.content.Intent;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.Toast;  
  11.   
  12. public class MainActivity extends AppCompatActivity {  
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.   
  18.         // ボタンのイベント  
  19.         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {  
  20.             @Override  
  21.             public void onClick(View view) {  
  22.   
  23.                 // サービスを呼ぶ  
  24.             }  
  25.         });  
  26.     }  
  27. }  
Step2.通知用のアイコン作成
serviceクラスから通知(notification)を行う必要があるのですが、通知用の小さいアイコンが必須になるので、事前に作っておきます。

(1)app - res - drawable - 右クリック - New - Image Assetを選択

(2)下図の①のところをNotification Iconsに設定、
    ②のところに任意の名称を設定(ここではic_service_notificationに設定しました)、
    ③でiconの形状を設定(ここではデフォルトのままにしました。)します。

  (3)設定したらNext - Finish
Step3.serviceクラス作成
  1. package test.test;  
  2.   
  3. import android.app.NotificationChannel;  
  4. import android.app.NotificationManager;  
  5. import android.app.PendingIntent;  
  6. import android.app.Service;  
  7. import android.content.Intent;  
  8. import android.os.Build;  
  9. import android.os.IBinder;  
  10.   
  11. import androidx.core.app.NotificationCompat;  
  12.   
  13.   
  14. //(1) Serviceの派生クラスを作成  
  15. public class MyService  extends Service {  
  16.   
  17.     //(2) 必須のメソッド。 バインドしない場合はnullを返す。  
  18.     @Override  
  19.     public IBinder onBind(Intent intent){  
  20.         return null;  
  21.     }  
  22.   
  23.     //(3.3)の一部  startforegroudnに渡すIDを定義する。 0以外の数字 。  
  24.     private static final int ONGOING_NOTIFICATION_ID = 1;  
  25.   
  26.     //(3) 呼び出し側でstartService()かstartForegroundService() をすると呼び出されます。  
  27.     @Override  
  28.     public int onStartCommand(Intent intent,  
  29.                               int flags,  
  30.                               int startId) {  
  31.   
  32.         //(3.1)  チャンネルIDの登録  
  33.         String channelID = "MY_CHANNEL_ID"// 通知チャンネル用のID。適当な名前を定義   
  34.         createNotificationChannel(channelID);  
  35.   
  36.         // 通知をタップしたときにアプリを呼び出す準備  
  37.         Intent notificationIntent = new Intent(this, MainActivity.class);  
  38.         intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK);  
  39.         PendingIntent pendingIntent =  PendingIntent.getActivity(this0, notificationIntent, 0);  
  40.   
  41.         // 通知の準備  
  42.         NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelID)  
  43.                 .setContentTitle("通知のタイトル")  
  44.                 .setContentText("通知の内容")  
  45.                 .setSmallIcon(R.drawable.ic_service_notification)  
  46.                 .setContentIntent(pendingIntent)  
  47.                 .setPriority(NotificationCompat.PRIORITY_DEFAULT);  
  48.   
  49.         // (3.3) このAPIを呼ぶと、フォアグラウンドサービスとなる。  
  50.         startForeground(ONGOING_NOTIFICATION_ID, builder.build());  
  51.   
  52.         // (3.4) 戻り値を指定。  
  53.         return START_STICKY;  
  54.     }  
  55.   
  56.   
  57.     // 通知のチャンネルを作成する。この関数はほぼもってきたそのまま。  
  58.     // https://developer.android.com/training/notify-user/build-notification?hl=ja  
  59.     private void createNotificationChannel(String channelID) {  
  60.         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {  
  61.   
  62.             //アプリ - 通知 に表示される情報  
  63.             CharSequence name = "サービス起動中の通知"// なんでもOK。  
  64.             String description = "サービス起動中の通知は、XXXのための通知です。"// なんでもOK。  
  65.   
  66.             // 通知のレベルとか名称とか設定  
  67.             int importance = NotificationManager.IMPORTANCE_DEFAULT;  
  68.             NotificationChannel channel = new NotificationChannel(channelID, name, importance);  
  69.             channel.setDescription(description);  
  70.   
  71.             // 通知の登録  
  72.             NotificationManager notificationManager = getSystemService(NotificationManager.class);  
  73.             notificationManager.createNotificationChannel(channel);  
  74.         }  
  75.     }  
  76. }  
上記コードの説明
(1)serviceクラスを継承したクラスを作成します。
(2)onBind()を実装。必須のメソッドとなります。
  bindしない場合はreturn nullでOKです。
(3)onStartCommand()を実装。
  親アプリがstartService()をするとこちらが呼ばれます。
(3.1) createNotificationChannelをして、通知チャンネルを作成。Android8以降で必要。
    設定 - 通知で使用される機能になります。
(3.2) 通知の準備
フォアグラウンドサービスにするには、通知を表示して、生きていることをユーザーに知らせる必要があるため、必須となります。 その準備として、通知のタイトルや通知のアイコンなどを準備してます。

 (3.3) startForeground()を実行
このAPIを呼び出すことでフォアグラウンドサービスとなります。第一引数のidには0以外 の値を設定する必要があります。ここでは1を指定しました。

(3.4) 戻り値を設定( 3種類あるので用途に合わせて設定 )

このコードでは文字列は直値を記載してますが、実際はstrings.xmlに定義して、使用したほうが良いと思います。

Step4.manifest
(1) applicationの中に、<service android:name=".MyService">を追加します。
(2) application>の外に、<uses-permission android:name="android.permission.FOREGROUND_SERVICE">を追加します。

(2)のほうは、Android9以降では必須となります。

以下xml全体となります。うまくコードをはれなかったので、図を張ってます。

Step5.MainActivityからサービスを呼ぶ
  1. package test.test;  
  2.   
  3. import androidx.annotation.NonNull;  
  4. import androidx.appcompat.app.AppCompatActivity;  
  5.   
  6. import android.content.Intent;  
  7. import android.os.Bundle;  
  8. import android.view.View;  
  9. import android.widget.Button;  
  10. import android.widget.Toast;  
  11.   
  12. public class MainActivity extends AppCompatActivity {  
  13.     @Override  
  14.     protected void onCreate(Bundle savedInstanceState) {  
  15.         super.onCreate(savedInstanceState);  
  16.         setContentView(R.layout.activity_main);  
  17.   
  18.         // ボタンのイベント  
  19.         findViewById(R.id.button).setOnClickListener(new View.OnClickListener() {  
  20.             @Override  
  21.             public void onClick(View view) {  
  22.   
  23.                 // サービスを呼ぶ  
  24.                 Intent serviceIntent = new Intent(MainActivity.this, MyService.class);  
  25.                 startService(serviceIntent);  
  26.             }  
  27.         });  
  28.     }  
  29. }  
Step5.テスト
emulatorの5.1と10で動くことだけ確認しました。
0 件のコメント:
コメントを投稿