[15. 서비스 컴포넌트]
*서비스 : 백그라운드 작업 목적 컴포넌트
[15-1. 서비스 이해]
[서비스]
-서비스 컴포넌트 : 오래 걸리는 작업을 백그라운드에서 처리 가능하게 해주는 컴포넌트
-화면 구현 X. 역시 컴포넌트의 한 종류이므로 시스템에서 생명주기 관리
[서비스 생성. 등록]
-서비스 생성: Service 클래스 상속받은 클래스로 작성
-> 내부에 onBind() 생명주기 함수 재정의 필수
class MyService: Service() { //Service 클래스 상속받아 작성
override fun onBind(intent: Intent) : IBinder? { //반드시 재정의
return null
}
}
-서비스 등록 : 역시 컴포넌트이므로 매니페스트 파일에 <service>태그로 등록.
-> 실행할 인텐트 지정.등록
명시적 인텐트로 실행 | 클래스명만 등록 |
암시적 인텐트로 실행 | <intent-filter> 등록 |
<service
android:name = “.MyService”
android:enabled=“true”
android:exported=“true” ></service>
[서비스 실행]
-서비스를 실행하려면 시스템에 인텐트 전달하여 실행 O
-시스템에 인텐트 전달하여 ‘서비스’ 실행 함수 2가지
① 실행 : startService() 함수 | 바인딩되지 않은 서비스 종료 : stopService() 함수 . 실행 중인 서비스 종료함수 |
1) 내부 앱 서비스 실행
-해당 서비스를 인텐트에 담아서 startService()의 매개변수로 전달함
val intent = Intent(this, MyService::class.java)
startService(intent) //전달
2) 외부 앱 서비스 실행
- setPackage() 함수로 외부 앱의 패키지명 명시 필수
-외부 앱 서비스는 ‘암시적 인텐트’로 실행해야 함
단, 외부 앱이 백그라운드 상태일 경우 서비스 실행불가
val intent = Intent(this, MyService::class.java)
intent.setPackage(“com.example.test_outter”) //외부앱 패키지명 명시
startService(intent) //전달
② bindService() 함수 | 바인딩된 서비스 unbindService() 함수. 실행 중인 서비스 종료 함수 |
<필수 준비>
- ServiceConnection 인터페이스 구현한 객체 준비
- 내부에 인터페이스의 추상메소드 2개 재정의 필수
val connection: ServiceConnection = object : ServiceConnection { //인터페이스 구현 객체 준비
//(1) onServiceConnected() : 서비스 구동시 자동 호출 함수
override fun onServiceConnected (name:ComponentName?, service: IBinder?) {}
//(2) onServiceDisconnected() : 서비스 종료시 자동 호출 함수
override fun onServiceDisconnected (name: ConponentName?) { }
}
1) 내부 앱 서비스 실행
-bindService() 함수 매개변수 3개 -> ( 인텐트 /ServiceConnection 구현 객체/ flags 상수값 전달)
-flags 상수값 대부분 ‘Context.Bind_AUTO_CREATE’ 지정 -> 서비스 실행상태 아니어도 객체 생성하여 실행함
2) 외부 앱 서비스 실행
추가로 setPackage() 함수로 외부앱 패키지명 명시 필수
[서비스 생명주기]
-위의 2가지 실행함수 중 어떤 것 이용했는지에 따라 생명주기도 다름
[15-2. 바인딩 서비스]
*서비스 실행 함수 2개 제공 이유 : 서비스 이용 상황 2가지로 구분 위함
⇒ startService() 함수 사용 : 데이터 상호작용 불필요한 경우 (백그라운드 작업만 수행)
⇒ bindService() 함수 사용 : 데이터 상호작용 필요한 경우 (객체 바인딩)
[IBinder 객체 바인딩]
-bindService() 함수는 서비스를 실행한 곳에도 객체를 전달(바인딩) 하는 함수
-bindService() 함수 실행 -> onBind() 함수 자동 실행됨
-onBind() 함수의 반환타입은 IBinder() 인터페이스의 구현 객체임.
-이 구현객체는 서비스를 bindService()로 실행한 곳에서 onServiceConnected() 함수 매개변수로 받아서 데이터 상호작용 가능
[프로제스 간 통신하는 방법] (2)
① 메신저 바인딩
-API에서 제공하는 Messenger 객체를 바인딩하는 방법
<서비스 코드>
-외부에서 전달해온 데이터를 Message 타입으로 받고, 전달받은 Message의 what값으로 데이터 구분. obj 속성으로 전달받은 데이터 가져옴 -내부에 onBind() 함수 재정의하는데, 이 함수의 반환타입은 Messenger 객체의 binder 속성으로 지정하여 데이터 전달함 |
<내부 앱 연동>
<즉, 액티비티 코드> | bindService() 로 서비스 실행하는 곳
-서비스에서 넘어온 객체 받기 : bindService()로 서비스 실행하여 자동 호출된 onBind() 함수의 반환 객체 (즉, 서비스에서 넘어온 객체)를 onServiceConnected() 의 매개변수로 받고, Messenger() 생성자 매개변수에 지정해줌. -서비스에 데이터 전달하기 : Messenger 의 send() 함수에 전달할 데이터를 Message 객체에 담아서 전달 |
<외부 앱 연동> | bindService()로 서비스 실행하는 곳
-외부 앱에서 서비스를 bindService()로 실행하기 위해서는 해당 외부앱의 매니페스트에 <intent-filter> 선언 필수 -서비스 등록된 매니페스트에는 <package> 태그의 name 속성값으로 연동할 앱 패키지명을 지정 -코드에서 실행할 외부 앱의 패키지명을 setPackage() 에 명시 |
<외부 앱과의 데이터 상호작용>
-Parcelable 타입/ Bundle 타입의 데이터만 주고받을 수 있음
-따라서 상호작용할 데이터를 Bundle에 담고, 이 Bundle 객체를 다시 Message 객체에 담아서 send() 로 보낼 것
② AIDL 기법 사용
-AIDL : 프로세스 간 통신 구현 시 사용 기법
-안드로이드는 기본적으로 하나의 프로세스에서 직접 다르 프로제스 접근 불가능
-따라서 전달할 데이터를 ‘시스템’에 전달하고, 시스템이 다른 프로세스에 전달.
-데이터 종류 많을 경우, 동시에 멀티 스레드 환경에서 실행 가능한 AIDL 이 더 적합
<서비스 제공 앱>
▶확장자 aidl 파일 필요
-확장자만 다를 뿐, 자바 인터페이스, 추상 함수만 정의된 파일
▶서비스 컴포넌트 : aidl 파일 속 추상 함수를 구현한 구체적인 로직 작성 역할
-AIDL 은 바인드 서비스 이용하므로 onBind() 함수에서 서비스를 ‘인텐트’로 실행한 곳에 객체 전달해주어야 함. 이때 객체는 프로세스 간 통신 대행 객체 Stub 객체를 전달
▶외부 앱과의 연동을 위해 매니페스트에 <intent-filter> 추가함
<서비스 이용 외부 앱>
▶AIDL 서비스를 이용하는 앱에서도 똑같은 AIDL 파일 가지고 있어야 함
▶bindService() 함수로 외부 앱의 서비스 실행함
▶bindService()로 호출 시 자동 호출되는 onServiceConnected() 함수의 두 번째 매개변수로 서비스에서 전달한 Stub 객체임.
-AIDL은 프로세스 간 통신 대행을 위해 Stub 객체 사용하므로 Stub 객체로 받은 것.
-Stub 객체 받을 때 AIDL 파일에 선언한 인터페이스 타입으로 받으면 됨
[15-3. 백그라운드 제약]
-‘화면 구현’ 하는 액티비티 컴포넌트 제외.
나머지 컴포넌트들은 모두 백그라운드에서 작업 처리 목적으로 사용됨.
[‘브로드캐스트 리시버’의 백그라운드 제약]
-매니페스트에 <intent-filter>로 등록한 리시버를 암시적 인텐트로 실행 시 -> 실행 X
-코드에서 registerReceiver() 함수로 등록한 리시버는 암시적 인텐트도 잘 실행 O
[‘서비스‘의 백그라운드 제약]
-기본적으로 앱이 ’백그라운드 상태‘일 때 인텐트 전달하면 오류 발생
<서비스 정상 실행되는 포그라운드 상황>
보이는 액티비티가 있는 경우 |
포그라운드 서비스 있는 경우 |
또 다른 포그라운드 앱 연결되어 있는 경우 |
<백그라운드 상황 中. 정상 실행되는 일부 상황>
우선순위 높은 FCM(파이어베이스 클라우드 메시징) 처리하는 경우 |
브로드캐스트 수신하는 경우 |
알림에서 PendingIntent 실행하는 경우 |
VPN 앱이 포그라운드로 승격되기 전 VpnService 시작하는 경우 |
<앱 백그라운드 상황에서 서비스 실행하는 방법>
▶startForegroundService() 함수로 인텐트 시작하기 -단, 이 함수로 실행한 서비스는 얼마 후 오류 발생한 뒤 강제 종료됨 -제공 이유 : 사용자에게 앱 실행 중임을 알림, 즉, 빨리 startForeground() 호출하여 (알림 발생) 앱을 포그라운드 상황으로 만들라는 의미 ▶결국 알림 객체를 사용해야 하므로 매니페스트에 퍼미션 등록 필요 |
[15-4. 잡 스케줄러]
-앱 백그라운드 상황의 작업 제약을 -> ’잡 스케줄러‘ 이용하여 보완 가능
-모든 상황 처리 X
-일부 상황에 한해서 어떤 상황에서 서비스 실행할지 조건 명시하여 보완 처리 O
[잡 스케줄러에 명시 가능한 실행 조건]
네트워크 타입 |
배터리 충전 상태 |
특정 앱 콘텐츠 프로바이더 갱신 |
실행주기 |
최소 지연 시간 |
시스템 재구동 시 현재 조건 유지 여부 |
[잡 스케줄러 3가지 구성요소]
잡 서비스 | 백그라운드 작업 구현 |
잡 인포 | 잡 서비스 실행 조건 지정 |
잡 스케줄러 | 잡 인포를 시스템에 등록. 잡 서비스 등록 시 ‘데이터 전달’ |
▶[잡 서비스]
<잡서비스 생성>
① 매니페스트에 잡 서비스 등록
-<service> 태그로 등록. -android.permission.BIND_JOB_SERVICE 퍼미션 포함 |
② 코드에서 JobService() 상속받은 클래스 작성
-여기에서 생명주기 함수 onCreate() / onStartCommand() / onDestroy() 재정의
-onStartJob() / onStopJob() 재정의 필수
-두 함수 모두 Boolean 타입 반환하는데, 반환값에 따라 다르게 동작함
onStartJob() | 백 그라운드에서 처리할 작업 구현 |
onStopJob() | 작업 비정상 처리된 경우 호출됨 |
▸[onStartJob() 함수]
-false 반환 : 작업 완벽히 종료됨을 의미
-true 반환 : 오랜 시간 살아있는 서비스 의미
▸[onStopJob() 함수]
-> onStartJob() 함수가 true 반환해서 오랜 시간 서비스 살아있는 상황에서
갑자기 잡 스케줄러 실행 조건 변경되거나 cancle() 함수로 취소된 경우에 한해서만 호출됨
-false 반환 : 잡 스케줄러 등록 취소
-true 반환 : 잡 스케줄러 재등록
▶잡 인포
<잡 서비스를 잡스케줄러 이용 -> 시스템에 등록>
▸JobInfo 객체에 잡 서비스 실행 조건을 담음
▸JobInfo.Builder() 생성자의 매개변수에 (등록작업 식별값/등록할 잡 서비스) 순서로 담음
▸이렇게 만든 JobInfo 객체를 JobService의 schedule() 함수로 시스템에 등록
<등록 시 실행 조건 명시 setter() 함수 종류>
setPersisted() | 기기 재부팅해도 작업 등록 유지 여부 설정 |
setPeriodic() | 작업 실행 주기 설정 |
setMinimumLatency() | 작업 실행 지연시간 설정 |
setOverideDeadline() | 작업 최대 실행 시간 설정 |
setRequiredNetworkType() | 네트워크 타입 설정 |
setRequiresBatteryNotLow() | 배터리 낮은 상태 아님을 설정 |
setRequresCharging() | 배터리 충전상태인지 설정 |
▶잡 스케줄러
[⇒ 잡 서비스 등록 시 데이터 전달]
-JobInfo 객체 이용하여 시스템 등록 시, 조건에 만족할 때만 실행됨 -잡 서비스에 데이터 전잘하려면 JobInfo.Builder 의 setExtras() 함수 이용 -setEctras()의 매개변수로 PersistableBundle 타입 객체 지정 -PersistableBundle 객체에 키-값 형태로 잡 서비스에 전달 데이터 담음 |
[⇒ 잡 서비스에서 데이터 가져오기]
-onStartJob() 함수의 매개변수에 접근하여 받음 -매개변수 타입은 JobParameters 인데, 이 타입의 extras 속성에 접근하면 데이터 담겨있음 |
[15-5. MP3 재생 앱 만들기] : 실습
-Ch15_Outer //음악 재생 앱
-Ch15_Service //Outer 앱 조종 역할
|
(1) 메신저 서비스
[MyMessengerService.kt] 파일
class MyMessengerService : Service() { //(1) 메신저 서비스
// 액티비티의 데이터를 전달받는 메신저
lateinit var messenger: Messenger
// 액티비티에 데이터를 전달하는 메신저
lateinit var replyMessenger: Messenger
lateinit var player: MediaPlayer //음악 재생용 객체 선언
//'서비스'의 생명주기 함수 재정의
override fun onCreate() { //시작
super.onCreate()
player = MediaPlayer() //재생 객체 초기화 생성
}
override fun onDestroy() { //종료
super.onDestroy()
player.release()
}
// 액티비티로부터 받은 데이터 메시지 처리할 Handler를 inner 클래스로 생성
inner class IncomingHandler(
context: Context,
private val applicationContext: Context = context.applicationContext
) : Handler(Looper.getMainLooper()) { //Handler() 상속받음
override fun handleMessage(msg: Message) { //외부에서 메시지 데이터 받으면 자동 호출되는 함수 재정의
when (msg.what) { //받은 msg의 what 속성별 처리 분리
10 -> { //액티비티에서 서비스 요청 들어오자마자 보내줄 데이터 처리
replyMessenger = msg.replyTo
if (!player.isPlaying) {
player = MediaPlayer.create(this@MyMessengerService, R.raw.music)
try {
//액티비티로 넘겨줄 데이터 준비
val replyMsg = Message() //Message() 객체
replyMsg.what = 10 //식별값은 10
val replyBundle = Bundle() // Bundle 타입 객체 준비
replyBundle.putInt("duration", player.duration)
replyMsg.obj = replyBundle //번드 객체를 다시 Message 객체의 obj에 담고
replyMessenger.send(replyMsg) //보낼 데이터를 Message 객체에 담아서 messenger의 send()로 전달
// 데이터 모두 넘겨줬으면 이제 음악 재생
player.start()
} catch (e: Exception) {
e.printStackTrace()
}
}
}
20 -> {
// 멈춤 메시지
if (player.isPlaying)
player.stop()
}
else -> super.handleMessage(msg)
}
}
}
override fun onBind(intent: Intent): IBinder {
messenger = Messenger(IncomingHandler(this))
//이 서비스 사용하는 곳에서 데이터 넘기면 처리할 내부의 Handler 지정
return messenger.binder //IBinder 구현 객체를 반환
}
}
(2) AIDL 서비스 작성
interface MyAIDLInterface {
int getMaxDuration();
void start();
void stop();
}
[MyAIDLService.kt]
class MyAIDLService : Service() { //(2) AIDL 이용한 서비스 클래스
lateinit var player: MediaPlayer
//서비스의 생명주기 함수 재정의
override fun onCreate() { //시작
super.onCreate()
player = MediaPlayer()
}
override fun onDestroy() { //종료
player.release()
super.onDestroy()
}
override fun onBind(intent: Intent): IBinder? {
//반환할 객체 = Stub() 객체
return object : MyAIDLInterface.Stub() {
override fun getMaxDuration(): Int { // 외부에서 이 함수 자동호출 된 경우
return if (player.isPlaying) //반환할 데이터 처리
player.duration
else 0
}
override fun start() { //외부에서 이 함수 자동호출 된 경우
if (!player.isPlaying) { //플레이 준비
player = MediaPlayer.create(this@MyAIDLService, R.raw.music)
try {
player.start() //플레이 시작
} catch (e: Exception) {
e.printStackTrace()
}
}
}
override fun stop() { //외부에서 이 함수 호출된 경우
if (player.isPlaying)
player.stop() //중지
}
}
}
}
[참고] : Do It 안드로이드 앱 프로그래밍 with 코틀린 |
'App(앱)_관련 공부 모음 > [교재] Andorid App_Kotlin 기반' 카테고리의 다른 글
17. [저장소에 데이터 보관] (0) | 2022.03.25 |
---|---|
16. [콘텐츠 프로바이더 컴포넌트] (0) | 2022.03.24 |
14. [브로드캐스트 리시버 컴포넌트] (0) | 2022.03.23 |
13. [액티비티 컴포넌트] (0) | 2022.03.18 |
12. [머티리얼 라이브러리] (0) | 2022.03.16 |