51歳からのプログラミング　備忘　Android Seviceを重複起動しない　Serviceの起動確認

「MainActivityのスタートボタンを押すと、Serviceが起動する」というプログラムを作ったら、スタートボタンを押すたびにServiceが複数起動してしまった！。なので重複起動しない方法を備忘なのです。

<font color="#099">ActivityManager.RunningServiceInfo()メソッドを使う方法は非推奨</font>になっているようですし、そもそもActivityManagerの各メソッドは、デバックなどの開発過程で使用するもので、完成版アプリを稼働させるコードには使わないように、というリファレンスでしたので、ActivityManagerのメソッドを使わない方法が健全なのかな？

#挙動確認のサンプルコード

###サンプルコードの概要
1. **API26**

2. **主なファイル**<br>MainActivity.java<br>MyService.java　:　extends Service

3. **MainActivity.java**
■　スタートボタン.クリックイベント：<br>MyService.classのグローバル変数MyServiceStateを参照し、MyServiceState == false　の時にMyService.javaを起動させる<br>
■　ストップボタン.クリックイベント：<br>stopService()を実行して、MyService.classを停止


4. **MyService.java**<br>グローバル変数　 ：public static boolean MyServiceState = false;を設定<br>onCreate()時　　　：MyServiceState = true;に代えます<br>onStartCommand() ：Logを流します。<br>onDestroy()　　　 ：MyServiceState = false;に代えます

■　説明
MyService.classで、グローバルでstaticな変数を宣言します。その変数の値によって、Serviceが起動しているか否かを判定します。以下のコードでは、変数をboolean値にして、falseなら起動していない、trueなら起動している、としてます。ActivityMain.classから、MyService.classの変数の値を参照することで、Serviceの起動、未起動を判定します。

■　挙動の確認
MyService.classのonCreate()内の<font color="#099">MyServiceState = false;</font>にすると、スタートボタンを押すたびに、Logが流れます。<font color="#099">MyServiceState = true;</font>にすると、スタートボタンを何度押しても、Logは一度きりしか流れません（ストップボタンを押すとMyServiceは破棄されるので、再度スタートボタンでLogを１回流せます）

（注意）
<font color="#099">API26以降では、Serviceの起動時に、通知と、5秒以内の<strong>startForeground()</strong>の実行がないとクラッシュします</font>。以下のコードでは、通知設定をすると、コードが長く見づらくなるので、クラッシュ上等で通知処理はしてません。Serviceを重複起動させないという挙動確認のためだけのコードです。API16~29まで対応したクラッシュしないコードを最後（[Logでカウントする常駐アプリのサンプルコード]）に追加しときます。

という与件で以下コードスタートです！
###挙動確認サンプルコード
---
####ActivityMain.java

```java: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
```java: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でカウントする常駐アプリのサンプルコード

<table><tr>
<td>ターゲットSDK</td><td>API29</td>
</tr><tr>
<td>最小SDK</td><td>API16</td>
</tr><tr>
<td>Manifest追加</td><td>uses-permission android:name="android.permission.FOREGROUND_SERVICE"</td>
</tr><tr>
<td>主なファイル</td><td>MainActivity.java<br>MyService.java</td>
</tr><tr>
<td>その他</td><td>通知コンテンツを設定します。通知アイコンはお好みで</td>
</tr></table>

■説明
以下のコードは、さっきのサンプルコードと違って、APIバージョンを16~29で確認してます。通知領域に通知を出して、クラッシュしないようにしておきました。

UI画面でスタートを押すと、Logにカウントメッセージが流れ、ストップボタンでメッセージが止まり、再びスタートでカウントメッセージが流れます。スタートボタンを連続プッシュしても、カウントメッセージは重複しません。

では稚拙ながら（恥）
####MainActivity.java

```java: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

```java: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;
    }
}
```


</tr></td>