본문 바로가기

안드로이드

예전부터 궁금했던 parcelize 로 액티비티간 객체 교환에 대한 것을 파헤쳐보자 + as 로 캐스팅하기

예전부터 난 객체를 액티비티간에 옮기고 싶었다 물론 이제 다른 아이디어들도 새록새록 떠오르지만 

어찌됐건간에 이러한 데이터 이동에 관한 방법을 고찰해본것을 적어놓아야겠다.

 

우선 객체를  putExtra로 옮기는 방법은 Parcelize,Serializable,Parcelable가 있다.

앞전꺼는 옛날기술이고 뭐 좋은지도 모르니 직접써보고 좋다는 Parcelize 사용법을 살펴보겠다

https://yuuj.tistory.com/211

 

[Android][Kotlin] Intent로 Class 값 넘기기 (Parcelize, Serializable, Parcelable 차이)

액티비티간 Intent로 값 넘기기 (클래스) FirstActivity에서 SecondActivity로 값을 넘겨주고 싶을때 intent를 사용해서 전달이 가능한데, Int형이나 Array , String등의 자료형을 넘겨주려면 이 게시글을 참고

yuuj.tistory.com

전반적인 다른것까지 설명들은 위 블로그를 참고하자.

 

최초에 난 솝커톤을 하면서 액티비티간에 객체를 담은 배열을 주고 받고싶었다.

 

일단 객체는 직렬화 찾아보면 바로 나올테니 그거는 일단 가능한데 객체를 담은 배열은 어떻게 보내지? 라고 생각했는데 결과적으로 방법은 객체를 직렬화시켜주고 그 Parcelable된 객체를 arraylist로 만들어소 주고받을수있는

intent.putParcelableArrayListExtra("intentData",goData)
intent.getParcelableArrayListExtra<SampleData>("intentData")

 이 둘을 사용해서 객체를 직렬화시켜놓은것을 리스트로 만들어서 액티비티 간에 주고받았다.

 

 

이제 자세히 설명해 보겠다.


직렬화란 무엇인가?

직렬화란?

  • 직렬화는 메모리 내에 존재하는 정보를 보다 쉽게 전송 및 전달하기 위해 byte 코드 형태로 나열하는 것이다. 여기서 메모리 내에 존재하는 정보는 즉 객체를 말한다.

그래서 객체를 직렬화 시켜서 주고받게 된다.

 

코틀린에서 직렬화와 intent를 통해 액티비티에서 다른 액티비티로 값을 전달하는 방법은 세가지가 있다. 

 

이중 Serializable은 자바의 표준 인터페이스로 이렇게 데이터 클래스의 뒤에 : Serializable만 달아주면 되기 때문에 굉장히 간편하다는 장점이 있다.  

그러나 속도가 굉장히 느리다는 단점이 있다. 따라서 프로젝트 내에서 Serializable을 많이 사용하면 할수록 성능은 저하된다. 

 

Parcelable은 자바 기반이 아닌 Android SDK의 인터페이스로, Serialize보다 빠르며 안드로이드에서 사용하길 권장한다.

다만, 필요한 코드를 모두 개발자가 구현해줘야 하기 때문에 그러나 간단한 값을 넘겨주는것 치고는 너무 작성해야 할 코드가 많고, 보일러플레이트 코드가 많이 생긴다.

 

Parcelize 의 장점은

@Parcelize

kotlin-parcelize 플러그인은 Parcelable 구현을 자동으로 해준다.

새로운 클래스를 생성도 필요 없고 @Parcelize 어노테이션을 추가하는 것만으로 직접 Parcelable 관련 코드를 작성 한 것과 같이 동작한다. 컴파일타임에 바이트 코드 변조를 하기 때문에 추가되는 메서드 및 런타임시 오버헤드 비용도 발생하지 않는다. 그리고 무엇보다 이 플러그인은 구글과 JetBrains가 협업하여 만든 플러그인이기 때문에 다른 3rd-party 라이브러리와는 다르게 추후 계속 유지보수 될 것이라 기대한다. 

Parcelable에 대해서는 이전 포스팅에서 확인할 수 있다.

 

-> 출처: 찰스의 안드로이드

 

그러므로 이둘은 생략하고 가장 좋다는 parcelize만  사용법을 봐보겠다.

 

 


Parcelize

이런 문제를 보완하기 위해 나온 코틀린의 @Parcelize는 시간과 노력을 덜 들이고 손쉽게 데이터 클래스를 Parcelable하게 만들어 준다. Serializable의 간편성과 Parcelable의 성능을 모아둔 격이다. 

Parcelize를 사용하기 위해서는 데이터 클래스에 @Parcelize 어노테이션을 달아주기만 하면 끝이다. 

 

자세한 적용법은 다음과 같다.

 

1.build.gradle에 추가해주고

plugins {
    id("kotlin-parcelize")
}

 

2.dataclass에 @Paracelize 어노테이션을 추가해준다.

package changhwan.experiment.testparcelize

import android.os.Parcelable
import kotlinx.parcelize.Parcelize

@Parcelize
data class SampleData(
    val userName: String,
    val userData: String,
    val id : Int
) : Parcelable

 

