[Android] Service <-> Activity Communication (feat. Bind Service)

2020. 6. 21. 23:51·Android

 

Service는 App의 Background Thread로 동작한다고 소개했다.

 

activity에서 어떤 버튼을 눌러 Service에게 Intent를 매개로 데이터를 넘겨주는 것(Activity -> Service)은 쉬웠다.

그럼 반대로 Service ->Activity를 하기 위해선?

 

 

Service 용도

지난 시간에 배웠지만 복습해보자

UI와 관계없는 어떤 작업이 앱이 실행중이든 실행중이지 않든 (Background) 어떤 작업이 수행되도록 하고싶을 때

ex. music play service

 

 

즉 Service <->Activity 양방향 Communcation 을 할 수 있는 2가지 방법을 소개한다.

 

1. BroadCast System

BroadCastReceiver 를 모른다면 아래 글을 참고하시라

 

[Android] BroadCast Receiver

[개요] Application 차원에서 신호를! 송수신하는 Component system or Application 에서도 Intent Fillter를 통해 메시지 송수신 가능. ※ when to use? 시스템의 화면 꺼짐, 화면 캡쳐, SMS 문자 수신, , 배터리..

m-falcon.tistory.com

그림으로 표현하면 이런 느낌이다.

 

Activity, Service 양쪽에 BroadCastReceiver를 등록하고 Intent Filter를 지정하여 메시지를 주고받는 식이다.

 

 

전체 코드를 업로드하기에는 양이 많고

MainActivity <-> Service 간 통신의 핵심이되는 부문만 살펴보자.

 

<MainActivity.kt>

        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            playListInitialize()
            listViewInitialize()
//            service -> Activity broadCastReceiver 등록
            registerReceiver(receiver, IntentFilter(getString(R.string.service_to_main_intent_filter)))
            val serviceIntent = Intent(this, MusicPlayService::class.java)
//            MusicPlayService가 Background에서 동작
            startService(serviceIntent)
        }
        
        //    MainActivity 입장에서 Service에게 음악정보, 재생 상태를 '받고'싶은 상태
    var receiver : BroadcastReceiver = object : BroadcastReceiver() {
        override fun onReceive(context: Context?, intent: Intent?) {
//            Service send state info to main receiver
            val mode = intent?.getStringExtra("mode")
            when (mode) {
                "start" -> {
                    progressThreadIsAlive = true
//                    음악 플레이 시간을 progress bar의 최대값으로
                    progress_bar.max = intent.getIntExtra("playtime", -1)
                    progress_bar.progress = progress_bar.min
                }
                "stop" -> {
                    progressThreadIsAlive = false
                    progress_bar.progress = progress_bar.min
                }
            }
        }
    }

registerReciever 메소드를 통해 BroadCastReiver를 등록한다.

이때 2nd parameter로 com.example.SERVICE_TO_ACTIVITY 를 넘긴다.

(Service->Activity) 로 오는 IntentFilter를 등록한다는 뜻.

 

<Service.kt>

    var receiver = object : BroadcastReceiver() {
    override fun onReceive(context: Context?, intent: Intent?) {
        val mode = intent?.getStringExtra("mode")
        when (mode) {
            "start" -> {
                song = intent.getStringExtra("songName")
//                raw Resource내에서 songName을 그대로 찾아옴. (확장자 제외)
                val songId = resources.getIdentifier(song, "raw", packageName)
//                기존것을 멈추고 새 노래 Play가능하게 해야함.
                playerInitialize()

//                기존곡(플레이중이던 곡)이 아닐 경우 player 초기화하고 재생성
//                songId는 resource getIdentifier기 때문에 파일 제목과 같아야함.
//                파일명 - 'song'변수 (확장자 mp3제외) 가 같아야함.
                player = MediaPlayer.create(context, songId)
                player!!.start()

                val serviceToMainIntent = Intent(getString(R.string.service_to_main_intent_filter))
                serviceToMainIntent.putExtra("mode", "start")
                serviceToMainIntent.putExtra("playtime", player!!.duration)
                sendBroadcast(serviceToMainIntent)
//                플레이어 종료시 수행됨
                player!!.setOnCompletionListener {
                    val serviceToMainIntent = Intent(getString(R.string.service_to_main_intent_filter))
                    serviceToMainIntent.putExtra("mode", "stop")
                    sendBroadcast(serviceToMainIntent)
                }
            }
            "stop" -> {
//                null이거나 플레이중이면
                playerInitialize()
            }
        }
    }
}

    override fun onCreate() {
        super.onCreate()
        registerReceiver(receiver, IntentFilter(getString(R.string.main_to_service_intent_filter)))
    }

