AsyncTask를 가볍게 감싼 Tasks#onBackground를 만들었다.
사용할 때 depth를 줄이고, 읽기 쉬운 코드를 작성하기 위한 Wrapper코드다.
코드 구현 주안점
- 최소한으로 적은 코드를 작성한다.
- RxJava 스타일의 빌더 패턴을 이용한다.(익명 AsyncTask를 구현할 필요가 없고, depth도 줄고, 가독성도 좋다)
- onBackgroud가 nonnull일 경우 onSuccess도 nonnull을 받는다.
- onComplete를 만들어서 성공이든 실패든 상관없이 공통적으로 수행할 코드를 넣는다. onComplete는 예외발생시 결과가 null이라서 어쩔수 없이 nullable을 받는다.
- onBackgroud에서 에러가 발생할 시 onError가 미구현이면 예외를 던진다.
- 실행중에 에러가 발생하더라도 Task를 호출한 쓰레드의 StackTrace를 확인할 수 있다.
- 잘 쓰이지는 않겠지만 onCancel도 지원한다.
- 공용 쓰레드풀을 이용한다.
import android.app.Activity
import android.os.AsyncTask
import android.util.Log
import androidx.fragment.app.Fragment
import java.lang.ref.WeakReference
object AsyncTasks {
fun <T> onBackground(onBackground: () -> T): Task<T> = Task(onBackground)
class Task<T>(private val onBackground: () -> T) : AsyncTask<Void, Void, T>() {
private val callerData = RuntimeException("<<<< This is caller stacktrace >>>>")
private var onSuccess: ((T) -> Unit)? = null
private var onError: ((Throwable) -> Unit)? = null
private var onCancel: (() -> Unit)? = null
private var onComplete: ((T?) -> Unit)? = null
private var throwable: Throwable? = null
private var activityReference: WeakReference<Activity>? = null
private var fragmentReference: WeakReference<Fragment>? = null
fun onSuccess(onSuccess: (T) -> Unit) = apply { this.onSuccess = onSuccess }
fun onError(onError: (Throwable) -> Unit) = apply { this.onError = onError }
fun onCancel(onCancel: () -> Unit) = apply { this.onCancel = onCancel }
fun onComplete(onComplete: (T?) -> Unit) = apply { this.onComplete = onComplete }
fun onAvailable(activity: Activity) = apply { activityReference = WeakReference(activity) }
fun onAvailable(fragment: Fragment) = apply { fragmentReference = WeakReference(fragment) }
private fun isAvailable(): Boolean {
if (activityReference != null) {
val activity = activityReference?.get()
if (activity == null || activity.isFinishing || activity.isDestroyed) return false
}
if (fragmentReference != null) {
val fragment = fragmentReference?.get()
if (fragment == null || fragment.isDetached || fragment.activity == null) return false
}
return true
}
override fun doInBackground(vararg params: Void): T? {
return try {
onBackground()
} catch (throwable: Throwable) {
Log.w("AsyncTasks", Log.getStackTraceString(throwable), callerData)
this.throwable = throwable
null
}
}
override fun onPostExecute(result: T) {
if (!isAvailable()) return
if (throwable != null) {
onError?.invoke(throwable!!)
?: onComplete ?: throw RuntimeException(
"AsyncTasks#onError or onComplete not implemented",
callerData
)
} else {
onSuccess?.invoke(result)
}
onComplete?.invoke(result)
}
override fun onCancelled() {
if (!isAvailable()) return
onCancel?.invoke()
onComplete?.invoke(null)
}
fun execute(
onSuccess: ((T) -> Unit)? = null,
onError: ((Throwable) -> Unit)? = null,
onComplete: ((T?) -> Unit)? = null
): AsyncTask<Void, Void, T> {
if (onSuccess != null) {
if (this.onSuccess != null) throw RuntimeException(
"AsyncTasks#onSuccess already implemented",
callerData
)
this.onSuccess = onSuccess
}
if (onError != null) {
if (this.onError != null) throw RuntimeException(
"AsyncTasks#onError already implemented",
callerData
)
this.onError = onError
}
if (onComplete != null) {
if (this.onComplete != null) throw RuntimeException(
"AsyncTasks#onComplete already implemented",
callerData
)
this.onComplete = onComplete
}
return super.executeOnExecutor(CommonExecutors.getCachedThreadPool())
}
fun execute(): AsyncTask<Void, Void, T> {
return super.executeOnExecutor(CommonExecutors.getCachedThreadPool())
}
}
}
사용 예제는
fun test() {
AsyncTasks.onBackground {
"abcd"
}.execute()
AsyncTasks.onBackground {
"abcd"
}.execute { result: String? ->
}
AsyncTasks.onBackground {
"abcd"
}.onCancel {
}.execute(onSuccess = { result: String ->
}, onError = { ex: Throwable ->
}, onComplete = { result: String? ->
})
AsyncTasks.onBackground {
"abcd"
}.onSuccess { result: String ->
}.onError { ex: Throwable ->
}.onCancel {
}.onComplete { result: String? ->
}.execute()
}