본 문서는 Flutter의 Isolate 개념에 대해 기술적 배경과 함께, 실제 애플리케이션 개발 중 발생할 수 있는 문제와 해결 방법을 중심으로 다룹니다. 특히, 권한 요청(permission_handler) 또는 Notification 초기화 과정에서 발생하는 "Unable to detect current Android Activity" 예외 상황을 예시로 삼아, 이를 해결하기 위한 방법을 제시합니다.
1. Isolate의 개념
1.1. 정의
Dart 언어의 가장 큰 특징 중 하나는 Isolate라는 단위로 프로세스를 분할한다는 점입니다. 전통적인 스레드(thread)와 달리, 하나의 Isolate는 고유한 메모리 힙(Heap)을 가지고 완전히 독립적으로 동작합니다. 따라서 여러 Isolate 간에 객체를 직접 공유할 수 없으며, 필요한 경우 메시지 패싱으로만 통신할 수 있습니다.
Key Point: Isolate 간에는 공유 메모리가 없으므로, 동시성 문제(데이터 경합, 락 경쟁 등)가 비교적 단순해지는 반면, 통신 비용이 발생합니다.
1.2. 용도
- UI 및 이벤트 루프 처리(메인 Isolate): Flutter 앱은 기본적으로 메인 Isolate 하나를 사용해 UI 렌더링과 사용자 이벤트를 처리합니다.
- 백그라운드 연산 분산(서브 Isolate): 이미지 변환, JSON 파싱, 대규모 계산 등 CPU 사용이 많은 작업은 별도의 Isolate에서 처리함으로써 UI를 부드럽게 유지할 수 있습니다.
Dart에서 별도의 Isolate를 간단히 활용하기 위해 compute() 함수가 제공됩니다. 이를 통해 함수 실행을 별도의 Isolate로 분산하고, 결과를 받아올 수 있습니다.
2. 별도의 Isolate 생성 예시
2.1. compute() 함수
아래는 간단한 예시로, 대규모 반복 계산을 별도의 Isolate에서 처리하는 코드입니다.
Future<int> intensiveCalculation(int max) async {
int sum = 0;
for (int i = 0; i < max; i++) {
sum += i;
}
return sum;
}
void main() async {
WidgetsFlutterBinding.ensureInitialized();
// 별도의 Isolate를 통해서 intensiveCalculation 실행
final result = await compute(intensiveCalculation, 1000000000);
runApp(MyApp());
}
- compute()는 내부적으로 별도의 Isolate를 생성한 뒤, 함수(intensiveCalculation)와 매개변수(1000000000)를 메시지로 전달합니다.
- 함수가 종료되면 결과(result)를 메인 Isolate로 다시 전달합니다.
- 이 접근을 통해 메인 Isolate에서 긴 루프를 돌려도 UI가 멈추지 않습니다.
2.2. 라이브러리/플러그인의 내부 사용
개발자가 명시적으로 compute()를 사용하지 않아도, 이미지 처리나 파일 압축 등 일부 라이브러리는 성능 향상을 위해 내부적으로 별도의 Isolate를 생성할 수 있습니다. 따라서 디버깅 시 "메인 Isolate" 이외의 "Background Isolate"가 보이는 것은 지극히 정상적인 동작입니다.
3. 실제 문제 사례: 권한 요청 시 Activity 인식 실패
3.1. 문제 상황
다음 예외가 발생하며, 안드로이드 플랫폼에서 권한 요청이나 알림 초기화가 제대로 이뤄지지 않는 사례가 보고될 수 있습니다.
PlatformException(PermissionHandler.PermissionManager, Unable to detect current Android Activity., null, null)
이는 permission_handler나 flutter_local_notifications 등의 플러그인이 안드로이드의 현재 Activity를 찾지 못해 발생하는 오류입니다. Flutter에서는 안드로이드 네이티브와의 상호작용을 위해 "지금 어떤 Activity가 실행 중인지" 알아야 하는데, 아래와 같은 경우에 Activity 정보를 확보하지 못할 수 있습니다.
- 별도의 Isolate에서 권한 요청 메서드를 호출
- 메인 Isolate이 완전히 초기화되기 전에, 너무 이른 시점에서 권한을 요청
- 백그라운드 상태에서 권한 요청이 트리거되어 Activity가 존재하지 않는 상황
3.2. 문제 원인 분석
Flutter 플러그인은 보통 ActivityPluginBinding(또는 레지스트리)을 통해 현재 Activity 레퍼런스를 얻습니다. 그러나 별도의 Isolate나 초기화가 완료되지 않은 시점에서는 이 레퍼런스가 null 상태일 수 있습니다.
(a) 별도의 Isolate에서 요청
별도의 Isolate는 메인 Isolate와 Activity 정보를 공유하지 않으므로, permission_handler가 Activity 레퍼런스를 찾지 못해 예외가 발생합니다.
(b) 조기 호출
runApp() 호출 직후, 아직 FlutterActivity 또는 MainActivity가 UI에 완전히 연결되지 않은 상태에서 권한 요청을 하면 실패할 가능성이 높아집니다.
4. 해결 방안 및 구현 예시
4.1. 메인 Isolate에서 권한 요청 로직 실행
가장 안전한 해결책은 메인 Isolate가 UI를 렌더링할 준비를 마친 뒤에 권한 요청을 수행하는 것입니다. 예를 들어, 아래와 같이 첫 번째 화면(예: HomeScreen)의 initState() 내에서 권한 요청을 처리하되, WidgetsBinding.instance.addPostFrameCallback를 사용해 프레임 렌더링이 완료된 후 실행합니다.
@override
void initState() {
super.initState();
// 위젯 트리가 생성되고 나서 권한 요청을 수행
WidgetsBinding.instance.addPostFrameCallback((_) async {
final permissionStatus = await Permission.notification.request();
if (permissionStatus.isGranted) {
// 알림 초기화 로직 수행
await NotificationManager().initialize();
}
});
}
- 이 방법은 "현재 Activity"가 정상적으로 설정된 상태이므로 permission_handler가 Activity 레퍼런스를 올바르게 인식할 수 있습니다.
4.2. 백그라운드 Isolate 사용 시 메시지 패싱 처리
만약 별도의 Isolate에서 연산 도중 권한이 필요해졌다면, 직접 그 위치에서 권한 요청 메서드를 호출해서는 안 됩니다. 대신 메인 Isolate에 요청을 위임해야 합니다. 다음과 같은 시나리오로 구현할 수 있습니다:
- 백그라운드 Isolate → "권한 요청 필요" 메시지 전송
- 메인 Isolate → 권한 요청 수행 및 결과(승인/거부) 수신
- 메인 Isolate → 결과를 백그라운드 Isolate로 다시 전달
- 백그라운드 Isolate → 결과에 따라 후속 로직 처리
이 접근 방식은 Isolate 간 메시지 채널(포트)을 설정해야 하며, 구현 난이도가 다소 높습니다. 따라서 권장되는 접근은, 가능하다면 UI/권한 관련 작업은 메인 Isolate에서만 수행하고, CPU 집약 작업만 별도의 Isolate로 분리하는 것입니다.
5. 결론
- Isolate는 Dart/Flutter가 제공하는 독립 실행 환경으로, UI 스레드(메인 Isolate)와 작업 스레드(백그라운드 Isolate)를 분리함으로써 앱 성능을 향상시킬 수 있습니다.
- 권한 요청, 카메라/갤러리 접근, 알림 초기화 등 Activity 객체가 필요한 작업은 반드시 메인 Isolate에서 실행해야 합니다.
- 예외가 발생하는 경우는 대부분 Isolate 분리나 초기화 순서(타이밍) 문제이므로, WidgetsBinding.instance.addPostFrameCallback을 통해 프레임 렌더링 이후에 메인 Isolate에서 해당 로직을 실행하는 방법이 안전합니다.
이상으로 Flutter Isolate의 개념과 실제 발생한 문제 상황, 그리고 해결 방안을 살펴보았습니다. 개발 환경 및 플러그인 호환성에 따라 일부 세부 구현이 달라질 수 있으나, 핵심은 "Activity와 직접 상호작용하는 작업은 반드시 메인 Isolate에서"라는 원칙임을 기억하시기 바랍니다.