woong's

Android 죽지 않는 서비스 사용하기 본문

Develop/Android

Android 죽지 않는 서비스 사용하기

dlsdnd345 2016. 2. 14. 13:06

Android 죽지 않는 서비스 사용하기


이번에 사용할 기회가 생겨서 android 죽지 않는 서비스 를 구성해 보았습니다.

하루 꼬박 걸려서 구성했네요 .ㅜ 

여러 블로그 살펴보니 많은 시행착오를 겪는 분들이 많으신 것 같습니다.

중간에 2% 부족하게 완성하신 분도 계신것 같습니다. 

저 또하 많은 시행착오를 겪고 마무리를 지었습니다.


이 방법이 사용자에 악용되지 않았으면 합니다.


서비스가 죽지 말아야 할 경우


1. 앱을 종료 했을 때

2. 폰을 재시작 했을 때

3. Task Kill 을 했을 때



이 세 가지 부분을 막아야 이모탈 서비스를 구성 할 수 있습니다.


 1. 앱을 종료 했을 때

    - 기본 서비스를 사용 하면 막을 수 있습니다.

2. 폰을 재시작 했을 때

    - 폰 재시작시 이벤트를 받아서 서비스 재시작

3. Task Kill 을 했을 때

   - startForeground 를 통해서 서비스를 죽지 않게 함.



기본 구성


서비스를 실행 시키고 , 서비스가 종료 될 시 알람 매니져를 통해서 서비스를 예약 합니다. 

이와 같이 하면 서비스를 종료 해도 알람매니져에서 이벤트를 발생 시켜서 서비스가 다시 살아 나게 됩니다.


시행착오 및 문제점


1. 앱을 종료후에 Task kill 을 하면 onDestroy 가 타서 다시 알람 매니져를 등록해서 서비스를 실행

  -onDestroy 가 호출되는 시점이 불분명 합니다. 빠를때도 있고 10분이 걸릴때도 있습니다.

   이렇게 구성이 되면 카카오톡으로 예를 들면 메세지를 앱을 키기 전까지는

   onDestroy가 타기 전까지는 받을수 없게 될것 입니다.


2. startForeground 를 사용합니다.

   -startForeground 를 이용하면 task kill 을 해도 서비스가 죽지 않고 잘 작동되지만 , 아이스크림 버전 이상 부터 

    notification 을 보여 주어야 한다.

   

   아이스크림 이하버전 에서는 Notification 을 안나타나게 할 수 있습니다.

   4.3 버전에서는 항시 명시 될수 있도록 구성이 되어 있습니다.

  구성을 하지 않으면 서비스가 실행이 되지 않거나 에러가 나타 나는 경우 입니다.

  notification을 보여주자니 한구석이 찜찜합니다.



저도 계속 시도 해보고 자료를 찾아보았지만 해결을 하지 못했었습니다. 카카오톡을 살펴보니 카카오톡은

위 조건을 다 만족하면서 notification 이 나타나지 않습니다. 무엇인가 방법은 있는데 찾지 못하고 있다는 생각이

들어 좀더 찾아보다가 해결 방법을 찾게 되었습니다.


해결 방법


startForeground 를 이용하여 Task kill 을 하더라고 서비스를 죽이 않게 구성 합니다.

이제 문제점은 notification을 보여주어야 되는 문제점이 있습니다.


startForeground 를 실행하는 동시에 notification 이 생겨 납니다.

