Hun's Blog

[Android] 안드로이드 4대 컴포넌트 - Service 본문

Android

[Android] 안드로이드 4대 컴포넌트 - Service

jhk-im 2020. 3. 21. 23:52

4대 컴포넌트



- Activity
- Service
- Broadcast Receiver
- Content Provider

각 컴포넌트는 하나의 독립된 형태로 존재하며, 정해진 역할을 수행한다.
컴포넌트 간에 Intent라는 일종의 메시지 객체를 사용하여 상호 통신을한다.
가장 많이 사용하는 컴포넌트는 액티비티이며 나머지 컴포넌트도 매우 중요하므로 어떠한 기능을 담당하는지 파악해두어야 한다.

이미지1 안드로이드 4대 컴포넌트와 인텐트 

 


Service 



- 서비스는 백그라운드에 실행되는 프로세스를 의미한다.
- 화면이 존재하지 않는다.
- 한번 시작 된 서비스는 앱이 종료되도 계속 백그라운드에서 돌아간다.
- 모든 서비스는 Service 클래스를 상속받아 사용한다.
- 네트워크를 통해 데이터를 가져올 수 있다.

Service Lifecycle
onStartComand와 onBind 로 나뉜다.

  이미지4 서비스 생명주기



onStartComand 
백그라운드에서 실행한다. 한번 시작된 서비스는 백그라운드에서 무기한으로 실행될 수 있다. 보통 작업 결과를 호출자에게 반환하지 않기 때문에, 호출자와 상호작용하는 작업에는 적합하지 않다 작업이 끝나면 서비스는 자동으로 소멸한다.
호출 메소드 -> startService
콜백 메소드 -> onStartCommand

백그라운드에서 음악을 재생하고 종료하는 예제

사전작업

1. mp3 파일준비
- Project -> src - > main -> res 안에 raw resource folder 생성
- raw 폴더 안에 mp3 파일 넣기

2. activity_main.xml

android:id="@+id/btn_music_start"

android:id="@+id/btn_music_stop"

 

버튼 생성

3. MainActivity.java
Intent, Button
Intent intent; private Button btn_music_start,btn_music_stop;

 

onCreate

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);
    Log.e("onCreate","Create!");
    intent = new Intent(this, MusicService.class);
    btn_music_start = (Button) findViewById(R.id.btn_music_start);
    btn_music_stop = (Button) findViewById(R.id.btn_music_stop);
    btn_music_start.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            startService(intent);
            Log.i("서비스 테스트""startService()");
        }
    });
    btn_music_stop.setOnClickListener(new View.OnClickListener() {
        @Override
        public void onClick(View v) {
            stopService(intent);
            Log.i("서비스 테스트""stopService()");
        }
    });
}
 
 

 

AndroidManifest.xml
<application></application> 안에 아래 내용 추가
<service android:name="MusicService"> <intent-filter> <action android:name="com.jroomstudio.myapplication"></action> </intent-filter></service>

 

 

service 를 상속받는 클래스
public class MusicService extends Service {}

 

 

onCreate()

1
2
3
4
5
@Override
public void onCreate() {
    Log.i("Service","onCreate");
    super.onCreate();
}
 
 

서비스가 생성될 때 가장 먼저 호출된다.


onStartCommand() 

1
2
3
4
5
6
7
8
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
    Log.i("Service","onStartCommand");
    mp = MediaPlayer.create(this, R.raw.song1);
    mp.setLooping(true);
    mp.start();
    return START_NOT_STICKY;;
}
 
 

액티비티에서 startService() 를 호출하면 호출된다. Intent를 전달받는다.

리턴타입
START_NOT_STICKY : 시스템이 서비스를 강제종료 하면 재생성하지 않는다.
START_STICKY : 시스템이 서비스를 강제종료 하면 재생성한다.
START_REDELIVER_INTENT : 시스템이 서비스를 강제종료하면 재 생성하고 마지막 Intent를 전달한다.

 

onDestroy()

1
2
3
4
5
6
@Override
public void onDestroy() {
    Log.i("Service","onDestroy");
    mp.stop();
    super.onDestroy();
}
 
 

서비스가 소멸될 때 호출된다. stopSelf()가 호출되거나 작업이 끝나면 호출된다.

이미지 5 액티비티 버튼


Music Start 클릭 -> startService -> onCreate - > onStartCommand
Music Stop 클릭 -> stopService -> onDestroy

이미지 6 Service Logcat


음악을 종료하지않고 앱을 종료했을때
START_NOT_STICKY : 음악이 종료된다.
START_STICKY :  음악이 재실행된다.
START_REDELIVER_INTENT : 시간이 조금 흐르고 재실행된다.  왜??? 다음에 알아보자 ;;

 

 

 

onBind 


startService() 메소드 대신 bindService() 메소들 통해 시작되는 서비스이다.

