Computer >> Hướng Dẫn Máy Tính >  >> Lập Trình >> Android

Cách truyền đối tượng (Object) giữa các Activity Android bằng Intent

Giới thiệu

Để truyền object giữa các Activity, object phải implement một trong 2 interface:

  • Serializable (Java standard) - Dễ dùng, chậm hơn, dùng reflection.
  • Parcelable (Android optimized) - Nhanh hơn, cần viết boilerplate code (hoặc dùng @Parcelize Kotlin).

Bài viết hướng dẫn cả 2 cách, ưu tiên Parcelable với @Parcelize (Kotlin).

Các bước thực hiện

Bước 1: Tạo dự án mới

Android Studio → File → New Project → Empty View Activity.

Bước 2: Tạo Model Class

Cách 1: Serializable (Java/Kotlin) - Đơn giản

// Java
import java.io.Serializable;

public class Message implements Serializable {
    private static final long serialVersionUID = 1L;
    private String content;
    private int priority;
    private boolean isRead;

    public Message(String content, int priority, boolean isRead) {
        this.content = content;
        this.priority = priority;
        this.isRead = isRead;
    }
    // Getters, setters, toString()...
}
// Kotlin
data class Message(
    val content: String,
    val priority: Int,
    val isRead: Boolean
) : Serializable

Cách 2: Parcelable với @Parcelize (Kotlin - Khuyến nghị)

// build.gradle.kts (plugins)
id("org.jetbrains.kotlin.plugin.parcelize") version "1.9.0" apply true

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

@Parcelize
data class Message(
    val content: String,
    val priority: Int,
    val isRead: Boolean,
    val timestamp: Long = System.currentTimeMillis()
) : Parcelable

Cách 3: Parcelable thủ công (Java - không khuyến nghị)

public class Message implements Parcelable {
    private String content;
    private int priority;
    private boolean isRead;

    // Constructor, getters, setters...

    protected Message(Parcel in) {
        content = in.readString();
        priority = in.readInt();
        isRead = in.readByte() != 0;
    }

    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(content);
        dest.writeInt(priority);
        dest.writeByte((byte) (isRead ? 1 : 0));
    }

    @Override
    public int describeContents() { return 0; }

    public static final Creator CREATOR = new Creator() {
        @Override
        public Message createFromParcel(Parcel in) { return new Message(in); }
        @Override
        public Message[] newArray(int size) { return new Message[size]; }
    };
}

Bước 3: Layout Activity 1 (res/layout/activity_main.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:gravity="center"
    tools:context=".MainActivity">

    <EditText
        android:id="@+id/etMessage"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:hint="Nội dung tin nhắn"
        android:inputType="textMultiLine"
        android:minHeight="100dp"
        android:padding="12dp"
        android:background="@drawable/edittext_rounded" />

    <Button
        android:id="@+id/btnSendObject"
        android:layout_width="match_parent"
        android:layout_height="wrap_content"
        android:layout_marginTop="16dp"
        android:text="Gửi Object sang Activity 2" />
</LinearLayout>

Bước 4: Layout Activity 2 (res/layout/activity_second.xml)

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="https://schemas.android.com/apk/res/android"
    xmlns:tools="https://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:orientation="vertical"
    android:padding="24dp"
    android:gravity="center"
    tools:context=".SecondActivity">

    <TextView
        android:id="@+id/tvReceived"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:textSize="16sp"
        android:textStyle="bold"
        tools:text="Received: Message[content=Hello, priority=1, isRead=false]" />
</LinearLayout>

Bước 5: MainActivity.kt (Gửi object)

package com.example.passobject

import android.content.Intent
import android.os.Bundle
import android.widget.Toast
import androidx.appcompat.app.AppCompatActivity
import com.example.passobject.databinding.ActivityMainBinding

class MainActivity : AppCompatActivity() {

    private lateinit var binding: ActivityMainBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivityMainBinding.inflate(layoutInflater)
        setContentView(binding.root)

        binding.btnSendObject.setOnClickListener {
            val content = binding.etMessage.text.toString().trim()
            if (content.isEmpty()) {
                Toast.makeText(this, "Vui lòng nhập nội dung", Toast.LENGTH_SHORT).show()
                return@setOnClickListener
            }

            val message = Message(
                content = content,
                priority = 1,
                isRead = false
            )

            val intent = Intent(this, SecondActivity::class.java)
            // Cách 1: Parcelable (khuyến nghị)
            intent.putExtra("message", message)
            
            // Cách 2: Serializable (nếu dùng Serializable)
            // intent.putExtra("message", message) // Cùng API, tự detect

            startActivity(intent)
        }
    }
}