MainActivity와 마찬가지로

BroadCastReceiver를 생성ㅎ고

registerReceiver 메소드로 (com.example.ACTIVITY_TO_SERVICE) IntentFilter를 등록하여 열어둔다

 

이로써 MainActivity <-> Service간 BroadCastReceiver를 매개로 통신이 가능해진다.


2. bound Service and bind a component to Activity (communication Channel)

[BindService]

개요: Client-Server 에서 Server 역할

기능: Application component bind to service, request & reply, IPC

생명주기: App Component binding state // Background에서는 무한히 수행되지는 않는다.

 

필수 메소드 onBind()

  • Client - Service Interaction 을 위해 binding 시점에 호출되는 메소드
  • return : Ibinder object
  • client는 bindService(), unbindService() 로 바인딩&해제

보통 Activity에서 Service Class를 대상으로 bind - unbind

 

 

[MusicPlayerService.kt]

class MusicPlayService : Service() {

    lateinit var song : String
    var player : MediaPlayer? = null
    val binder = MyBinder()

//    Return the communication channel to the service.
//    May return null if clients can not bind to the service
    override fun onBind(intent: Intent): IBinder {
        return binder
    }

    inner class MyBinder : Binder() {
//        Binder 객체는 서비스에 연결시 바인더 객체 정보를 Activity로 전달해줘야함.
//        Activity가 외부에서 호출할 수 있는 함수를 미리 만들어놔야함.

//        -> 외부에서 이 객체(MusicPlayService)를 얻는 메소드 getService()
        fun getService() : MusicPlayService {
            return  this@MusicPlayService
        }
    }
 }

[MainActivity.kt]

//BroadCast Receiver 대신 BindService 이용해보자
// MainActivity는 Client, MusicPlayService(BindService)는 Server 역할
class MainActivity : AppCompatActivity() {
    lateinit var songList : Array<String>
    lateinit var songName: String
    var progressThreadIsAlive = false
    var progressThread : Thread? = null
    lateinit var musicPlayService : MusicPlayService
//    binding state flag
    var isBinding = false


// connectionResult는 
//bindService() 메소드 호출시 2번째 파라미터에 
// Disconnected or Connected 2가지 결과값을 반환받는다.

    var connectionResult = object : ServiceConnection {
        override fun onServiceDisconnected(name: ComponentName?) {
            isBinding = false
        }

        override fun onServiceConnected(name: ComponentName?, service: IBinder?) {
            isBinding = true
//            inner class를 매개로!
            val binder = service as MusicPlayService.MyBinder
            musicPlayService = binder.getService()
        }
    }
        override fun onCreate(savedInstanceState: Bundle?) {
            super.onCreate(savedInstanceState)
            setContentView(R.layout.activity_main)
            playListInitialize()
            listViewInitialize()
            serviceBinding()
        }
            fun serviceBinding() {
            val intent = Intent(this, MusicPlayService::class.java)
//            1st : intent, 2nd : Service Connection (Callback -> result varaible),
//            3rd : Flag Option (auto create -> if connection result is "DISCONNECTED" Auto Retry Connection!"
            bindService(intent, connectionResult, Context.BIND_AUTO_CREATE)

        }
           override fun onDestroy() {
        super.onDestroy()
        if (isBinding) {
            unbindService(connectionResult)
            isBinding = false
        }
    }
}

 

MainActivity에서

serviceBindind( intent, connectionResult, OptionFlag) 호출

-> connectionResult == Connected

