Android 애플리케이션에서 Activity, Service, Broadcast Receiver, Content Provider 등 컴포넌트 간에 통신을 하려면 Intent를 사용해야 한다. Intent 객체는 컴포넌트 간에 통신을 위한 메시지를 전달
하는 역할을 하고 Bundle 객체는 통신할 때 전달할 메시지를 저장
하는 역할을 한다. 즉 Bundle에 데이터를 저장해서 Intent로 데이터를 전달한다.
Intent는 Android 애플리케이션의 컴포넌트 간에 작업 요청을 전달하는 메시지 객체
로 Intent를 이용해 다른 애플리케이션의 컴포넌트를 호출하거나, 자신의 컴포넌트를 호출할 수 있다. Intent는 명시적 호출과 암시적 호출로 구분된다.
명시적 호출
호출하려는 컴포넌트를 직접 지정하는 방법으로 호출하려는 컴포넌트의 패키지명과 클래스명을 지정해야 한다.
Intent intent = new Intent(this, MyActivity.class);
startActivity(intent);
암시적 호출
호출하려는 컴포넌트의 패키지명과 클래스명을 모르는 경우 사용하는 방법으로 호출하려는 작업의 내용을 지정하고, 시스템이 이 작업을 처리할 수 있는 컴포넌트를 찾아서 호출한다.
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://www.naver.com"));
startActivity(intent);
Bundle은 Android에서 다양한 데이터를 저장하고 전달하기 위한 객체이다.
Bundle 객체는 Map 인터페이스를 구현하므로, 키-값 쌍으로 데이터를 저장하고 추출할 수 있다.
데이터 전달
class MainActivity : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
intent.putExtra("key", "kang")
startActivity(intent)
}
}
}
데이터 받기
class MainActivity2 : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val key = findViewById<TextView>(R.id.key)
key.text = intent.getStringExtra("key");
}
}
Intent.putExtra() 메서드 내부 코드는 다음과 같다.
public @NonNull Intent putExtra(String name, @Nullable String value) {
if (mExtras == null) {
mExtras = new Bundle();
}
mExtras.putString(name, value);
return this;
}
Bundle객체가 null이라면 Bundle객체를 생성한 뒤 name:value 값을 저장하고 데이터를 보낸다. 즉 Intent.putExtra() 메서드를 사용하면 Bundle객체를 생성하지 않아도 내부적으로 알아서 Bundle객체를 생성해서 값을 저장한 뒤 데이터를 보낸다.
데이터 전달
class MainActivity : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
val bundle = Bundle()
bundle.putString("key", "kang")
intent.putExtra("bundle", bundle)
startActivity(intent)
}
}
}
데이터 받기
class MainActivity2 : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val bundle = intent.getBundleExtra("bundle")
val key = findViewById<TextView>(R.id.key)
if (bundle != null) {
key.text = bundle.getString("key")
}
}
}
만약 Intent에 객체를 전달해야 한다면, Serializable
이나 Parcelable
을 구현한 클래스를 통해 객체를 직렬화
한 뒤 보내야 한다.
직렬화(Serialization)란 객체를 바이트 스트림으로 변환하여 전송하거나 저장하는 것을 말한다. 직렬화를 통해 객체를 전송하거나 저장할 수 있으며, 이를 역직렬화(Deserialization)하여 다시 객체로 변환할 수 있다.
Serializable은 Java의 표준 인터페이스로 구현이 아주 간단하다. 아래 코드와 같이 class 형태에서 Serializable만 구현하면 된다.
data class User(
val name: String?,
val age: Int
) : Serializable
단점으로는 Parcelable보다 속도가 느리다. 왜냐하면 Serializable 인터페이스를 구현한 클래스는 직렬화될 때 reflection을 사용하여 객체의 모든 필드를 검색하고, 이를 일련의 바이트로 변환하는 방식으로 직렬화한다.
(reflection은 자바에서 객체를 생성하고, 해당 객체의 메소드나 멤버 변수 등의 정보를 알아내는 기술)
때문에 런타임에 데이터를 직렬화/역직렬화하는 과정에 많은 객체를 생성하기 때문에 GC가 할 일이 많아지므로Serializable을 사용하는 객체가 많아지면 많아질수록 성능에 안좋은 영향을 미치게 된다.
Serializable로 객체 전달
class MainActivity : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
val intent = Intent(this, MainActivity2::class.java)
val bundle = Bundle()
bundle.putSerializable("userData", User("kang", 27))
intent.apply {
this.putExtra("bundle", bundle)
}
startActivity(intent)
}
}
}
data class User(
val name: String?,
val age: Int
) : Serializable
객체 받기
class MainActivity2 : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val bundle = intent.getBundleExtra("bundle")
val name = findViewById<TextView>(R.id.name)
val age = findViewById<TextView>(R.id.age)
if (bundle != null) {
val userData = bundle?.getSerializable("userData") as User
name.text = userData.name
age.text = userData.age.toString()
}
}
}
Parcelable은 Java가 아닌 Android SDK의 인터페이스로 Serializable과는 다르게 reflection 과정이 없고 직렬화/역직렬화를 하는 과정을 개발자가 모두 구현한다.
(kotlin에서는 plugins {id 'kotlin-parcelize'}
를 통해 쉽게 구현할 수 있슴) 이렇게 구현된 코드가 미리 컴파일되어 런타임에 빠르게 처리되기 때문에 Serializable보다 빠르게 객체를 전달할 수 있다.
Parcelable 쉽게 구현하기 위해 'kotlin-parcelize'
추가
plugins {
id 'kotlin-parcelize'
}
객체 전달
class MainActivity : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main)
val button = findViewById<Button>(R.id.button)
button.setOnClickListener {
// bundle 안쓰고 Parcelable
val intent = Intent(this, MainActivity2::class.java)
intent.putExtra("userData", User("Kang", 27))
startActivity(intent)
}
}
}
@Parcelize
data class User(
val name: String?,
val age: Int
) : Parcelable
객체 받기
class MainActivity2 : AppCompatActivity() {
@SuppressLint("MissingInflatedId")
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_main2)
val name = findViewById<TextView>(R.id.name)
val age = findViewById<TextView>(R.id.age)
// bundle 안쓰고 parcelable
val userData = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
intent.getParcelableExtra("userData", User::class.java)
} else {
intent.getParcelableExtra<User>("userData")
}
if (userData != null) {
name.text = userData.name
age.text = userData.age.toString()
}
}
}