동기
AsyncTask 가 Deprecated 되었다는 사실은 이미 대부분의 안드로이드 개발자들이 알고 있는 사실입니다. 물론 저도 그렇고요.
전 회사에서 개발자를 준비하고 있었을 때, 개발 부서 개발자 분들께 소스 코드를 보고 싶다고 말씀드려서 구경했던 프로젝트에서는 AsyncTask 와 HttpUrlConnection 을 통해서 데이터 통신-UI업데이트를 수행하고 있었습니다. 그 때 보았던 코드가 아직도 눈에 선하네요.
AsyncTask 가 Deprecated 되었지만 여전히 채택되고 있었던(어쩌면 지금도 채택되고 있을 수 있는) 이유는, Kotlin 이 아닌 Java 로 프로덕트를 개발하고 있었기 때문입니다.
저는 Kotlin 을 학습하여 회사 밖에서 다양한 개인 프로젝트 및 협업을 수행하였기 때문에, 꽤 오랜 시간 전부터 Coroutines 를 사용해왔습니다.
별 생각 없이 Coroutines 관련 코드를 작성하던 도중, 'AsyncTask 는 왜 Deprecated 되었을까?' 라는 궁금증이 생겼습니다.
AsyncTask
AsyncTask | Android Developers
developer.android.com
AsyncTask 는 Deprecated 되었지만, 관련 문서는 여전히 남아있습니다. 자세한 정보는 위 문서에서 확인하실 수 있습니다.
AsyncTask 는 3개의 타입 파라미터를 갖습니다. 각 타입 파라미터는 순서대로 Params, Progress, Result 에 해당합니다.
백그라운드 작업에 직접적으로 사용될 매개 변수를 Params 로 넘기고, 백그라운드 작업의 진행 상황을 표시하기 위해 사용될만한 타입을 Progress 타입에 넘기면 됩니다. 마지막으로 백그라운드 작업이 끝나고 반환될 타입을 Result 타입으로 할당해주면 되겠습니다.
이미 Deprecated 된 클래스이지만... 사용법은 다음과 같습니다.
먼저, AsyncTask 는 추상 클래스이므로, 이를 구현하는 클래스를 생성해야 합니다.
class MyAsyncTask : AsyncTask<String, Int, JsonObject>() {
override fun doInBackground(vararg params: String?): JsonObject {
val result: JsonObject
...
// do background job (ex.HttpUrlConnection)
return result
}
override fun onProgressUpdate(vararg values: Int?) {
super.onProgressUpdate(*values)
println("progress... $values")
//
}
override fun onPostExecute(result: JsonObject?) {
super.onPostExecute(result)
println(result)
// update UI
}
}
실행도 매우 간단한데, 다음과 같이 execute() 메서드에 지정한 타입의 파라미터만 넘겨주면 끝입니다.
MyAsyncTask().execute("Hello", "World")
백그라운드 작업을 위한 모든 과정이 코드에 그대로 드러나, 굉장히 직관적입니다. 가독성이 정말 높은 코드라고 볼 수 있겠습니다.
Coroutines 와 비슷하게 취소 기능도 존재했습니다. 당연히 취소될 수 있어야겠지요. 참 간단했는데, 작업에 대해 cancel() 메서드만 호출하면 됩니다.
그래서, 왜?
다양한 이유가 있겠지만, 대표적으로는 다음과 같습니다.
1. 재사용이 불가능하다.
정말 큰 단점인데, 재사용이 안 됩니다.
val asyncTask = MyAsyncTask()
asyncTask.execute("test", "test")
asyncTask.execute("test", "test")
asyncTask.execute("test", "test")
위와 같이 짜면 될 것 같지만, 다음과 같은 오류를 뿜으며 크래시가 발생합니다.
java.lang.IllegalStateException: Cannot execute task: the task is already running.
이유는 간단한데, 각 작업이 고유한 상태를 갖기 때문입니다.
@MainThread
public final AsyncTask<Params, Progress, Result> executeOnExecutor(Executor exec,
Params... params) {
if (mStatus != Status.PENDING) {
switch (mStatus) {
case RUNNING:
throw new IllegalStateException("Cannot execute task:"
+ " the task is already running.");
case FINISHED:
throw new IllegalStateException("Cannot execute task:"
+ " the task has already been executed "
+ "(a task can be executed only once)");
}
}
...
return this;
}
그래서 만약 동일한 작업을 다시 수행하고 싶다면, 새로운 AsyncTask 객체를 생성해서 execute() 해야 합니다. 즉, 클래스 객체 자체를 단일 작업으로 간주하고 작업을 수행합니다.
2. 안드로이드 버전에 따라 다른 처리가 필요하다.
크게 도넛, 허니컴, 젤리빈을 기준으로 조금씩 다른 구현이 필요합니다. 이러한 문제점은 개발자가 해당 클래스를 사용할 때에 더 많은 부분을 신경 쓰게 만듭니다.
3. 사용성이 낮다.
Coroutines, Flow, Rx 등에 존재하는 풍부한 기능들이 존재하지 않으므로, 다양한 상황에 대한 구현이 굉장히 까다롭습니다. 즉, 단일 작업을 수행하고 얻은 결과에 대한 데이터 핸들링을 별도로 구현하기 위해 많은 코드를 추가로 작성해야 하며, 이에 대한 통합 역시 쉽지 않습니다.
가령, 두 가지 요청을 통해 얻어 온 값으로 하나의 결과값을 만들어야 하는 경우가 있을 수 있겠지요.
두 가지 요청이 동일한 타입의 매개 변수가 필요하다면 그나마 다행이지만, 그렇지 않다면... 끔찍합니다.
4. 명시적인 종료가 요구된다.
Lifecycle-aware-coroutine-scope 를 통해, 별도의 취소나 종료 처리 없이 Coroutines 를 사용할 수 있습니다. 이와 반대로, AsyncTask 는 Activity 가 종료되어도 작업이 취소되지 않습니다. 이에 대한 명시적인 처리가 필요합니다.
이 외에도 구성 변경 등에 의한 동일 작업의 중첩 및 이에 따른 별도 조치 필요, Memory Leak, Context Leak, 콜 백 누락 등, 해결하기 힘들고 Coroutines, Rx 의 존재로 인해 해결해야 할 마땅한 이유를 찾기마저 힘들어진 문제가 가득한 AsyncTask 는 그렇게 Deprecated 됩니다.
꽤 오랜 시간 사랑 받았던 클래스입니다. 저 또한 그 편리에 대해 잘 알고 있고요. 동기에서의 제 이야기처럼, 아마 여전히 해당 클래스를 사용하는 조직이나 개인이 있으리라 사료됩니다.
사실 주의깊게 사용하고, 필요한 다양한 처리를 모두 수행해준다면 여전히 사용하기 좋은 클래스입니다. 하지만, 현재 널리 사용되고 있는 비동기 솔루션들은 이에 대한 개발자의 수고를 구조적으로 덜어주고 있으므로, 개선에 대해 고려하는 편이 좋을 것 같습니다.
'Android > Tech' 카테고리의 다른 글
Composable 파라미터 주의 사항 (feat.Recomposition) (0) | 2024.01.17 |
---|---|
Service 를 운용하는 환경에서 앱을 완전히 종료하기 (2) | 2024.01.06 |
Ktor 적용기 (feat.Ktor Generics Response Handler) (4) | 2023.11.25 |
Third-Party-Library 없이 비디오 컷 편집 기능 구현하기 (2) | 2023.11.20 |
[Jetpack Compose] Clickable 이 외의 Ripple Effect 없애기 (0) | 2023.11.15 |