본문 바로가기

Android + Kotlin

[Android Kotlin] TransactionTooLargeException 해결 방법

반응형

크래시틱스를 보던중에 TransactionTooLargeException 이 발생한 이슈가 올라와서 수정한 내용을 공유합니다.

 

 

TransactionTooLargeException 넌 누구냐?!!

 

 

 

안드로이드 레퍼런스 사이트에는 아래와 같이 나와있습니다.

 

The Binder transaction buffer has a limited fixed size, currently 1MB, which is shared by all transactions in progress for the process. Consequently this exception can be thrown when there are many transactions in progress even when most of the individual transactions are of moderate size.

 

Activity 간 Intent를 통해 데이터 전달시에 버퍼가 1MB라서 에러가 나는 것이라는 내용입니다.

(구글링을 해보니, 500KB 라고 하는 것도 있네요. 어찌되었건 대용랑 데이터는 전달할 수가 없다는 내용으로 판단됩니다.)

 

그럼, 해결책은??

 

The key to avoiding TransactionTooLargeException is to keep all transactions relatively small. Try to minimize the amount of memory needed to create a Parcel for the arguments and the return value of the remote procedure call. Avoid transferring huge arrays of strings or large bitmaps. If possible, try to break up big requests into smaller pieces.

 

"작게 쪼개서 보내라." - 할수 있다면 벌써했지~!!

 

 

그래서, 큰 사이즈의 데이터를 전달할수 있는 기능을 만들기로 하였습니다.

 


 

 

개발에 앞서, 전제조건으로 기존 Intent에 데이터를 추가하는 방법(putExtra)과 동일하거나 유사한 인터페이스 형태를 목표로 하였습니다.

위의 조건을 충족하기에는 Key, value를 사용하는 map을 사용하기로 했습니다.

 

프로젝트에 utils/ActivityHolder.kt 파일을 생성하고, map 에 put / get 을 할수 있는 코드를 구현하였습니다.

모든 데이터 타입을 저장하기위해서 Any 타입으로 설정합니다.

 

object ActivityHolder {
    private val map: HashMap<String, Any> = hashMapOf()

    fun putExtra(key: String, data: Any): String {
        map[key] = data
        return key
    }

    fun getExtra(key: String): Any? {
        return map[key]
    }
}

 

그리고, 전제조건 충족을 위해 Intent Extension으로 PutExtra와 유사한 함수를 만들어 줍니다.

 

fun Intent.putLargeExtra(key: String, value: Any?) {
    value?.let {
        putExtra(key, ActivityHolder.putExtra(key, it))
    }
}

inline fun <reified T : Any> Intent.getLargeExtra(key: String): T? {
    return ActivityHolder.getExtra(key) as T?
}

 

putLargeExtra()는 key와 value를 받아서, map에는 key 값으로 value를 저장하고, Intent에는 map에 저장한 key를 저장하도록 하였습니다.

getLargeExtra()는 inline reified 를 사용하여 리턴받는 변수의 타입에 맞춰 형변환이 되어 반환이 되도록 하였습니다.

 


 

 

위의 코드를 사용하여 대용량의 데이터를 전달해 보도록 하겠습니다.

 

개발이 잘 되었는지를 확인하기위해 TransactionTooLargeException 이 발생하는 코드를 구현하였습니다.

 

Intent(this@MainActivity, IntentLargeExtraActivity::class.java).also {
    // ByteArray 사이즈가 커서 TransactionTooLargeException이 발생함.
    it.putExtra("intent.extra.large.data", ByteArray(1000000))
    startActivity(it)
}

 

위의 코드에서 ByteArray(1000000) 생성하여 전달하면 앱이 죽습니다. - 재현 성공!!

 

 

이제 개발한 코드를 이용하여 전달해 보도록 하겠습니다.

 

 

1. Intent를 받는쪽의 화면(IntentLargeExtraActivity.kt)에 Intent 생성 코드를 추가합니다.

(그냥 Intent를 사용해도 되겠지만, 아래와 같이 구현시 공동작업이나 유지보수시에 화면에서 어떤 데이터를 사용하는지를 한눈에 알수 있다는 장점이 있습니다.)

 

companion object {
    private const val INTENT_EXTRA_KEY_NORMAL_DATA = "intent.extra.normal.data"
    private const val INTENT_EXTRA_KEY_LARGE_DATA = "intent.extra.large.data"

    fun createIntent(
        context: Context,
        normalData: String? = null,
        largeData: ByteArray? = null
    ) = Intent(context, IntentLargeExtraActivity::class.java).apply {
            putExtra(INTENT_EXTRA_KEY_NORMAL_DATA, normalData)
            putLargeExtra(INTENT_EXTRA_KEY_LARGE_DATA, largeData)
        }
}

 

putLargeExtra() 함수의 인터페이스가 Intent의 putExtra()와 동일합니다. 이질감이 없네요.ㅎ

 

 

2. 위의 createIntent() 함수를 이용하여 호출 화면쪽 구현을 합니다.

   일반 텍스트(String)와 위에서 exception이 발생했던 ByteArray(1000000) 을 전달하도록 합니다.

 

IntentLargeExtraActivity.createIntent(
    this@MainActivity,
    "알반적인 텍스트",
    ByteArray(1000000)
).also {
    startActivity(it)
}

 

실행하게 되면, 일단 텍스트는 intent에 저장이되고, ByteArray는 ActivityHolder의 map에 저장이 됩니다.

 

 

3. intent 수신쪽 화면에 데이터를 가져오는 코드는 아래와 같이 구현합니다.

 

val normalData: String? = intent.getStringExtra(INTENT_EXTRA_KEY_NORMAL_DATA)
val largeData: ByteArray? = intent.getLargeExtra(INTENT_EXTRA_KEY_LARGE_DATA)

 

getLargeExtra() 호출시 largeData의 데이터형(ByteArray)를 참고하여 형변환이 되어 반환이 되기때문에, 기존 intent 에서 getExtra() 와 동일한 인터페이스를 사용하도록 구현되었습니다.

 

 

위의 코드와 동일한 기능을 하면서,  null 체크를 하려면 아래와 같이 사용하시면 됩니다.

 

intent.getLargeExtra<ByteArray>(INTENT_EXTRA_KEY_LARGE_DATA)?.let {
    println("largeData=${it.contentToString()}")
}

 


 

 

실행한 결과 로그입니다.

 

 

정상적으로 데이터가 잘 전달되었음을 확인하였습니다.

 


 

 

전체 소스 코드는 아래 Github 링크를 확인해 주세요.

 

 

GitHub - rcbuilders/RemindSampleApp: https://heeeju4lov.tistory.com/ 블로그에서 Android + Kotlin 강좌에서 사용함.

https://heeeju4lov.tistory.com/ 블로그에서 Android + Kotlin 강좌에서 사용함. - GitHub - rcbuilders/RemindSampleApp: https://heeeju4lov.tistory.com/ 블로그에서 Android + Kotlin 강좌에서 사용함.

github.com

 

반응형