Bước 6: SecondActivity.kt (Nhận object)

package com.example.passobject

import android.os.Bundle
import androidx.appcompat.app.AppCompatActivity
import com.example.passobject.databinding.ActivitySecondBinding

class SecondActivity : AppCompatActivity() {

    private lateinit var binding: ActivitySecondBinding

    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        binding = ActivitySecondBinding.inflate(layoutInflater)
        setContentView(binding.root)

        // Nhận Parcelable
        val message = intent.getParcelableExtra("message")
        
        // Nhận Serializable (cách cũ)
        // val message = intent.getSerializableExtra("message") as? Message

        if (message != null) {
            binding.tvReceived.text = "Received: $message"
        } else {
            binding.tvReceived.text = "Không nhận được object"
        }
    }
}

Bước 7: AndroidManifest.xml

<activity android:name=".MainActivity" android:exported="true">
    <intent-filter>
        <action android:name="android.intent.action.MAIN" />
        <category android:name="android.intent.category.LAUNCHER" />
    </intent-filter>
</activity>
<activity android:name=".SecondActivity" android:exported="false" />

Truyền Object qua Fragment (Arguments Bundle)

// Gửi từ Activity/Fragment
val fragment = MyFragment()
val args = Bundle().apply {
    putParcelable("message", message) // Parcelable
    // putSerializable("message", message) // Serializable
}
fragment.setArguments(args)

// Nhận trong Fragment
val message = arguments?.getParcelable("message")

Truyền Object lớn / Complex (Không khuyến nghị qua Intent)

Intent có giới hạn kích thước (~1MB, tùy thiết bị). Đối với object lớn (bitmap, list lớn, JSON to):

  • ViewModel chia sẻ (Activity + Fragment, hoặc Fragment + Fragment): `activityViewModels()` hoặc `sharedViewModel`.
  • Repository/Singleton lưu trữ tạm.
  • File/Cache ghi tạm, đọc ở destination.
  • WorkManager/DataStore cho persistent data.

So sánh Serializable vs Parcelable

Tiêu chíSerializableParcelable (@Parcelize)
PerformanceChậm (reflection, I/O)Nhanh (tối ưu Android)
Code boilerplateÍt (implements Serializable)Rất ít (@Parcelize data class)
Kích thước objectLớn hơnNhỏ hơn
VersioningserialVersionUIDTự động (thêm field mới OK)
Khuyến nghịChỉ cho object đơn giản, ít dùngMặc định cho mọi trường hợp

Lưu ý quan trọng

  • Luôn dùng @Parcelize cho Kotlin data class - tự generate code Parcelable.
  • Object Parcelable/Serializable phải có **constructor không tham số** hoặc tất cả field có default value (data class tự có).
  • Nested object: Tất cả class con cũng phải implement Parcelable/Serializable.
  • Bitmap: Implement Parcelable (có sẵn) nhưng KHÔNG nên truyền Bitmap qua Intent (quá lớn) - truyền URI/path/file.
  • intent.getParcelableExtra("key") yêu cầu API 33+ (Kotlin extension). API cũ: intent.getParcelableExtra("key") as Type?.