2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 5 years have passed since last update.

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

Last updated at Posted at 2019-09-05

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

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

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

###サンプルコードの概要

  1. API26

  2. 主なファイル
    MainActivity.java
    MyService.java : extends Service

  3. MainActivity.java
    ■ スタートボタン.クリックイベント:
    MyService.classのグローバル変数MyServiceStateを参照し、MyServiceState == false の時にMyService.javaを起動させる

    ■ ストップボタン.クリックイベント:
    stopService()を実行して、MyService.classを停止

  4. 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

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

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

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

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;
    }
}
2
1
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
2
1

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?