이 notification 을 가져와서 cancel 시키는 방법 입니다.


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
 @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
 
        startForeground(1,new Notification());
 
        NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        Notification notification;
 
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
 
            notification = new Notification.Builder(getApplicationContext())
                    .setContentTitle("")
                    .setContentText("")
                    .build();
 
        }else{
            notification = new Notification(0""System.currentTimeMillis());
            notification.setLatestEventInfo(getApplicationContext(), """"null);
        }
 
        nm.notify(startId, notification);
        nm.cancel(startId);
 
        return super.onStartCommand(intent, flags, startId);
    }
cs


 핵심 코드 입니다.

  startForeground 를 실행하고 NotificationManager 를 통해서 notification 을 등록 합니다. notification 을 

  startForeground 시 등록했던 아이디와 동일하게 등록후 cancel 을 하면  startForeground 를 사용하면서

  notification 을 보여주지 않을 수 있습니다.


전체 구성 코드


mainActivity.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
package com.woong.beaconbackgroundservice;
 
import android.content.Intent;
import android.content.IntentFilter;
import android.support.v7.app.ActionBarActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
 
import com.woong.service.PersistentService;
import com.woong.service.RestartService;
 
 
public class MainActivity extends ActionBarActivity {
 
    private Intent intent;
    private RestartService restartService;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        initData();
 
    }
 
    @Override
    protected void onDestroy() {
        super.onDestroy();
 
        Log.i("MainActivity","onDestroy");
        //브로드 캐스트 해제
        unregisterReceiver(restartService);
    }
 
    /**
     * 데이터 초기화
     */
    private void initData(){
 
        //리스타트 서비스 생성
        restartService = new RestartService();
        intent = new Intent(MainActivity.this, PersistentService.class);
 
 
        IntentFilter intentFilter = new IntentFilter("com.woong.service.PersistentService");
        //브로드 캐스트에 등록
        registerReceiver(restartService,intentFilter);
        // 서비스 시작
        startService(intent);
 
 
    }
}
 
cs


메인 화면 에서는 서비스 등록 , 해제 , 브로드캐스트 등록 해제 관련 코드가 있습니다. 

여기는 특별한 코드는 없습니다.


RestartService.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
 
package com.woong.service;
 
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.util.Log;
 
/**
 * Created by woong on 2015. 1. 28..
 */
public class RestartService extends BroadcastReceiver {
 
    @Override
    public void onReceive(Context context, Intent intent) {
 
        Log.i("000 RestartService" , "RestartService called : " + intent.getAction());
 
        /**
         * 서비스 죽일때 알람으로 다시 서비스 등록
         */
        if(intent.getAction().equals("ACTION.RESTART.PersistentService")){
 
            Log.i("000 RestartService" ,"ACTION.RESTART.PersistentService " );
 
            Intent i = new Intent(context,PersistentService.class);
            context.startService(i);
        }
 
        /**
         * 폰 재시작 할때 서비스 등록
         */
        if(intent.getAction().equals(Intent.ACTION_BOOT_COMPLETED)){
 
            Log.i("RestartService" , "ACTION_BOOT_COMPLETED" );
            Intent i = new Intent(context,PersistentService.class);
            context.startService(i);
 
        }
 
 
    }
}
 
cs


재시작 , 서비스가 죽었을 때 다시 시작해주는 BroadcastReceiver 입니다.


PersistentService.java


1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
 
package com.woong.service;
 
import android.annotation.TargetApi;
import android.app.AlarmManager;
import android.app.Notification;
import android.app.NotificationManager;
import android.app.PendingIntent;
import android.app.Service;
import android.content.Intent;
import android.os.Build;
import android.os.CountDownTimer;
import android.os.IBinder;
import android.os.SystemClock;
import android.util.Log;
 
import com.woong.beaconbackgroundservice.R;
 
/**
 * Created by woong on 2015. 1. 28..
 */
public class PersistentService extends Service {
 
    private static final int MILLISINFUTURE = 1000*1000;
    private static final int COUNT_DOWN_INTERVAL = 1000;
 
    private CountDownTimer countDownTimer;
 
 
    @Override
    public IBinder onBind(Intent intent) {
        return null;
    }
 
    @Override
    public void onCreate() {
        unregisterRestartAlarm();
        super.onCreate();
 
        initData();
    }
 
    @TargetApi(Build.VERSION_CODES.JELLY_BEAN)
    @Override
    public int onStartCommand(Intent intent, int flags, int startId) {
 
        startForeground(1,new Notification());
 
        /**
         * startForeground 를 사용하면 notification 을 보여주어야 하는데 없애기 위한 코드
         */
        NotificationManager nm = (NotificationManager)getSystemService(NOTIFICATION_SERVICE);
        Notification notification;
 
        if(Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB){
 
            notification = new Notification.Builder(getApplicationContext())
                    .setContentTitle("")
                    .setContentText("")
                    .build();
 
        }else{
            notification = new Notification(0""System.currentTimeMillis());
            notification.setLatestEventInfo(getApplicationContext(), """"null);
        }
 
        nm.notify(startId, notification);
        nm.cancel(startId);
 
        return super.onStartCommand(intent, flags, startId);
    }
 
    @Override
    public void onDestroy() {
        super.onDestroy();
 
        Log.i("PersistentService" , "onDestroy" );
        countDownTimer.cancel();
 
        /**
         * 서비스 종료 시 알람 등록을 통해 서비스 재 실행
         */
        registerRestartAlarm();
    }
 
    /**
     * 데이터 초기화
     */
    private void initData(){
 
 
        countDownTimer();
        countDownTimer.start();
    }
 
    public void countDownTimer(){
 
        countDownTimer = new CountDownTimer(MILLISINFUTURE, COUNT_DOWN_INTERVAL) {
            public void onTick(long millisUntilFinished) {
 
                Log.i("PersistentService","onTick");
            }
            public void onFinish() {
 
                Log.i("PersistentService","onFinish");
            }
        };
    }
 
 
    /**
     * 알람 매니져에 서비스 등록
     */
    private void registerRestartAlarm(){
 
        Log.i("000 PersistentService" , "registerRestartAlarm" );
        Intent intent = new Intent(PersistentService.this,RestartService.class);
        intent.setAction("ACTION.RESTART.PersistentService");
        PendingIntent sender = PendingIntent.getBroadcast(PersistentService.this,0,intent,0);
 
        long firstTime = SystemClock.elapsedRealtime();
        firstTime += 1*1000;
 
        AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
 
        /**
         * 알람 등록
         */
        alarmManager.setRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,firstTime,1*1000,sender);
 
    }
 
    /**
     * 알람 매니져에 서비스 해제
     */
    private void unregisterRestartAlarm(){
 
        Log.i("000 PersistentService" , "unregisterRestartAlarm" );
 
        Intent intent = new Intent(PersistentService.this,RestartService.class);
        intent.setAction("ACTION.RESTART.PersistentService");
        PendingIntent sender = PendingIntent.getBroadcast(PersistentService.this,0,intent,0);
 
        AlarmManager alarmManager = (AlarmManager)getSystemService(ALARM_SERVICE);
 
        /**
         * 알람 취소
         */
        alarmManager.cancel(sender);
 
 
 
    }

 


PersistentService 에서 많은 처리를 해주고 있습니다.


서비스 가 죽을 시 알람 등록/해제 , startForeground 실행 등

코드 설명은 주석을 토해 부연설명을 해 놓았습니다.



Manifast.xml


퍼미션 등록


 

1
2
3
4
 
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.BOOT_COMPLETED" />
    <uses-permission android:name="android.permission.ACCESS_BACKGROUND_SERVICE" />



서비스 등록


 

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
<service
            android:name="com.woong.service.PersistentService"
            android:enabled="true"
            android:process=":remote" >
        </service>
 
        <receiver
            android:name="com.woong.service.RestartService"
            android:enabled="true"
            android:exported="false"
            android:label="RestartService"
            android:process=":remote" >
            <intent-filter>
                <action android:name="ACTION.RESTART.PersistentService" />
                <action android:name="android.intent.action.BOOT_COMPLETED" />
            </intent-filter>
        </receiver>
cs

 

이와 같이 구성하면 이모탈 서비스를 구성 할 수 있습니다.

프로젝트 첨부파일에 첨부 하겠습니다.

Comments