Android IntentService 구현 원리 및 백그라운드 작업 처리 가이드

백그라운드 작업과 IntentService의 필요성

Android 애플리케이션 개발 시 네트워크 통신, 대용량 파일 처리, 데이터베이스 일괄 업데이트 등 시간이 오래 걸리는 작업은 반드시 메인 스레드가 아닌 백그라운드 스레드에서 수행해야 합니다. 일반적으로 Service 컴포넌트를 생성하고 그 내부에서 Thread를 직접 관리하는 방식을 사용하지만, 이 경우 스레드 동기화, 서비스의 수명 주기(Lifecycle) 수동 종료, 동시성 문제 등을 직접 처리해야 하는 번거로움이 있습니다.

이러한 복잡성을 해결하기 위해 Android 프레임워크는 IntentService를 제공합니다. IntentServiceService를 상속받아 비동기 요청을 순차적으로 처리하도록 설계된 추상 클래스입니다. 개발자는 단순히 startService()를 통해 작업을 요청하기만 하면, 내부적으로 워커 스레드 생성, 작업 큐 관리, 그리고 모든 작업 완료 후 서비스 자동 종료까지 모두 프레임워크가 처리해 줍니다.

IntentService 활용 예제: 데이터 동기화

클라이언트에서 수집한 로그 데이터를 서버로 일괄 전송하는 시나리오를 가정해 보겠습니다. IntentService를 상속하여 LogSyncService를 구현합니다.

1. IntentService 상속 및 작업 로직 구현

public class LogSyncService extends IntentService {

    // 반드시 기본 생성자를 제공해야 하며, super()에 스레드 식별 이름을 전달합니다.
    public LogSyncService() {
        super("LogSyncWorkerThread");
    }

    @Override
    protected void onHandleIntent(@Nullable Intent intent) {
        if (intent != null) {
            String payload = intent.getStringExtra("EXTRA_LOG_PAYLOAD");
            executeSync(payload);
        }
    }

    private void executeSync(String data) {
        Log.d("LogSyncService", "작업 스레드: " + Thread.currentThread().getName());
        Log.d("LogSyncService", "동기화 시작: " + data);
        
        try {
            // 네트워크 전송 등 시간이 소요되는 작업 시뮬레이션
            Thread.sleep(2000);
            Log.d("LogSyncService", "동기화 완료");
        } catch (InterruptedException e) {
            Thread.currentThread().interrupt();
            Log.e("LogSyncService", "동기화 중단", e);
        }
    }
}

onHandleIntent() 메서드 내부의 코드는 자동으로 백그라운드 스레드에서 실행됩니다. 매니페스트 파일에 해당 서비스를 등록하는 것을 잊지 말아야 합니다.

2. UI에서의 서비스 실행

Button btnSync = findViewById(R.id.btn_start_sync);
btnSync.setOnClickListener(v -> {
    Log.d("MainActivity", "호출 스레드: " + Thread.currentThread().getName());
    
    Intent syncIntent = new Intent(MainActivity.this, LogSyncService.class);
    syncIntent.putExtra("EXTRA_LOG_PAYLOAD", "user_action_logs_batch_01");
    startService(syncIntent);
});

위 코드를 실행하면 로그를 통해 startService()는 메인 스레드에서 호출되지만, 실제 onHandleIntent() 내부의 작업은 IntentService[LogSyncWorkerThread]라는 별도의 백그라운드 스레드에서 수행됨을 확인할 수 있습니다.

IntentService 내부 아키텍처 및 소스 코드 분석

IntentService가 어떻게 스레드 관리와 수명 주기 제어를 자동화하는지 소스 코드를 통해 살펴보겠습니다.

1. 핵심 멤버 변수와 내부 Handler

public abstract class IntentService extends Service {
    private volatile Looper mServiceLooper;
    private volatile ServiceHandler mServiceHandler;
    private String mName;
    private boolean mRedelivery;

    private final class ServiceHandler extends Handler {
        public ServiceHandler(Looper looper) {
            super(looper);
        }

        @Override
        public void handleMessage(Message msg) {
            onHandleIntent((Intent)msg.obj);
            stopSelf(msg.arg1);
        }
    }
    // ...
}

핵심은 ServiceHandler라는 내부 클래스입니다. 이 Handler는 메인 스레드의 Looper가 아닌, 별도로 생성된 백그라운드 Looper를 바인딩하여 메시지를 처리합니다. handleMessage()에서는 추상 메서드인 onHandleIntent()를 호출하여 실제 비즈니스 로직을 위임하고, 작업이 끝나면 stopSelf(startId)를 호출하여 서비스를 종료합니다.

2. 서비스 생성 및 Looper 초기화 (onCreate)

@Override
public void onCreate() {
    super.onCreate();
    
    HandlerThread thread = new HandlerThread("IntentService[" + mName + "]");
    thread.start();
    
    mServiceLooper = thread.getLooper();
    mServiceHandler = new ServiceHandler(mServiceLooper);
}

onCreate() 단계에서 HandlerThread를 생성하고 시작합니다. HandlerThread는 자체적으로 Looper를 가지는 백그라운드 스레드입니다. 생성된 Looper를 ServiceHandler에 전달함으로써, 이후 이 Handler로 발송되는 모든 메시지는 메인 스레드가 아닌 이 백그라운드 스레드에서 순차적으로 처리되도록 보장합니다.

3. 작업 큐잉 (onStartCommand 및 onStart)

@Override
public int onStartCommand(@Nullable Intent intent, int flags, int startId) {
    onStart(intent, startId);
    return mRedelivery ? START_REDELIVER_INTENT : START_NOT_STICKY;
}

@Override
public void onStart(@Nullable Intent intent, int startId) {
    Message msg = mServiceHandler.obtainMessage();
    msg.arg1 = startId;
    msg.obj = intent;
    mServiceHandler.sendMessage(msg);
}

startService()가 호출되면 onStartCommand()를 거쳐 onStart()로 진입합니다. 여기서 전달받은 Intent와 시스템이 할당한 startIdMessage 객체에 매핑하여 ServiceHandler의 메시지 큐에 삽입합니다. 여러 번의 startService() 호출은 메시지 큐에 순차적으로 누적되며, Handler는 이를 하나씩 꺼내어 처리합니다.

4. 작업 완료 및 서비스 종료

다시 ServiceHandlerhandleMessage()를 보면, onHandleIntent() 실행이 완료된 직후 stopSelf(msg.arg1)를 호출합니다. stopSelf()startId를 전달하는 것은 매우 중요한데, 이는 "현재 처리한 이 특정 요청(startId)이 시스템이 보낸 가장 마지막 요청일 경우에만 서비스를 종료하라"는 의미입니다. 이를 통해 큐에 아직 처리되지 않은 다른 메시지(작업)가 남아있을 때 서비스가 조기에 종료되는 치명적인 오류를 방지할 수 있습니다.

태그: Android IntentService HandlerThread Service BackgroundTask

7월 11일 00:26에 게시됨