본문 바로가기
[ Developer ]/Android

[Android] 안드로이드 서비스 이용한 타이머 Service

by 김현섭. 2016. 8. 7.
336x280(권장), 300x250(권장), 250x250, 200x200 크기의 광고 코드만 넣을 수 있습니다.
Android Service로 타이머 생성해보기

현재까지 실행했던 앱들은 Activity가 꺼지면 앱은 실행되지 않지만 
서비스로 처리를 해주게 되면 백그라운드로 실행을 할 수 있게 된다

예를 들면 음악 앱 같은 경우 앱을 화면에서 내려도 혼자도 진행을 하는 것을 알 수 있다
그러한 기능을 서비스로 처리를 한것이다

Activity가 Service를 실행 시킨다면 Activity가 죽더라도 Service는 실행이 되고 있다
하지만 Service가 정해진 기능이 끝난다면 종료된다

서비스 실습을 위해서 프로젝트를 하나 생성한다
우선은 생성 후 layout을 먼저 정리한다


*activity_main.xml
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
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:gravity="center"
    tools:context="com.ktds.hskim.myservice.MainActivity">
 
    <TextView
        android:id="@+id/tvCounter"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_gravity="center"
        android:gravity="center"
        android:textSize="70dp"
        android:text="0" />
 
    <LinearLayout
        android:orientation="horizontal"
        android:gravity="center"
        android:layout_width="match_parent"
        android:layout_height="wrap_content">
 
        <Button
            android:id="@+id/btnPlay"
            android:text="Play"
            android:textSize="40dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
 
        <Button
            android:id="@+id/btnStop"
            android:text="Stop"
            android:textSize="40dp"
            android:layout_width="wrap_content"
            android:layout_height="wrap_content" />
    </LinearLayout>
 
</LinearLayout>
cs


그런 후 MainActivity에서 텍스트뷰와 버튼 2개를 가져온 후 버튼의 이벤트를 준다




Play 버튼을 클릭 하면 Toast로 1~50까지 1초에 한번 씩 Toast가 뜨게 된다
Stop 버튼을 클릭 하면 중지 되게 된다

Activity와 별개로 띄우는 방식이 Service 방식이다
Thread와 Service 동일하지만 Service를 쓰는 이유는 액티비티가 죽어도 서비스는 실행이 되기 때문이다
이제 서비스를 추가해본다




이름은 MyCounterService라고 생성을 해준다
이전에 실습을 했던 Receiver와 마찬 가지로 Manifest에 등록이 되게 된다
이제 Service에 로직을 작성해 보자

우선은 Thread를 추가를 해준다


* Counter - Runnable
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
    private class Counter implements Runnable {
 
        private int count;
        private Handler handler = new Handler();
 
        @Override
        public void run() {
 
            for ( count = 0; count < 50; count++ ) {
 
                if ( isStop ) {
                    break;
                }
 
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), count + "", Toast.LENGTH_SHORT).show();
                        Log.d("COUNT", count + "");
                    }
                });
 
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
 
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), "서비스 종료", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
cs


Thread 안에서는 UI와 관련된 항목들을 사용할 수 없기 때문에 Handler를 이용해서 Toast와 Log를 감싸주었다
그런 후에 onCreate를 생성해서 Thread를 추가하고 실행시킨다


* onCreate - MyCounterService
1
2
3
4
5
6
7
    @Override
    public void onCreate() {
        super.onCreate();
 
        Thread counter = new Thread(new Counter());
        counter.start();
    }
cs


그런 후 Service를 멈췄을 때 정지 시키기 위해서 onDestroy를 추가해준다