3.putExtra로 보내기

이렇게 Parcealble를 넘기는 putExtra를 사용해서 넘겨준다.

 

package changhwan.experiment.testparcelize

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import changhwan.experiment.testparcelize.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    var goData = arrayListOf<SampleData>(
        SampleData("아오", "이거", 1),
        SampleData("진짜", "왜이러는겨", 2),
        SampleData("어카지", "킹받는다", 3)
    )
    var oneData =  SampleData("어카지", "킹받는다", 3)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.btnIntent.setOnClickListener {
            val intent = Intent(this, MainActivity2::class.java)
            intent.putExtra("onedata",oneData)
            intent.putParcelableArrayListExtra("intentData",goData)

            startActivity(intent)
        }

    }
}

여기서 리스트도 oneData가 직렬화한 객체이다.

 

4. getParcelableExtra로 받기

package changhwan.experiment.testparcelize

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import changhwan.experiment.testparcelize.databinding.ActivityMain2Binding

class MainActivity2 : AppCompatActivity() {

    private lateinit var binding: ActivityMain2Binding
    lateinit var givedData : ArrayList<SampleData>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        givedData = intent.getParcelableArrayListExtra<SampleData>("intentData") as ArrayList<SampleData>
        val givedOneData = intent.getParcelableExtra<SampleData>("onedata")
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main2)
        binding.xmlData = this

    }
}

이렇게  getParcelableExtra 를 통해서 받으면된다.


 

 

이렇게 일단 객체는 받아봤는데 직렬화된 객체들의 배열은 어떻게 받을까?

 

이미 위에 코드에도 나와있지만 다시한번 봐보겠다.

 

우선 gradle를 추가하고

 

객체를 직렬화 시키는것까지는 같은 방법을 사용한다.

 

그다음에 액티비티에서 보낼때

putExtra 대신 putParcelableArrayListExtra 를 이용해서 보내고 보낼때 직렬화된 객체가 담긴 Arraylist로 보내야한다 이상하게 처음에 mutablelist로 보냈는데 그건 안되어서 안드로이드 공식문서봤더니

이런것만 전달이 되어있었는데 mutableList는 없어서 걍 Arraylist로 했더니 되었다.

 

공식문서 

 

Parcelable 구현 생성기  |  Android 개발자  |  Android Developers

Parcelable 구현 생성기 kotlin-parcelize 플러그인은 Parcelable 구현 생성기를 제공합니다. Parcelable 지원을 포함하려면 앱의 build.gradle 파일에 다음 Gradle 플러그인을 추가합니다. Groovy plugins { id 'kotlin-parce

developer.android.com

 어쨋든 그 arraylist를 putParcelableArrayListExtra 를 이용해서 보내면된다. 근데 희안하게 그냥 putExtra로 해도 보내진다. 어쨋든 전용있으니까 그거쓰자.

 

package changhwan.experiment.testparcelize

import android.content.Intent
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import changhwan.experiment.testparcelize.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding
    var goData = arrayListOf<SampleData>(
        SampleData("아오", "이거", 1),
        SampleData("진짜", "왜이러는겨", 2),
        SampleData("어카지", "킹받는다", 3)
    )
    var oneData =  SampleData("어카지", "킹받는다", 3)

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = DataBindingUtil.setContentView(this, R.layout.activity_main)

        binding.btnIntent.setOnClickListener {
            val intent = Intent(this, MainActivity2::class.java)
            intent.putParcelableArrayListExtra("intentData",goData)

            startActivity(intent)
        }

    }
}

 

 

그리고 이제 받는 액티비티에서 getParcelableArrayListExtra 를 이용해서 받아준다.

 

package changhwan.experiment.testparcelize

import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import androidx.databinding.DataBindingUtil
import changhwan.experiment.testparcelize.databinding.ActivityMain2Binding

class MainActivity2 : AppCompatActivity() {

    private lateinit var binding: ActivityMain2Binding
    lateinit var givedData : ArrayList<SampleData>

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        givedData = intent.getParcelableArrayListExtra<SampleData>("intentData") as ArrayList<SampleData>
        binding = DataBindingUtil.setContentView(this,R.layout.activity_main2)
        binding.xmlData = this

    }
}

 

 

여기서 사용했던방법과 처음 보고 신기했던 부분을 설명하자면

우선 의식에 흐름으로 

givedData를 지연초기화로 선언해놓고  

그걸 데이터 바인딩으로 xml과 다연결해놓고

거기에 데이터를 인텐트 로 끌어와서 담아서 정보를 뿌려주는 느낌으로 가보고싶었다.

 

그래서  바로 getParcelableArrayListExtra에 제너릭으로 <Arraylist<객체>> 를 담아줘야하는줄 알았는데

<객체> 를 담아주면 되는거였어서 제너릭 안에 SampleData를 넣어주었다.

그리고 그결과값을 바로 giveData에 넣어주려했지만 자료형이 

intent.getParcelableArrayListExtra<SampleData>("intentData") 는 

java.utill.ArrayList<SampelData>? 였기에 

따로 다른 변수에 받아서 그걸 givedData에 넣어주거나(심지어nullcheck까지해야함)

 

