やりたいこと
ボタンを押すと、フォアグラウンドサービスを起動するやり方を記録しておきます。
環境
OS : Windwos10Android 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が起動するようにします。
大事なところはまだですが、ここまではこんな感じのコードになります。
package test.test; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ボタンのイベント findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // サービスを呼ぶ } }); } }
Step2.通知用のアイコン作成
serviceクラスから通知(notification)を行う必要があるのですが、通知用の小さいアイコンが必須になるので、事前に作っておきます。(1)app - res - drawable - 右クリック - New - Image Assetを選択
(2)下図の①のところをNotification Iconsに設定、
②のところに任意の名称を設定(ここではic_service_notificationに設定しました)、
③でiconの形状を設定(ここではデフォルトのままにしました。)します。
(3)設定したらNext - Finish
Step3.serviceクラス作成
package test.test; import android.app.NotificationChannel; import android.app.NotificationManager; import android.app.PendingIntent; import android.app.Service; import android.content.Intent; import android.os.Build; import android.os.IBinder; import androidx.core.app.NotificationCompat; //(1) Serviceの派生クラスを作成 public class MyService extends Service { //(2) 必須のメソッド。 バインドしない場合はnullを返す。 @Override public IBinder onBind(Intent intent){ return null; } //(3.3)の一部 startforegroudnに渡すIDを定義する。 0以外の数字 。 private static final int ONGOING_NOTIFICATION_ID = 1; //(3) 呼び出し側でstartService()かstartForegroundService() をすると呼び出されます。 @Override public int onStartCommand(Intent intent, int flags, int startId) { //(3.1) チャンネルIDの登録 String channelID = "MY_CHANNEL_ID"; // 通知チャンネル用のID。適当な名前を定義 createNotificationChannel(channelID); // 通知をタップしたときにアプリを呼び出す準備 Intent notificationIntent = new Intent(this, MainActivity.class); intent.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_CLEAR_TASK); PendingIntent pendingIntent = PendingIntent.getActivity(this, 0, notificationIntent, 0); // 通知の準備 NotificationCompat.Builder builder = new NotificationCompat.Builder(this, channelID) .setContentTitle("通知のタイトル") .setContentText("通知の内容") .setSmallIcon(R.drawable.ic_service_notification) .setContentIntent(pendingIntent) .setPriority(NotificationCompat.PRIORITY_DEFAULT); // (3.3) このAPIを呼ぶと、フォアグラウンドサービスとなる。 startForeground(ONGOING_NOTIFICATION_ID, builder.build()); // (3.4) 戻り値を指定。 return START_STICKY; } // 通知のチャンネルを作成する。この関数はほぼもってきたそのまま。 // https://developer.android.com/training/notify-user/build-notification?hl=ja private void createNotificationChannel(String channelID) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { //アプリ - 通知 に表示される情報 CharSequence name = "サービス起動中の通知"; // なんでもOK。 String description = "サービス起動中の通知は、XXXのための通知です。"; // なんでもOK。 // 通知のレベルとか名称とか設定 int importance = NotificationManager.IMPORTANCE_DEFAULT; NotificationChannel channel = new NotificationChannel(channelID, name, importance); channel.setDescription(description); // 通知の登録 NotificationManager notificationManager = getSystemService(NotificationManager.class); notificationManager.createNotificationChannel(channel); } } }
(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に定義して、使用したほうが良いと思います。
(2) application>の外に、<uses-permission android:name="android.permission.FOREGROUND_SERVICE">を追加します。
(2)のほうは、Android9以降では必須となります。
以下xml全体となります。うまくコードをはれなかったので、図を張ってます。
この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からサービスを呼ぶ
package test.test; import androidx.annotation.NonNull; import androidx.appcompat.app.AppCompatActivity; import android.content.Intent; import android.os.Bundle; import android.view.View; import android.widget.Button; import android.widget.Toast; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); // ボタンのイベント findViewById(R.id.button).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // サービスを呼ぶ Intent serviceIntent = new Intent(MainActivity.this, MyService.class); startService(serviceIntent); } }); } }
0 件のコメント:
コメントを投稿