* onDestory
1
2
3
4
5
6
7
8
9
10
    private boolean isStop;
    
    /**
     * Stop Service가 실행될 때 호출
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        isStop = true;
    }
cs


이제 MainActivity에 가서 생성한 Service를 실행시켜주면 된다


* Oncreate - MainActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tvCounter = (TextView) findViewById(R.id.tvCounter);
        btnPlay = (Button) findViewById(R.id.btnPlay);
        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyCounterService.class);
                startService(intent);
            }
        });
        btnStop = (Button) findViewById(R.id.btnStop);
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyCounterService.class);
                stopService(intent);
            }
        });
    }
cs


Play 버튼을 클릭 시 Intent를 생성해서 startActivity로 실행을 시키고 Stop 버튼을 누를 시 stopService를 해주면 된다
이제 실행을 시켜보자


우선은 화면에서 Play 버튼을 누른다




화면이 꺼져도 계속 증가되는 것을 볼 수 있다
그리고 Stop을 누르면 종료가 되는 것도 볼 수 있다





@ Activity와 Service의 값 전달

Activitiy와 Service가 값을 전달하려면 다른 파일이 필요하다




위와 같이 생성을 해준다 이름은 iMyCounterService로 생성을 해준다
생성된 파일을 보면 java에서 만들었지만 aidl이란 폴더로 생성된 것을 볼 수 있다




이제 AIDL에서 변수를 하나 정의해준다


1
2
3
4
5
6
7
8
9
10
// iMyCounterService.aidl
package com.ktds.hskim.myservice;
 
// Declare any non-default types here with import statements
 
interface iMyCounterService {
 
    int getCount();
 
}
cs


그리고 이제 Build에서 Make Project를 해준다




그런 후 MyCounterService.java에서 다음을 추가해준다


* IMyCounterService.Stub
1
2
3
4
5
6
7
8
9
10
11
    /**
     * Service와 Activity가 통신하기 위한 바인더
     * Activity에게 getCount() 메소드를 제공해 Service의 Count 값을 전달
     */
    IMyCounterService.Stub binder = new IMyCounterService.Stub() {
 
        @Override
        public int getCount() throws RemoteException {
            return 0;
        }
    };
cs


바인더를 사용해서는 종료 시 Destroy가 먹히지 않기 때문에 binder를 리턴시켜준다




그리고 나서 count를 접근하기 위해서 Counter 메소드의 변수를 클래스 멤버 변수로 올려준다
바인더에서 리턴을 count를 해주면 된다




MainActivity에서 binder를 가져오려면 바인더를 추가 해줘야한다


* MainActivity
1
2
3
4
5
6
7
8
9
10
11
12
13
14
    private IMyCounterService binder;
 
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 서비스가 가진 binder를 리턴 받음
            binder = IMyCounterService.Stub.asInterface(service);
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
 
        }
    };
cs


이제 Main에서도 Thread를 생성해준다


* GetCOuntThread - MainActivity
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
    private class GetCountThread implements Runnable {
 
        private Handler handler = new Handler();
 
        @Override
        public void run() {
 
            while(running) {
                if ( binder == null ) {
                    continue;
                }
 
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            tvCounter.setText(binder.getCount() + "");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                });
 
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
cs


이제 Main의 onCreate에서 Thread를 추가해준다


* onCreate - MainActivity
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
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tvCounter = (TextView) findViewById(R.id.tvCounter);
        btnPlay = (Button) findViewById(R.id.btnPlay);
        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyCounterService.class);
                //startService(intent);
                bindService(intent, connection, BIND_AUTO_CREATE);
                running = true;
                new  Thread(new GetCountThread()).start();
            }
        });
        btnStop = (Button) findViewById(R.id.btnStop);
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(connection);
                running = false;
            }
        });
    }
cs


btnPlay와 btnStop을 눌렀을 때 각각의 로직에 맞게 작성을 해주면 된다
그리고 나서 GetCountThread에서 run을 실행 시 binder가 Null인지를 체크주면 된다


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
        @Override
        public void run() {
 
            while(running) {
                if ( binder == null ) {
                    continue;
                }
 
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            tvCounter.setText(binder.getCount() + "");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                });
 
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
cs


그런 후에 MyCounterService에 가서 onUnbind를 추가해주면 된다


1
2
3
4
5
    @Override
    public boolean onUnbind(Intent intent) {
        isStop = true;
        return super.onUnbind(intent);
    }
cs


다음과 같이 결과를 볼 수 있다



@ 구동 원리
Activity와 Service의 중개를 위해서는 AIDL을 사용한다
각 각 AIDL을 가지고 getCount()로 중개를 해주는 방식

Activity에서 Service Connection이라고 하는 것을 안드로이드에서 제공을 해주는데 Activity가 뜬느 순간에
Service 자체를 Activity로 연결을 해주게 된다

