「MainActivityのスタートボタンを押すと、Serviceが起動する」というプログラムを作ったら、スタートボタンを押すたびにServiceが複数起動してしまった!。なので重複起動しない方法を備忘なのです。
ActivityManager.RunningServiceInfo()メソッドを使う方法は非推奨になっているようですし、そもそもActivityManagerの各メソッドは、デバックなどの開発過程で使用するもので、完成版アプリを稼働させるコードには使わないように、というリファレンスでしたので、ActivityManagerのメソッドを使わない方法が健全なのかな?
#挙動確認のサンプルコード
###サンプルコードの概要
-
API26
-
主なファイル
MainActivity.java
MyService.java : extends Service -
MainActivity.java
■ スタートボタン.クリックイベント:
MyService.classのグローバル変数MyServiceStateを参照し、MyServiceState == false の時にMyService.javaを起動させる
■ ストップボタン.クリックイベント:
stopService()を実行して、MyService.classを停止 -
MyService.java
グローバル変数 :public static boolean MyServiceState = false;を設定
onCreate()時 :MyServiceState = true;に代えます
onStartCommand() :Logを流します。
onDestroy() :MyServiceState = false;に代えます
■ 説明
MyService.classで、グローバルでstaticな変数を宣言します。その変数の値によって、Serviceが起動しているか否かを判定します。以下のコードでは、変数をboolean値にして、falseなら起動していない、trueなら起動している、としてます。ActivityMain.classから、MyService.classの変数の値を参照することで、Serviceの起動、未起動を判定します。
■ 挙動の確認
MyService.classのonCreate()内のMyServiceState = false;にすると、スタートボタンを押すたびに、Logが流れます。MyServiceState = true;にすると、スタートボタンを何度押しても、Logは一度きりしか流れません(ストップボタンを押すとMyServiceは破棄されるので、再度スタートボタンでLogを1回流せます)
(注意)
API26以降では、Serviceの起動時に、通知と、5秒以内のstartForeground()の実行がないとクラッシュします。以下のコードでは、通知設定をすると、コードが長く見づらくなるので、クラッシュ上等で通知処理はしてません。Serviceを重複起動させないという挙動確認のためだけのコードです。API16~29まで対応したクラッシュしないコードを最後([Logでカウントする常駐アプリのサンプルコード])に追加しときます。
という与件で以下コードスタートです!
###挙動確認サンプルコード
####ActivityMain.java
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_start = findViewById(R.id.btn_start);
Button btn_stop = findViewById(R.id.btn_stop);
final Intent intent = new Intent(this,MyService.class);
btn_start.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
// MyService.classのMyServiceState変数を参照する
// MyServiceState=false ならServiceを起動
if(MyService.MyServiceState == false){
startForegroundService(intent);
}
}
});
btn_stop.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
stopService(intent);
}
});
}
####MyService.java
public class MyService extends Service {
// グローバルでstaticな変数 MyServiceState を初期値 false で宣言
// onCreate()で true を代入することで
// MyServiceStateを参照すればServiceがonCreateされているか否かを判定できる
public static boolean MyServiceState = false;
public MyService() {
}
@Override
public void onCreate(){
super.onCreate();
// グローバルstaticな MyServiceState を true にする
MyServiceState = true;
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
Log.d("Service","Started");
return START_NOT_STICKY;
}
@Override
public void onDestroy(){
super.onDestroy();
MyServiceState = false;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}
#Logでカウントする常駐アプリのサンプルコード
ターゲットSDK | API29 |
最小SDK | API16 |
Manifest追加 | uses-permission android:name="android.permission.FOREGROUND_SERVICE" |
主なファイル | MainActivity.java MyService.java |
その他 | 通知コンテンツを設定します。通知アイコンはお好みで |
■説明
以下のコードは、さっきのサンプルコードと違って、APIバージョンを16~29で確認してます。通知領域に通知を出して、クラッシュしないようにしておきました。
UI画面でスタートを押すと、Logにカウントメッセージが流れ、ストップボタンでメッセージが止まり、再びスタートでカウントメッセージが流れます。スタートボタンを連続プッシュしても、カウントメッセージは重複しません。
では稚拙ながら(恥)
####MainActivity.java
public class MainActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
Button btn_start = findViewById(R.id.btn_start);
Button btn_stop = findViewById(R.id.btn_stop);
final Intent intent = new Intent(this,MyService.class);
// スタートボタン
// MyServiceの変数 state が false なら MyService起動
btn_start.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
if(MyService.state == false){
if(Build.VERSION.SDK_INT >=26){
startForegroundService(intent);
}else{
startService(intent);
}
}
}
});
// ストップボタン
btn_stop.setOnClickListener(new View.OnClickListener(){
@Override
public void onClick(View view) {
stopService(intent);
}
});
}
}
####MyService.java
public class MyService extends Service {
// スレッドの状態を決める変数
// signal true でThread稼働 false で停止
// onCreate で true にして、onDestroy で false にする
private boolean signal;
// MyServiceを起動するか判定する変数
public static boolean state = false;
public MyService() {
}
@Override
public void onCreate(){
super.onCreate();
state = true;
}
@Override
public int onStartCommand(Intent intent,int flags,int startId){
// 通知コンテンツの作成と通知発行
Notification notification;
NotificationCompat.Builder builder = new NotificationCompat.Builder(this,"id");
Intent intentm = new Intent(this,MainActivity.class);
PendingIntent pendingIntent = PendingIntent.getActivity(this,0,intentm,0);
notification = builder.setSmallIcon(R.drawable.notification_icon)
.setContentTitle("title")
.setContentText("text")
.setContentIntent(pendingIntent)
.build();
if(Build.VERSION.SDK_INT >=26){
int importance = NotificationManager.IMPORTANCE_LOW;
NotificationChannel channel = new NotificationChannel("id","name",importance);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
startForeground(1,notification);
}else{
notification = builder.setPriority(NotificationCompat.PRIORITY_LOW).build();
NotificationManagerCompat managerCompat = NotificationManagerCompat.from(this);
managerCompat.notify(1,notification);
}// ここまでが通知発行コード
signal = true;
startThread();
return START_NOT_STICKY;
}
// Logを流し続けるスレッド
public void startThread(){
new Thread(new Runnable(){
@Override
public void run() {
int i=0;
// onDestroy で signal false になるまで稼働
while(signal){
try {
sleep(1000);
Log.d("COUNT", String.valueOf(i));
} catch (InterruptedException e) {
e.printStackTrace();
}
i++;
}
}
}).start();
}
@Override
public void onDestroy(){
super.onDestroy();
// スレッド停止信号を発行
signal = false;
// Service未稼働状態を表現
state = false;
}
@Override
public IBinder onBind(Intent intent) {
return null;
}
}