아니면 as를 통해서 ArrayList<SampleData>로 캐스팅해서 담아줬다.

(캐스팅에 대해서는 밑에서 설명하겠다)

 

이렇게 해서 했더니 데이터가 원하는대로 전달되고 화면상에도 뿌려졌다.

 


코틀린(Kotlin) 캐스팅(casting), is 와 as

 

https://m.blog.naver.com/wnstn0154/221855117820

 

코틀린(Kotlin) 캐스팅(casting), is 와 as

캐스팅 Casting은 주조(鑄造)라는 뜻. 금속을 녹여서 만들어 둔 틀에 부어서 식혀 금속 제품을 만드는 것...

blog.naver.com

 

이 글을 참고하면되는데

 

간단하게 말해서

자바 배울때 다형성 배웠던것 상속관계의 클래스들 상에서

선언시 부모클래스의 자료형으로도 쓸수있고 자식의 자료형으로도 클래스를 사용하게 될것이다.

 

그래서 캐스팅이일어나는데 개념은 이러하다.

1. up casting

하위 클래스가 상위 클래스화 되는 것, 축소의 개념이 된다.

기본적으로 하위 클래스는 상위 클래스를 상속 받은 것이기 때문에 하위 클래스는 상위 클래스의 프로퍼티를 전부 포함하고 있다. 따라서 up casting은 특별한 연산자나 함수가 필요하지 않다. 그냥 데이터 타입을 지정해주기만 해도 가능하다.

var A: Drinks = Cola()

인스턴스 A는 Drinks 클래스 데이터 타입을 갖는다.

그리고 Cola 클래스로 부터 생성된 인스턴스이다.

따라서, A는 오버라이드 된 Cola의 Drink()함수를 갖는다.

2. down casting

상위 클래스가 하위 클래스로 되는 것. 확장의 개념이 된다.

var A: Cola = Drinks() >>> error type mismatch

타입이 잘못 됐다는 에러가 발생한다. 다운 캐스팅은 컴파일러가 자동적으로 실행시켜주지 않는다.

왜?

Cola클래스는 Drinks클래스보다 메소드가 하나 더 있으며, Drink()함수 또한 오버라이드를 통해 기능이 확장 됐다. 따라서 Drinks클래스는 자신보다 더 커 버린 Cola클래스를 담을 수 없다. 캐스팅(주조)하기에는 틀 자체가 너무 커져버린 것이다. 따라서 다운 캐스팅은 일반적인 경우에는 사용할 수 없고, 사용하고 싶을 때는 특별한 예약어를 이용해야 한다.

 

is

is의 기본적인 활용은 다음과 같다.

조건문(인스턴스 is 클래스 타입){}

is의 반환형은 Boolean으로 A가 B와 호환되는지에 대한 정보를 반환한다. 또한 조건문 안에서 동작할 때에는 다운 캐스팅이 적용 된다.

var A = Drinks()
var B = Cola()

if(A is Cola){
}

이런식으로 작성된 코드면, A는 Cola보다 작은 범위를 가진 super class인 Drinks를 상속받은 클래스의 인스턴스 이기 때문에 호환이 가능하다.

is가 포함된 조건문 안의 코드는 전부 다운캐스팅이 적용된다. Cola클래스에만 존재하는 메소드/ 오버라이딩된 메소드, 프로퍼티들을 모두 사용할 수 있다.

 

 

as

as는 조건문 안에서 뿐만 아니라, 전체적인 코드에 다운캐스팅을 적용할 수 있는 방법이다.

인스턴스 = super 클래스로 만든 인스턴스 as 다운 캐스팅할 sub 클래스 타입

구체적으로 표현하자면

var C = A as cola

A는 superclass Drinks의 인스턴스이다.

C는 A에 subclass인 cola 클래스가 적용된 반환 값을 받는 인스턴스가 된다.

그러면 C와 B는 cola클래스

A는 Drinks클래스가 되는 것이 일반적이지만, as를 사용하면 A에도 다운 캐스팅이 적용된다.

즉 as는 반환 값과 a에 사용된 매개 인스턴스 또한 다운캐스팅이 된다.

고로 위쪽 결과물에서는 

 

java.utill.ArrayList 가 부모클래스로 코틀린의 ArrayList가 만들어졌기에 자료형이 ArrayList인 변수에 담고싶다면 다운 캐스팅을 as로 해서 넣어줘야했던것이다.

 

2022.09.18 갑자기 생각나서 내용을 추가하는부분

 

멀티모듈을 사용하다보니 domain layer 관련 모듈에서는 순수 자바 코틀린 모듈이므로 안드로이드 관련 의존성이 아예 없어서 parcelize를 쓸수가 없다 (안드로이드에서 제공하는 직렬화니까)

그래서 뭐 느리고 빠르고를 제치고 어쩔수없이 Serializable 을 쓸수밖에 없으니 참고하자 -> 실제로 이걸로 직렬화 다달아놨다. 

혹시 이부분 관련해서 더좋은 방법이있거나 기발한 아이디어가 있다면 제발 댓글로 알려주세요 엉엉엉 ㅠㅠㅠ