Android/App개발2010. 9. 9. 18:57
http://blog.naver.com/huewu/110083320332

안드로이드에서 Service 를 사용하는 방법에는 크게 두 가지가 있습니다.


 첫번째는 startService() / stopService() 를 이용해서, 특정 Service 를 시작 하거나 종료 시키는 것입니다. 두 번째는, bindService() 를 이용해서, Service 의 IBinder 객체를 얻어온 후, 해당 Service 에서 정의된 API 를 호출 하는 방법입니다. 

 Service 에 Bind 한 후, API 를 호출하는데는 두 가지 방법이 있습니다. Local Service 로 구현하는 방법과 Remote Service 로 구현하는 방법입니다. Local Service 의 경우에는 Service 와 Service 를 이용하는 어플리케이션이 항상 동일한 Process 에서 작동하는 경우에 해당합니다. 이 경우 bindService() 의 결과로 바로 해당 Service 에 접근해서 원하는 API 를 호출 할 수 있습니다. 하지만 실재로 Service 가 돌고 있는 Process 가 아닌 별개의 Process 에서 API 를 호출 하고자 할 때는 반드시 IBinder 와 AIDL을 통해  RPC(Remote Procedure Call)이 이루어져야 합니다. 보다 상세한 내용은 안드로이드 개발자 사이트의 AIDL 관련 항목을 참조하시면 좋습니다.

  안드로이드 SDK 중에는Remote Service Binding 관련 예제가 첨부되어 있습니다. 하지만 예제의 경우, Remote Service 와 해당 Service 를 이용하는 어플리케이션 모두 동일한 Package 에 속해 있습니다. 그런데, 제 경우에 Service 를 제작하는 개발자와 Service 를 사용하는 어플리케이션을 개발하는 개발자가 달라, 작업상의 편의를 위해 서로 다른 Package 에 속한 완전히 구분된 형태로 구현할 필요가 생겼습니다. 

 그래서 서로 다른 Package / Process 에서 작동하는 Service 와 어플리케이션을한번 만들어 보려고 했습니다만... 생각보다 수월하지 않더군요. 몇 가지 삽질 끝에, AIDL 에 관련된 클래스를 하나의 패키지로 분리한 후, JAR 라이브러리로 생성하고, Service 와 어플리케이션 모두 동일하게 해당 JAR 를 참조하면 원하는 바대로 정상적으로 동작함을 확인 할 수 있었습니다. 

<AIDL 을 위한 프로젝트를 생성하고, 원하는 Interface 를 정의해 보았습니다.>

 AIDL 패키지를 생성하고 JAR 라이브러리로 뽑는 방법은 다음과 같습니다. 우선 안드로이드 프로젝트를 생성한 후, 원하는 인터페이스를 갖는 AIDL 파일을 추가합니다. 그러면 자동적으로 AIDL 툴이 작동하고, 해당 AIDL 인터페이스에 대응되는 클래스 파일이 생성 추가됩니다. (그림의 경우, printLog() 라는 API 를 갖는 Test.aidl 을 추가 하자, 자동으로 Test.java 파일이 생성되었음을 확인 할 수 있습니다.)

<일반적인 JAR 파일로 Export 하면 됩니다.>

 그 후, 해당 프로젝트에 대하여 JAR File 로 Export 합니다. 이 때, 그림과 같이 우리에게는 AIDL 툴이 자동으로 생성해준 Java  파일내의 내용만이 필요함으로, 해당 파일 외에 다른 것들은 포함할 필요가 없습니다. 특히 AndroidMenifest 파일을 포함하게 되면, 다른 안드로이드 프로젝트에서 해당 JAR 파일을 참조할 수 없으니 (중복된 파일이 존재해서...) 주의해야 합니다. 

<생성한 JAR 파일을 프로젝트에 추가하자.>

 그 다음에는 구현하고자 하는 RemoteService 와 RemoteController 에서 해당 JAR 파일을 참조 라이브러리로 추가하면 작업 완료. 그 이 후의 방법은 일반적인 방법과 동일합니다. RemoteService 는 해당 JAR 를 참조한 후, JAR 내에 정의된 클래스를 Import 한 후, Stub 클래스를 구현하면 되고, RemoteController 에서는 bindService() 이 후에, JAR 에 정의된 대로, Stub 클래스의 asInterface() API 를 호출해서 AIDL 인터페이스 클래스를 생성한 후, 원하는 API 를 호출하면 됩니다.



 <결과가 볼품 없긴 하지만, 정상적으로 동작하고 있다...>

 그림을 유심히 살펴보시면, RemoteService 는 PID 274 인 Process 에서, RemoteController 는 PID 267인 Process 에서 작동 중이고, 로그창에서 확인 할 수 있듯이, PID 267 인 RemoteController 의 Process 가 RemoteService 의 API 를 호출 함을 확인하실 수 있습니다. 보다 상세한 구현 내용은 첨부한 압축파일을 풀어서, 세 개의 프로젝트를 살펴보시면 도움이 될 듯 합니다. (주석이 없어서 조금...애매하긴 하지만...)

 그런데 왜 이런 번거러운 작업이 필요하게 된걸까요?

 서로 다른 Package 를 사용할 때, RemoteController 는 RemoteService 에 바인드 한 후, 한 후, 자신이 Bind 한 Service 가 제공해주는 AIDL 인터페이스에 대한 정보를 알 수가 없습니다. 이 때 생각해 볼 수 있는 방안이 두 가지 있는데... 첫째는 Service 생성에 사용한 AIDL 파일을 그대로 또다른 어플리케이션에 복사해서 또 하나의 AIDL 인터페이스 클래스를 생성하는 방법이고, 두 번째는, 해당 Service 가 구현된 패키지를 참조하는 방법입니다. 두 가지 방법 모두 시도해 보았습니다만, 어플리케이션이 강제로 종료되고 말더군요. 

 우선 AIDL 을 그냥 복사하는 경우. 비록 AIDL 에 정의된 인터페이스와 자동으로 생성된 클래스의 구현 내용은 동일할지 모르지만, RemoteService 에서 사용되는 클래스와는 서로 다른 별도의 클래스가 생성되는 셈입니다. 따라서, bindService 를 통해 전달받은 IBinder 객체를 이용해서 원래 정해진 RemoteService 내에 정의된 클래스 외에 다른 클래스로 변환하고자 하니까 SecurityException 이 발생하게 됩니다.

 두 번째로, 단순히 해당 Service 를 참고만하는 경우에는 컴파일은 정상적으로 수행되긴 하지만, 실제 작동 시에 문제가 발생하게 됩니다. bindService() 를 통해 전달받은 IBinder 객체를 이용해 RemoteService 에 정의된 Proxy 객체를 생성하고자 하지만, RemoteController 패키지내에는 해당 클래스에 관한 정보가 전혀 없기 때문에, Class Definition 을 찾을 수 없다는 오류가 발생하게 됩니다. 

 저 개인적으로는 처음에, AIDL 파일만 있으면 어떤 어플리케이션이던지 손쉽게, 내가 스스로 작성한 Service 에 Bind 한 후, 원하는 API를 호출할 수 있을지 않을까 했습니다만... 세상에 쉬운일은 없더군요. 무슨일이던지 실재로 해보지 않고 마음대로 상상하지 말자...라는 걸 느끼게 해준 삽질이였습니다.
Posted by 삼스