Connect성공시 binder (MyBinder Type) 를 service(IBinder Type)으로 리턴받는다.

이 service변수를MyBinder 타입으로 넘겨받는다.

 

Q. service는 대체 어디로부터 넘겨받는 것인지?

A. MusicPlayerService.kt 멤버 onBind 메소드의 "return bind" 에서 넘겨받는다.

 

service변수 (binder) 가 곧 'Communication Channel' 역할을 하는 것이다.

-> val binder = service as MusicPlayService.MyBinder

-> musicPlayService = binder.getService() // 별도의 MusicPlayerService 객체를 얻어오는데 성공.

 

 

☞ BroadCastReceiver 를 이용한 방법은 Activity, Service 양측에 모두 receiver를 등록하고

양방향이다 보니 Intent를 stop,play 메소드마다 모두 생성했어야했는데

bindService를 이용하는 방법은 binder라는 'Channel' 을 이용해서 한번만 binding해두면

두고두고 쓸 수 있어 코드가 더 간결하다.

 

 

1번 , 2번 방법 모두 커뮤니케이션 구현 방식이 다를뿐,  실행 결과는 동일하다.


[Reference]

https://www.brightdevelopers.com/android-tips-activity-service/

 

저작자표시 (새창열림)

'Android' 카테고리의 다른 글

[Android] instance  (0) 2020.07.03
[Android] Service 2편 (Foreground)  (0) 2020.06.22
[Android] Service (feat. Thread)  (0) 2020.06.20
[Android] drawable resize, customizing  (0) 2020.06.20
[Android] BroadCast Receiver  (0) 2020.06.18
'Android' 카테고리의 다른 글
  • [Android] instance
  • [Android] Service 2편 (Foreground)
  • [Android] Service (feat. Thread)
  • [Android] drawable resize, customizing
M_Falcon
M_Falcon
  • M_Falcon
    Falcon
    M_Falcon
  • 전체
    오늘
    어제
    • 분류 전체보기 (430)
      • Web (16)
        • Nodejs (14)
        • Javascript (23)
        • FrontEnd (4)
      • DataBase (39)
        • Fundamental (1)
        • Redis (4)
        • PostgreSQL (10)
        • NoSQL (4)
        • MySQL (9)
        • MSSQL (3)
        • Error (4)
      • Algorithm (79)
        • Algorithm (문제풀이) (56)
        • Algorithm (이론) (23)
      • JVM (65)
        • Spring (13)
        • JPA (5)
        • Kotlin (13)
        • Java (24)
        • Error (7)
      • 기타 (68)
        • Kafka (3)
        • Kubernetes (3)
        • Docker (12)
        • git (19)
        • 잡동사니 (26)
      • 재테크 (11)
        • 세무 (4)
        • 투자 (3)
        • 보험 (0)
      • BlockChain (2)
        • BitCoin (0)
      • C (32)
        • C (10)
        • C++ (17)
        • Error (3)
      • Low Level (8)
        • OS (3)
        • 시스템 보안 (5)
      • 네트워크 (3)
      • LINUX (30)
        • Linux (26)
        • Error (4)
      • 저작권과 스마트폰의 이해 (0)
      • 생각 뭉치 (6)
      • 궁금증 (2)
      • Private (4)
        • 이직 경험 (0)
        • 꿈을 찾아서 (1)
      • Android (21)
        • OS (4)
  • 블로그 메뉴

    • 홈
    • WEB
    • 알고리즘
    • DataBase
    • Linux
    • Mobile
    • C
    • 방명록
  • 링크

    • github
  • 공지사항

  • 인기 글

  • 태그

    C++
    Spring
    linux
    Kotlin
    프로그래머스
    ubuntu
    javascript
    algorithm
    Git
    JPA
    android
    kafka
    Bitcoin
    알고리즘
    database
    docker
    PostgreSQL
    java
    Programmers
    백준
  • hELLO· Designed By정상우.v4.10.3
M_Falcon
[Android] Service <-> Activity Communication (feat. Bind Service)
상단으로

티스토리툴바