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 앱이라는 것을 다시 확인할 수 있습니다.
mjs@mjs:~/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 - 진동, Vibrator, VibrationEffect 예제
- Android - TabLayout 구현 방법 (+ ViewPager2)
- Android - PackageManager로 Package 정보 가져오기
- Android - ACTION_BOOT_COMPLETED 이벤트 받기
- Android - FusedLocationProviderClient으로 위치 정보 얻기
- Android - GPS, Network 위치 정보 얻기 (LocationManager)
- Android - Foreground Service 실행
- Android - 시간, 날짜 변경 이벤트 받기
- Android - currentTimeMillis(), elapsedRealtime(), uptimeMillis()
- Android - PowerManager WakeLock
- Android - 파일 입출력 예제 (Read, Write, 내부, 외부 저장소)
- Android - Screen On/Off 이벤트 수신, 상태 확인
- Android - 다른 앱의 Service에 바인딩
- Android - Handler vs Executor
- Android - Darkmode 활성화하는 방법
- Android - hasSystemFeature(), 지원되는 Feature 확인
- Android - 앱 권한 확인(Permission check)
- Android - 설치된 앱 리스트 가져오기
- Android App Shortcuts 구현
- Android - ContentProvider 구현 및 예제
- Android - AIDL을 이용하여 Remote Service 구현
- Android - Uri, Scheme, SSP(Scheme Specific Part) 정리
- Android - 앱 설치, 삭제 이벤트 받기 (BroadcastReceiver 인텐트 받기)
- Android - SharedPreferences로 간단한 데이터 저장 방법
- Android - AlarmManager로 알람을 등록하는 방법
- Android - Quick Settings에 Custom Tile 추가하는 방법 (kotlin)
- Android - Broadcast Receiver 등록 및 이벤트 수신 방법
- Android - 앱 권한 요청 (kotlin)
- Android - 네트워크(WIFI) 연결 상태 확인 및 변경 감지
- Mockito - static, final method를 mocking하는 방법
- Andriod - 커스텀 퍼미션을 정의하는 방법
- Robolectric으로 Unit Test 작성하기
- Android Mockito로 Unit 테스트 코드 작성하기 (kotlin)
- Android - Handler 사용 방법
- Android - IntentService 사용 방법
- Android - JobIntentService 사용 방법