AIDL을 이용하여 Remote Service 구현에서 AIDL으로 Remote Service를 구현하였습니다. 이 예제는 동일한 앱에서 정의한 서비스로 바인딩하는 예제였습니다.
만약 다른 앱에서 이 서비스로 바인딩하려면 어떻게 해야 할까요?
다른 앱의 서비스에 바인딩하는 것은, 자신의 앱의 서비스에 바인딩하는 것과 거의 동일합니다. 하지만, 다음과 같은 내용을 추가로 구현해야 합니다.
- 서비스를 제공하는 앱에서 누구나 서비스에 바인딩할 수 있도록 선언 (exported="true")
- 서비스에 바인딩하는 앱이 IRemoteService, IRemoteServiceCallback를 참조할 수 있도록 AIDL 산출물을 라이브러리로 제공
이 글에서 사용되는 앱은 RemoteService 앱과 Client 앱 입니다. RemoteService에 서비스가 구현되어있으며, AIDL을 이용하여 Remote Service 구현에서 소개된 샘플 앱입니다.
RemoteService 앱의 프로젝트에 AIDL 라이브러리와 Client 앱을 추가할 것입니다.
이 글에서 만든 프로젝트는 GitHub - RemoteService and Client App(Kotlin)에서 확인할 수 있습니다.
Client 앱에서 IRemoteService, IRemoteServiceCallback 참조
Client는 ServiceConnection을 통해서 서비스의 Binder 객체를 얻을 수 있지만, 이 바인더 객체를 IRemoteService, IRemoteServiceCallback으로 변환하려면 앱이 이 클래스들을 참조하고 있어야 합니다.
RemoteService에서 구현된 바인더 인터페페이스를 Client에서 참조하려면 다음과 같은 방법이 있습니다.
- RemoteService와 Client 앱에서 동일한 코드의 AIDL파일을 각각 생성하고 컴파일
- AIDL 인터페이스에 대한 Jar 라이브러리를 만들고, 그것을 RemoteService와 Client 앱에서 참조
여기서는 2의 방법으로 구현하는 것을 소개합니다.
Client 앱 및 AIDL 라이브러리 모듈 추가
앞으로 AIDL을 이용하여 Remote Service 구현에서 소개된 RemoteService 프로젝트에 Client 앱과 AIDL 라이브러리를 추가할 것입니다.
먼저 이 프로젝트를 다운로드받고 안드로이드 스튜디오에서 실행해주세요.
[File]->[New]->[New Module]
를 클릭하여 다음과 같이 Client앱을 추가합니다.
AIDL 라이브러리는 Android Library
로 추가합니다.
package name은 RemoteService와 동일한 com.example.remoteservice
로 설정하시면 됩니다.
RemoteService 앱의 Service를 Public으로 만들기
기본적으로 AndroidManifest에 서비스를 정의하면 자신의 앱에서만 실행할 수 있습니다.
다른 앱에서 실행할 수 있도록 만드려면 Service TAG에 exported="true"
를 추가해야 합니다.
com.example.remoteservice
앱의 Manifest에 다음과 같이 service를 정의하면 됩니다.
또한, 다른 앱에서 이 서비스를 찾기 쉽도록 하려고 MY_SERVICE
라는 Action으로 IntentFilter를 등록하였습니다.
<service android:name=".RemoteService" android:process=":remote"
android:exported="true">
<intent-filter>
<action android:name="com.example.remoteservice.MY_SERVICE" />
</intent-filter>
</service>
이제 Client 앱에서 MY_SERVICE
Action으로 이 서비스를 찾을 수 있고, 실행시킬 수도 있습니다.
AIDL Module 구현
aidllib
모듈의 main/aidl/com/example/remoteservice
경로에 RemoteService에서 구현한 AIDL 파일 두개를 이동시킵니다.
RemoteService도 aidllib을 참조할 것이기 때문에 AIDL 파일은 삭제해도 됩니다.
aidllib 모듈을 Build
를 하면 IRemoteService와 IRemoteServiceCallback 클래스가 생성되며 다른 앱에서 참조할 수 있습니다.
RemoteService와 Client에서 aidllib 라이브러리 참조
RemoteService와 Client 앱에서 aidllib을 의존성으로 추가해야 클래스를 참조할 수 있습니다.
[File] -> [Project Structure]
를 누르면 다음과 같은 화면이 보입니다. RemoteService와 Client 앱의 Module dependency
에 aidllib을 추가하면 앱에서 이 라이브러리의 코드를 참조할 수 있습니다.
Client에서 Remote Service에 바인딩
Client 앱의 MainActivty는 RemoteService의 MainActivity와 이름이 같기 때문에, MainActivity2로 변경하였습니다.
다음은 MainActivity2에서 RemoteService 앱의 Service에 바인딩하는 코드입니다.
class MainActivity2 : AppCompatActivity() {
companion object {
const val TAG = "MainActivity2"
}
private var bound = false;
private var iRemoteService: IRemoteService? = null
private var callback = object : IRemoteServiceCallback.Stub() {
override fun onItemAdded(name: String?) {
Log.d(TAG, "onItemAdded: $name")
bound = true
}
override fun onItemRemoved(name: String?) {
Log.d(TAG, "onItemRemoved: $name")
bound = false
}
}
private val connection = object : ServiceConnection {
override fun onServiceConnected(className: ComponentName, service: IBinder) {
Log.d(TAG, "onServiceConnected: $className")
iRemoteService = IRemoteService.Stub.asInterface(service)
iRemoteService!!.addCallback(callback)
}
override fun onServiceDisconnected(className: ComponentName) {
Log.d(TAG, "onServiceDisconnected: $className")
iRemoteService = null
}
}
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
}
override fun onResume() {
super.onResume()
if (!bound) {
val intent = Intent("com.example.remoteservice.MY_SERVICE")
intent.setPackage("com.example.remoteservice")
bindService(intent, connection, Context.BIND_AUTO_CREATE);
}
}
override fun onStop() {
super.onStop()
if (bound) {
iRemoteService!!.removeCallback(callback)
unbindService(connection)
}
}
}
대부분의 코드는 AIDL을 이용하여 Remote Service 구현에서 소개한 MainActivity
의 코드와 동일합니다.
다른 부분은 인텐트 객체를 생성할 때 MY_SERVICE
Action과 setPackage()
로 com.example.remoteservice
패키지를 설정한 것 입니다. RemoteService 앱의 서비스는 MY_SERVICE
Action을 갖고 있는 인텐트 필터로 등록되었기 때문에, 아래와 같은 인텐트로 서비스를 찾을 수 있습니다.
val intent = Intent("com.example.remoteservice.MY_SERVICE")
intent.setPackage("com.example.remoteservice")
bindService(intent, connection, Context.BIND_AUTO_CREATE);
QUERY 퍼미션 설정
Android 11에서는 Package visibility로, Target SDK API가 30 이상인 앱은 기본적으로 다른 앱의 정보를 검색할 수 없습니다.
다른 앱을 찾고 싶다면 AndroidManifest에 <Query>
Syntax로 패키지 이름을 설정하거나 다음과 같이 QUERY_ALL_PACKAGES
퍼미션을 선언해야 합니다.
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" />
Client 앱의 Target API가 30 이상으로 설정되어 있다면 위의 퍼미션을 꼭 선언하셔야 합니다. API 30 미만인 앱은 Package visibility가 적용되지 않기 때문에 퍼미션을 추가하지 않으셔도 됩니다.
실행
Client 앱을 실행해보면 Remote Service에 바인딩되면서 아래와 같이 로그가 출력하게 됩니다.
12-20 14:05:19.987 5754 5754 D MainActivity2: onServiceConnected: ComponentInfo{com.example.remoteservice/com.example.remoteservice.RemoteService}
12-20 14:05:19.989 20138 20156 D RemoteService: Add callback : com.example.remoteservice.IRemoteServiceCallback$Stub$Proxy@882781d
12-20 14:05:24.977 5754 5772 D MainActivity2: onItemAdded: new item
12-20 14:05:24.977 5754 5772 D MainActivity2: onItemRemoved: old item
12-20 14:05:29.980 5754 5772 D MainActivity2: onItemAdded: new item
12-20 14:05:29.984 5754 5772 D MainActivity2: onItemRemoved: old item
로그에서 보이는 PID로 프로세스를 확인하면 Client와 RemoteService 앱이라는 것을 다시 확인할 수 있습니다.
js@js:~/codechacha$ adb shell ps -ef | grep com.example
u0_a156 5754 276 1 14:05:18 ? 00:00:00 com.example.client
u0_a153 20112 276 0 10:54:57 ? 00:00:05 com.example.remoteservice
u0_a153 20138 276 0 10:54:57 ? 00:00:36 com.example.remoteservice:remote
정리
Client 앱에서 RemoteService에 구현된 Service로 바인딩하는 과정을 알아보았습니다. 서비스 구현과 별개로, 두개의 앱에서 IRemoteService, IRemoteServiceCallback 클래스를 사용하기 때문에 라이브러리 모듈로 만들고 함께 참조하도록 하였습니다.
만약 프로젝트가 다른 두개의 앱에서 IRemoteService 등의 바인더 인터페이스에 접근이 필요하다면 AIDL의 산출물을 Jar 파일로 만들어 각각의 앱에 의존성으로 추가되도록 만들어야 합니다.
Related Posts
- Android 14 - 사진/동영상 파일, 일부 접근 권한 소개
- Android - adb push, pull로 파일 복사, 다운로드
- Android 14 - 암시적 인텐트 변경사항 및 문제 해결
- Jetpack Compose - Row와 Column
- Android 13, AOSP 오픈소스 다운로드 및 빌드
- Android 13 - 세분화된 미디어 파일 권한
- Android 13에서 Notification 권한 요청, 알림 띄우기
- Android 13에서 'Access blocked: ComponentInfo' 에러 해결
- 에러 해결: android gradle plugin requires java 11 to run. you are currently using java 1.8.
- 안드로이드 - 코루틴과 Retrofit으로 비동기 통신 예제
- 안드로이드 - 코루틴으로 URL 이미지 불러오기
- Android - 진동, Vibrator, VibrationEffect 예제
- Some problems were found with the configuration of task 에러 수정
- Query method parameters should either be a type that can be converted into a database column or a List
- 우분투에서 Android 12 오픈소스 다운로드 및 빌드
- Android - ViewModel을 생성하는 방법
- Android - Transformations.map(), switchMap() 차이점
- Android - Transformations.distinctUntilChanged() 소개
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - 휴대폰 전화번호 가져오는 방법
- Android 12 - Splash Screens 알아보기
- Android 12 - Incremental Install (Play as you Download) 소개
- Android - adb 명령어로 bugreport 로그 파일 추출
- Android - adb 명령어로 App 데이터 삭제
- Android - adb 명령어로 앱 비활성화, 활성화
- Android - adb 명령어로 특정 패키지의 PID 찾기
- Android - adb 명령어로 퍼미션 Grant 또는 Revoke
- Android - adb 명령어로 apk 설치, 삭제
- Android - adb 명령어로 특정 패키지의 프로세스 종료
- Android - adb 명령어로 screen capture 저장
- Android - adb 명령어로 System 앱 삭제, 설치
- Android - adb 명령어로 settings value 확인, 변경
- Android 12 - IntentFilter의 exported 명시적 선언
- Android - adb 명령어로 공장초기화(Factory reset)
- Android - adb logcat 명령어로 로그 출력