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

Làm thế nào để custom object có thể Parcelable trên Android

Triển khai Parcelable cho Custom Object

Parcelable là interface Android (android.os.Parcelable) để serialize object hiệu năng cao. Không dùng reflection, yêu cầu viết code đọc/ghi thủ công nhưng nhanh hơn Serializable 10-20 lần.

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

Khởi tạo project Android Studio.

Bước 2: Model Class Parcelable (src/ParcelableObject.java)

package com.example.myapplication;

import android.os.Parcel;
import android.os.Parcelable;

public class ParcelableObject implements Parcelable {
    private String name;
    private int age;
    private String email;
    private boolean isActive;
    
    // Constructor thông thường
    public ParcelableObject(String name, int age, String email, boolean isActive) {
        this.name = name;
        this.age = age;
        this.email = email;
        this.isActive = isActive;
    }
    
    // ========== PARCELABLE IMPLEMENTATION ==========
    
    // 1. Constructor đọc từ Parcel - THỨ TỰ PHẢI KHỚP writeToParcel
    protected ParcelableObject(Parcel in) {
        name = in.readString();
        age = in.readInt();
        email = in.readString();
        isActive = in.readByte() != 0; // Boolean đọc qua byte
    }
    
    // 2. CREATOR - Bắt buộc, public static final
    public static final Creator<ParcelableObject> CREATOR = new Creator<ParcelableObject>() {
        @Override
        public ParcelableObject createFromParcel(Parcel in) {
            return new ParcelableObject(in);
        }
        
        @Override
        public ParcelableObject[] newArray(int size) {
            return new ParcelableObject[size];
        }
    };
    
    @Override
    public int describeContents() {
        return 0; // Thường trả về 0, trừ khi có FileDescriptor
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeInt(age);
        dest.writeString(email);
        dest.writeByte((byte) (isActive ? 1 : 0)); // Boolean ghi qua byte
    }
    
    // ========== GETTERS ==========
    public String getName() { return name; }
    public int getAge() { return age; }
    public String getEmail() { return email; }
    public boolean isActive() { return isActive; }
    
    @Override
    public String toString() {
        return "ParcelableObject{name='" + name + "', age=" + age + ", email='" + email + "', active=" + isActive + "}";
    }
}

Bước 3: Layout (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:gravity="center"
    tools:context=".MainActivity">

    <Button
        android:id="@+id/btnParcelable"
        android:layout_width="wrap_content"
        android:layout_height="wrap_content"
        android:text="Tạo & Truyền Parcelable Object"
        android:padding="16dp" />

</LinearLayout>

Bước 4: MainActivity.java - Truyền qua Intent

package com.example.myapplication;

import android.content.Intent;
import android.os.Build;
import android.os.Bundle;
import android.support.annotation.RequiresApi;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Toast;

public class MainActivity extends AppCompatActivity {
    @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        
        findViewById(R.id.btnParcelable).setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                ParcelableObject user = new ParcelableObject(
                    "Sairam Krishna", 28, "sairam@example.com", true
                );
                
                Intent intent = new Intent(MainActivity.this, DetailActivity.class);
                intent.putExtra("user_parcelable", user); // Parcelable object
                startActivity(intent);
                
                Toast.makeText(MainActivity.this, "Đã gửi Parcelable", Toast.LENGTH_SHORT).show();
            }
        });
    }
}

Bước 5: DetailActivity.java - Nhận object

package com.example.myapplication;

import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.widget.TextView;

public class DetailActivity extends AppCompatActivity {
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_detail);
        
        // Nhận Parcelable - KHÔNG cần cast explicit như Serializable
        ParcelableObject user = getIntent().getParcelableExtra("user_parcelable");
        
        TextView tvInfo = findViewById(R.id.tvInfo);
        if (user != null) {
            tvInfo.setText("Nhận Parcelable:\n" + user.toString());
        } else {
            tvInfo.setText("Không nhận được object");
        }
    }
}

Android Studio Auto-generate Parcelable (Rất quan trọng!)

  1. Viết class với các field
  2. Alt+Insert (Windows/Linux) / Cmd+N (Mac)
  3. Chọn Parcelable
  4. Chọn các field cần parcel
  5. OK → IDE sinh toàn bộ code boilerplate

Parcelable với các kiểu dữ liệu phức tạp

public class ComplexParcelable implements Parcelable {
    private String name;
    private List<String> tags;           // List
    private List<ParcelableObject> items; // List
    private ParcelableObject detail;      // Nested Parcelable
    private int[] scores;                 // Primitive array
    private Bundle extras;                // Bundle
    private SparseArray<String> map;      // SparseArray
    
    protected ComplexParcelable(Parcel in) {
        name = in.readString();
        
        // List
        tags = new ArrayList<>();
        in.readStringList(tags);
        
        // List
        items = new ArrayList<>();
        in.readTypedList(items, ParcelableObject.CREATOR);
        
        // Nested Parcelable
        detail = in.readParcelable(ParcelableObject.class.getClassLoader());
        
        // Primitive array
        scores = in.createIntArray();
        
        // Bundle
        extras = in.readBundle(getClass().getClassLoader());
        
        // SparseArray
        map = new SparseArray<>();
        int size = in.readInt();
        for (int i = 0; i < size; i++) {
            int key = in.readInt();
            String value = in.readString();
            map.put(key, value);
        }
    }
    
    @Override
    public void writeToParcel(Parcel dest, int flags) {
        dest.writeString(name);
        dest.writeStringList(tags);
        dest.writeTypedList(items);
        dest.writeParcelable(detail, flags);
        dest.writeIntArray(scores);
        dest.writeBundle(extras);
        
        // SparseArray custom write
        dest.writeInt(map.size());
        for (int i = 0; i < map.size(); i++) {
            dest.writeInt(map.keyAt(i));
            dest.writeString(map.valueAt(i));
        }
    }
    
    // CREATOR... (tương tự)
}

Parcelable vs Serializable - Tóm tắt

Đặc điểm Serializable Parcelable
Interface java.io.Serializable android.os.Parcelable
Cơ chế Reflection Manual read/write
Tốc độ Chậm Nhanh (10-20x)
Garbage objects Nhiều Ít
Code boilerplate Không (chỉ implements) Nhiều (nhưng IDE auto-gen)
Intent.putExtra putExtra(key, Serializable) putExtra(key, Parcelable)
Intent.getExtra getSerializableExtra(key) + cast getParcelableExtra(key) - no cast

Best Practices

  • Luôn dùng IDE auto-generate - đừng viết tay
  • Giữ thứ tự read/write y hệt nhau
  • Dùng writeTypedList()/readTypedList() cho List<Parcelable>
  • Dùng writeParcelable()/readParcelable() cho nested object
  • Primitive arrays: writeIntArray(), createIntArray(), etc.
  • Bundle: writeBundle()/readBundle()
  • Enum: writeInt(ordinal)/values()[readInt()]

@Parcelize (Kotlin only)

// Kotlin - chỉ cần annotation
@Parcelize
data class User(
    val name: String,
    val age: Int,
    val email: String
) : Parcelable