Seize the day

POST : Android Dev Study

Simple AsyncTask like RxJava

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()
}
top

posted at

2019. 12. 6. 18:48


CONTENTS

Seize the day
BLOG main image
김대정의 앱 개발 노트와 사는 이야기
RSS 2.0Tattertools
공지
아카이브
최근 글 최근 댓글
카테고리 태그 구름사이트 링크