- 클라이언트-서버 와 같이 동작하며 서비스가 서버의 역할을 한다.
- 액티비티에서 서비스에 어떠한 요청을 하고, 서비스로부터 결과를 받을 수 있다.
- 프로세스간 통신에도 사용된다.
- 열결된 액티비티가 사라지면 서비스도 소멸된다.
- 백그라운드에서 무한히 실행되지 않는다.
- 하나의 서비스에 다수의 액티비티 연결이 가능하다.
- 애플리케이션 안의 기능을 외부에 제공하는 경우 많이 사용한다.

구현 및 동작 

호출 메소드 -> bindService
콜백 메소드 -> onBind
구현해야하는 클래스 -> ServiceConnection

https://newland435.tistory.com/7
해당 블로그의 예제를 참고하였다.

* Service에 임의의 수를 반환하는 메소드를 만들고 IBinder를 통해 접근하여 해당 메소드를 실행한다. 여기서 액티비티를 2개 만들고 각각 동일한 Service에 접근하여 메소드를 실행해보고 결과를 살펴보도록 하자.

 

1. LocalService

IBinder mBinder = new LocalBinder();

클라이언트와 상호작용을 할 수 있는 객체이다.
액티비티에서 해당 객체를 활용하여 서비스에 접근한다.

 

1
2
3
4
5
class LocalBinder extends Binder {
    LocalService getService(){
        return LocalService.this;
    }
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

클라이언트가 액세스 하는 서비스 클래스이다.

 

1
2
3
4
@Override
public IBinder onBind(Intent intent) {
    return mBinder;
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

 

- 서비스 바인딩 객체를 생성하기 위해 onBind() 구현
- onBind() 는 IBinder를 반환하고 이 객체가 서비스와 액티비티 사이의 인터페이스 역할액
- 액티비티에서 bindService()를 호출 -> IBinder 반환 -> 인터페이스 통해 송수신

 

1
2
3
int getRan(){
    return  new Random().nextInt();
}
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

임의의 숫자를 반환하는 메소드이다.
IBinder 객체를 통해 접근한 액티비티는 해당 메소드를 실행할 수 있다.

 

 

 

2. MainActivity / SecondActivity

LocalService mLocalService; boolean isService = false;

액티비티에서 접근할 서비스와 서비스가 연결되었는지 구분하기 위한 boolean 을 생성

 

1
2
3
4
5
6
7
8
9
10
11
12
ServiceConnection connection = new ServiceConnection() {
    @Override    public void onServiceConnected(ComponentName name, IBinder service) {
        LocalService.LocalBinder mb = (LocalService.LocalBinder) service;       
        mLocalService = mb.getService();        
        isService = true;    
    }
    
    @Override    public void onServiceDisconnected(ComponentName name) {
        Toast.makeText(getApplicationContext(), "서비스 연결 해제", Toast.LENGTH_SHORT).show();        
        isService = false;    
    }
};
 
 

서비스에 연결하기위해 구현해야하는 ServiceConnection 클래스이다.

onServiceConnected() --> 서비스가 연결될 때 호출된다. 해당 메소드 안에 IBinder를 사용하기 위한 준비하는 로직을 구현한다.

*onServiceDisconnected() --> 서비스가 연결이 끊겼을때 호출된다.

- unbindService()로 접속 종료 라는 말만 믿고 삽질 엄청했다...
unbindService 를 실행하여도 onServiceDisconnected가 호출되지 않았다. 
https://developer.android.com/reference/android/content/ServiceConnection.html
위 링크에 설명이 나와 있는데...
간단히 말하자면 unbindService 가 아닌 앱이 종료되거나 충돌이 났을 때 호출된다는 것이다. unbindService가 두번 연속 호출되면  illegalargumentexception: Service not registered 에러가 발생한다.  해당 Exception은 잘못된 인자 전달 시 발생한다고 한다. 음... 일단은 넘어가고 나중에 알아보자;; 일단 위 예제는 boolean 과 if를 통해서 해당 문제를 막고 있다.  

 

1
2
3
4
Button btn_start = findViewById(R.id.button_start);
Button btn_stop = findViewById(R.id.button_stop);
Button btn_data = findViewById(R.id.button_data);
Button btn_move = findViewById(R.id.button_move);
http://colorscripter.com/info#e" target="_blank" style="text-decoration:none;color:white">cs

액티비티에 버튼을 생성한다.

 

 

btn_start -> bindService

1
2
3
4
5
6
7
btn_start.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent intent = new Intent(MainActivity.this, LocalService.class);
        bindService(intent,connection,Context.BIND_AUTO_CREATE);
    }
});
 
 

intent 를 통해 서비스를 실행하고
bindService() 를 통해 서비스와 연결한다.

 

 

 