Service에서 AIDL을 생성한다
연결이 된다면 AIDL 객체를 Activity로 연결을 해주는 방식이다

같은 AIDL Binder를 가지게 되므로 그것을 이용해서 연결을 하는 형태이다

Service에는 count를 증가시키는 Thread를 가지고 있다
Activity는 AIDL을 이용해서 count 값을 받아온다

Activity에서도 받기 위해서 Thread를 이용해서 binder.getCount()를 주기적으로 계속 호출한다


@ 전체적인 소스
* 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
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
import android.content.ComponentName;
import android.content.Intent;
import android.content.ServiceConnection;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.TextView;
 
public class MainActivity extends AppCompatActivity {
 
    private TextView tvCounter;
    private Button btnPlay;
    private Button btnStop;
 
    private IMyCounterService binder;
 
    private ServiceConnection connection = new ServiceConnection() {
        @Override
        public void onServiceConnected(ComponentName name, IBinder service) {
            // 서비스가 가진 binder를 리턴 받음
            binder = IMyCounterService.Stub.asInterface(service);
        }
 
        @Override
        public void onServiceDisconnected(ComponentName name) {
 
        }
    };
 
    private Intent intent;
    private boolean running = true;
 
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
 
        tvCounter = (TextView) findViewById(R.id.tvCounter);
        btnPlay = (Button) findViewById(R.id.btnPlay);
        btnPlay.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                Intent intent = new Intent(MainActivity.this, MyCounterService.class);
                //startService(intent);
                bindService(intent, connection, BIND_AUTO_CREATE);
                running = true;
                new  Thread(new GetCountThread()).start();
            }
        });
        btnStop = (Button) findViewById(R.id.btnStop);
        btnStop.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                unbindService(connection);
                running = false;
            }
        });
    }
 
    private class GetCountThread implements Runnable {
 
        private Handler handler = new Handler();
 
        @Override
        public void run() {
 
            while(running) {
                if ( binder == null ) {
                    continue;
                }
 
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        try {
                            tvCounter.setText(binder.getCount() + "");
                        } catch (RemoteException e) {
                            e.printStackTrace();
                        }
                    }
                });
 
                try {
                    Thread.sleep(500);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
        }
    }
}
 
cs


* MyCounterService.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
import android.app.Service;
import android.content.Intent;
import android.os.Handler;
import android.os.IBinder;
import android.os.RemoteException;
import android.util.Log;
import android.widget.Toast;
 
public class MyCounterService extends Service {
 
    public MyCounterService() {
    }
 
    private int count;
 
    /**
     * Service와 Activity가 통신하기 위한 바인더
     * Activity에게 getCount() 메소드를 제공해 Service의 Count 값을 전달
     */
    IMyCounterService.Stub binder = new IMyCounterService.Stub() {
 
        @Override
        public int getCount() throws RemoteException {
            return count;
        }
    };
 
 
    @Override
    public void onCreate() {
        super.onCreate();
 
        Thread counter = new Thread(new Counter());
        counter.start();
    }
 
    @Override
    public IBinder onBind(Intent intent) {
        return binder;
    }
 
    @Override
    public boolean onUnbind(Intent intent) {
        isStop = true;
        return super.onUnbind(intent);
    }
 
    private boolean isStop;
 
    /**
     * Stop Service가 실행될 때 호출
     */
    @Override
    public void onDestroy() {
        super.onDestroy();
        isStop = true;
    }
 
    private class Counter implements Runnable {
 
 
        private Handler handler = new Handler();
 
        @Override
        public void run() {
 
            for ( count = 0; count < 50; count++ ) {
 
                if ( isStop ) {
                    break;
                }
 
                handler.post(new Runnable() {
                    @Override
                    public void run() {
                        Toast.makeText(getApplicationContext(), count + "", Toast.LENGTH_SHORT).show();
                        Log.d("COUNT", count + "");
                    }
                });
 
                try {
                    Thread.sleep(1000);
                } catch (InterruptedException e) {
                    e.printStackTrace();
                }
            }
 
            handler.post(new Runnable() {
                @Override
                public void run() {
                    Toast.makeText(getApplicationContext(), "서비스 종료", Toast.LENGTH_SHORT).show();
                }
            });
        }
    }
}
''cs