btn_stop -> unbindService

1
2
3
4
5
6
7
8
9
10
11
12
btn_stop.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(isService){
            Toast.makeText(getApplicationContext(), "unbindService", Toast.LENGTH_SHORT).show();
            unbindService(connection);
            isService = false;
        } else {
            Toast.makeText(getApplicationContext(), "isService - False", Toast.LENGTH_SHORT).show();
        }
    }
});
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

unbindServie() 로 연결을 종료한다.

 

 

btn_data -> getRan() 실행

1
2
3
4
5
6
7
8
9
10
11
btn_data.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        if(!isService){
            Toast.makeText(getApplicationContext(), "isService - False", Toast.LENGTH_SHORT).show();
            return;
        }
        int num = mLocalService.getRan();
        Toast.makeText(getApplicationContext(), "getData -> " + num, Toast.LENGTH_SHORT).show();
    }
});
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

 

btn_move -> 액티비티 이동

1
2
3
4
5
6
7
btn_move.setOnClickListener(new View.OnClickListener() {
    @Override
    public void onClick(View v) {
        Intent secondIntent = new Intent(MainActivity.this,SecondActivity.class);
        startActivity(secondIntent);
    }
});
http://colorscripter.com/info#e" target="_blank" style="color:#4f4f4ftext-decoration:none">Colored by Color Scripter
 

 

 

onDestroy --> unbindService

1
2
3
4
5
6
7
8
9
@Override
protected void onDestroy() {
    super.onDestroy();
    Log.e("onDestroy","Destroy!");
    if(isService){
        unbindService(connection);
        isService = false;
    }
}
 
 

 

 

 

실행결과

 


1. DATA CONFIRM -> 연결되지 않아 getData 실행 실패
2. SERVICE START -> DATA CONFIRM  -> getData 를 실행한다.
3. MOVE ACTIVITY -> Second Acttivity 이동


4. DATA CONFIRM -> 연결되지 않아 getData 실행 실패
5. SERVICE START -> DATA CONFIRM  -> getData 를 실행한다.

6. 뒤로가기 눌렀을때 -> Main Activity 이동 -> DATA CONFIRM  -> getData 를 실행한다.
7. MOVE ACTIVITY -> Main Activity 이동  -> DATA CONFIRM -> getData 실행 실패




정리 - 

서비스는 두가지로 나뉜다 . 
onStartComand 와 onBind 

onStartComand는 백그라운드에서 실행한다. 멜론에서 음악을틀고 화면(액티비티)이 onPause 되도 음악은 여전히 실행된다. 그것을 실현할 수 있는것이 서비스 - onStartComand이다. 액티비티에서 인텐트를 통해 서비스를 실행하고 startService() 로 서비스를 실행한다. 서비스에서 onStartCommand가 실행이 되고 이곳에 mp3 실행하는 것을 구현하면 앱이 종료되기 전까지 액티비티 화면이 내려가거나 홈으로 이동해도 음악이 실행된다.  생명주기는 간단하다 onCreate -> onStartCommand -> onDestory 이다.  앱 종료시 서비스를 재실행을 하거나 완전히 종료하거나 하는 등의 처리를 할 수 있다. 

onBind는 서비스-액티비티가 서버-클라이언트의 형태로 이루어지는 것이다. IBinder 객체가 활용되고 액티비티는 이 객체를 통해 서비스에 접근하여 서비스에 기재되어있는 어떠한 기능을 사용할 수 있게 된다. 다수의 액티비티가 연결 가능하며 프로세스간의 통신에도 사용된다.  액티비티에서 bindService로 서비스를 호출하면 서비스에서는 IBinder 객체를 반환하고 반환받은 객체로 서비스에 접근하게된다.  
onBind의 가장 기본적인 활용도는 *각각의 컴포넌트가 동일한 프로세스를 실행할 수 있다는 것이다. 모든 컴포넌트는 IBinder를 반환받아 서비스에 접근할 수 있기 때문이다. 




참고
https://developer.android.com/guide/components/bound-services?hl=ko
https://developer.android.com/reference/android/app/Service.html#LocalServiceSample
https://brunch.co.kr/@mystoryg/80
https://developer.android.com/reference/android/app/Activity
https://juyoung-1008.tistory.com/34
https://coding-restaurant.tistory.com/98
https://designthing.net/the-basic-components-in-an-android-app/

 

Service  |  Android 개발자  |  Android Developers

From class android.content.ContextWrapper void attachBaseContext(Context base) Set the base context for this ContextWrapper. boolean bindIsolatedService(Intent service, int flags, String instanceName, Executor executor, ServiceConnection conn) Variation of

developer.android.com

 

The basic components in an android app

Today we continue to learn an overview of how to organize an application in Android, with this overview, we'll know an app in Android is held by which compositi

designthing.net