Lợi ích của việc sử dụng Parcelable thay vì tuần tự hóa đối tượng


97

Theo tôi hiểu, BundleParcelablethuộc về cách Android thực hiện tuần tự hóa. Nó được sử dụng chẳng hạn để chuyển dữ liệu giữa các hoạt động. Nhưng tôi tự hỏi, nếu có bất kỳ lợi ích nào trong việc sử dụng Parcelablethay vì tuần tự hóa cổ điển trong trường hợp lưu trạng thái của các đối tượng kinh doanh của tôi vào bộ nhớ trong chẳng hạn? Nó sẽ đơn giản hơn hay nhanh hơn so với cách cổ điển? Tôi nên sử dụng tuần tự hóa cổ điển ở đâu và sử dụng gói ở đâu tốt hơn?

Câu trả lời:


99

Từ "Pro Android 2"

LƯU Ý: Nhìn thấy Parcelable có thể đã đặt ra câu hỏi, tại sao Android không sử dụng cơ chế tuần tự hóa Java tích hợp sẵn? Hóa ra nhóm Android đã đưa ra kết luận rằng quá trình tuần tự hóa trong Java quá chậm để đáp ứng các yêu cầu giao tiếp liên xử lý của Android. Vì vậy nhóm đã xây dựng giải pháp Parcelable. Phương pháp Parcelable yêu cầu bạn tuần tự hóa rõ ràng các thành viên trong lớp của mình, nhưng cuối cùng, bạn nhận được tuần tự hóa các đối tượng của mình nhanh hơn nhiều.

Cũng nhận ra rằng Android cung cấp hai cơ chế cho phép bạn chuyển dữ liệu sang một quy trình khác. Đầu tiên là chuyển một gói cho một hoạt động bằng cách sử dụng một ý định và thứ hai là chuyển Một gói cho một dịch vụ. Hai cơ chế này không thể thay thế cho nhau và không nên nhầm lẫn. Đó là, Parcelable không có nghĩa là được chuyển cho một hoạt động. Nếu bạn muốn bắt đầu một hoạt động và chuyển cho nó một số dữ liệu, hãy sử dụng một gói. Parcelable có nghĩa là chỉ được sử dụng như một phần của định nghĩa AIDL.


9
"Pro Android 2" là gì?
AlikElzin-kilaka

79
Đoạn thứ hai là không đúng sự thật, bạn có thể vượt qua một Parcelable như một tham số để một hoạt động bằng cách sử dụng gói ...
Ixx

3
Khi tôi tuần tự hóa các đối tượng của mình, tôi tạo một getBundlephương thức, sau đó gọi phương thức đó từ writeToParcelas dest.writeBundle(getBundle());và tôi có sẵn cả hai tùy chọn trong đối tượng một cách tự động. Có rất thú vị Parcel tính năng cho các đối tượng sống lưu ý ở đây: developer.android.com/reference/android/os/Parcel.html
mikebabcock

2
@lxx: Tôi đã tự hỏi tại sao người ta cần phải chuyển một đối tượng có thể chuyển đổi qua gói cho hoạt động. IMO nếu bạn làm như vậy, bạn đang thêm một cấp độ tuần tự hóa nữa một cách không cần thiết & không có gì khác.
Tăng

4
Philippe Breault đã có một bài viết hay về điều này, và cũng thêm một bài kiểm tra hiệu suất. developerphil.com/parcelable-vs-serializable
WonderCsabo

23

Serializablechậm một cách hài hước trên Android. Trên thực tế, đường viền vô dụng trong nhiều trường hợp.

ParcelParcelablenhanh chóng tuyệt vời, nhưng tài liệu của nó cho biết bạn không được sử dụng nó cho mục đích chung là tuần tự hóa để lưu trữ, vì việc triển khai thay đổi theo các phiên bản Android khác nhau (tức là bản cập nhật hệ điều hành có thể phá vỡ ứng dụng dựa trên nó).

Giải pháp tốt nhất cho vấn đề tuần tự hóa dữ liệu để lưu trữ với tốc độ hợp lý là cuộn của riêng bạn. Cá nhân tôi sử dụng một trong các lớp tiện ích của riêng mình có giao diện tương tự Parcelvà có thể tuần tự hóa tất cả các loại tiêu chuẩn rất hiệu quả (với chi phí là an toàn loại). Đây là một phiên bản rút gọn của nó:

public interface Packageable {
    public void readFromPackage(PackageInputStream in)  throws IOException ;
    public void writeToPackage(PackageOutputStream out)  throws IOException ; 
}


public final class PackageInputStream {

    private DataInputStream input;

    public PackageInputStream(InputStream in) {
        input = new DataInputStream(new BufferedInputStream(in));
    }

    public void close() throws IOException {
        if (input != null) {
            input.close();
            input = null;
        }       
    }

    // Primitives
    public final int readInt() throws IOException {
        return input.readInt();
    }
    public final long readLong() throws IOException {
        return input.readLong();
    }
    public final long[] readLongArray() throws IOException {
        int c = input.readInt();
        if (c == -1) {
            return null;
        }
        long[] a = new long[c];
        for (int i=0 ; i<c ; i++) {
            a[i] = input.readLong();
        }
        return a;
    }

...

    public final String readString()  throws IOException {
        return input.readUTF();
    }
    public final <T extends Packageable> ArrayList<T> readPackageableList(Class<T> clazz) throws IOException {
        int N = readInt();
        if (N == -1) {
            return null;
        }
        ArrayList<T> list = new ArrayList<T>();
        while (N>0) {
            try {
                T item = (T) clazz.newInstance();
                item.readFromPackage(this);
                list.add(item);
            } catch (InstantiationException e) {
                e.printStackTrace();
            } catch (IllegalAccessException e) {
                e.printStackTrace();
            }
            N--;
        }
        return list;
    }

}



public final class PackageOutputStream {

    private DataOutputStream output;

    public PackageOutputStream(OutputStream out) {
        output = new DataOutputStream(new BufferedOutputStream(out));
    }

    public void close() throws IOException {
        if (output != null) {
            output.close();
            output = null;
        }
    }

    // Primitives
    public final void writeInt(int val) throws IOException {
        output.writeInt(val);
    }
    public final void writeLong(long val) throws IOException {
        output.writeLong(val);
    }
    public final void writeLongArray(long[] val) throws IOException {
        if (val == null) {
            writeInt(-1);
            return;
        }
        writeInt(val.length);
        for (int i=0 ; i<val.length ; i++) {
            output.writeLong(val[i]);
        }
    }

    public final void writeFloat(float val) throws IOException {
        output.writeFloat(val);
    }
    public final void writeDouble(double val) throws IOException {
        output.writeDouble(val);
    }
    public final void writeString(String val) throws IOException {
        if (val == null) {
            output.writeUTF("");
            return;
        }
        output.writeUTF(val);
    }

    public final <T extends Packageable> void writePackageableList(ArrayList<T> val) throws IOException {
        if (val == null) {
            writeInt(-1);
            return;
        }
        int N = val.size();
        int i=0;
        writeInt(N);
        while (i < N) {
            Packageable item = val.get(i);
            item.writeToPackage(this);
            i++;
        }
    }

}

2
Sự khác biệt giữa việc sử dụng lớp tùy chỉnh này của bạn và chỉ triển khai giao diện Có thể ngoại hóa và làm điều tương tự là gì?
Carrotman 42

1
Bundle cũng sắp xếp các tên trường ... nó không phù hợp với hàng nghìn đối tượng.
Reuben Scratton

1
Xin lỗi, việc phát minh ra một bộ nối tiếp khác thật tệ - bây giờ chỉ có một "Có thể bán được" khác để giải quyết. Có rất nhiều thứ để lựa chọn, với một thư viện (sự khác biệt là thư viện được kiểm tra, thử nghiệm và sử dụng định dạng mà người khác sử dụng): ProtocolBuffers, JSON, XML, v.v. Thật đáng tiếc khi thư viện Android thực sự tệ về mặt này .

2
Tôi không nghĩ câu mở đầu là đúng nữa (5 năm sau khi nó được đưa ra). Tôi đã sử dụng tuần tự hóa java mà không có bất kỳ sự cố nào trong một thời gian dài. Bạn có thể tìm thấy một số điều thú vị tiềm ẩn về chủ đề này trong một bài đăng trên blog mà tôi vừa viết. nemanjakovacevic.net/blog/english/2015/03/24/…
Nemanja Kovacevic

1
Vâng, nó không thực sự là một vấn đề như vậy nữa. Tôi không sử dụng gì nhiều hơn Serializable (với triển khai readObject / writeObject được tối ưu hóa) trong vài năm. Trên thực tế, tôi đã xem xét một kết xuất hex của một vài đối tượng được tuần tự hóa chỉ vài ngày trước và hài lòng rằng nó không quá lãng phí.
Reuben Scratton


11

Nếu bạn cần serialization cho mục đích lưu trữ tức nhưng muốn tránh hình phạt tốc độ phản xạ phát sinh do việc Serializable giao diện bạn nên tạo một cách rõ ràng giao thức serialization của riêng bạn với Externalizable giao diện.

Khi được triển khai đúng cách, tốc độ này khớp với tốc độ của Parcelable và cũng tính đến khả năng tương thích giữa các phiên bản khác nhau của Android và / hoặc nền tảng Java.

Bài viết này cũng có thể làm sáng tỏ mọi thứ:

Sự khác biệt giữa Serializable và Externalizable trong Java là gì?

Ngoài ra, nó cũng là kỹ thuật tuần tự hóa nhanh nhất trong nhiều tiêu chuẩn, đánh bại Kryo, Avro, Protocol Buffers và Jackson (json):

http://code.google.com/p/thrift-protobuf-compare/wiki/Benchmarking


7

Có vẻ như ngày nay sự khác biệt không còn quá rõ ràng, ít nhất là không khi bạn chạy nó giữa các hoạt động của riêng mình.

Theo các thử nghiệm hiển thị trên trang web này , Parcelable nhanh hơn khoảng 10 lần trên các thiết bị mới nhất (như nexus 10) và nhanh hơn khoảng 17 lần trên các thiết bị cũ (như mong muốn Z)

vì vậy bạn quyết định xem nó có xứng đáng hay không.

có thể đối với các lớp tương đối nhỏ và đơn giản, Serializable là tốt, còn phần còn lại, bạn nên sử dụng Parcelable


Tôi nghĩ rằng bạn đã đúng về số tiền khi nói rằng sự khác biệt đang giảm dần. Tuy nhiên, tôi phát hiện ra rằng việc sử dụng serializable có thể hiệu quả hơn nhiều về kích thước của mảng byte được sắp xếp theo nguyên tắc và điều đó có thể giúp tránh được TransactionTooLargeException. Tôi rất muốn nghe ý kiến ​​của bạn về bài đăng trên blog này (của tôi) nemanjakovacevic.net/blog/english/2015/03/24/…
Nemanja Kovacevic

Chà, bạn có thể chỉ cần đặt đối tượng tiêu thụ bộ nhớ khổng lồ trên một biến tĩnh và đặt nó thành null ngay khi bạn tìm nạp (ví dụ trên onCreate). Điểm bất lợi là nó không hỗ trợ đa quy trình và đó là cách thực hiện hơi bẩn. Tự hỏi nếu đó là trường hợp nếu bạn muốn chuyển một bitmap lớn.
nhà phát triển android

4

Parcelable chủ yếu liên quan đến IPC bằng cách sử dụng cơ sở hạ tầng Binder , nơi dữ liệu được truyền dưới dạng Bưu kiện .

Vì Android phụ thuộc rất nhiều vào Binder cho hầu hết, nếu không phải là tất cả, các tác vụ IPC, nên việc triển khai Parcelable ở hầu hết các vị trí và đặc biệt là trong framework là rất hợp lý, vì nó cho phép chuyển một đối tượng sang một quy trình khác nếu bạn cần. Nó làm cho các đối tượng "có thể vận chuyển".

Nhưng nếu bạn có một lớp nghiệp vụ không dành riêng cho Android sử dụng rộng rãi các serializable để lưu trạng thái đối tượng và chỉ cần lưu trữ chúng vào hệ thống tệp, thì tôi nghĩ rằng serializable là tốt. Nó cho phép tránh mã tấm lò hơi có thể thay thế.


Bạn muốn lưu trữ một đối tượng thực trong hệ thống tệp saya trong ví dụ nào? Tại sao không chỉ cần lấy nội dung đối tượng và lưu trữ nội dung thực tế trong một tệp. Hãy xem JSON hoặc thậm chí xml chẳng hạn. Bạn có thể lưu các đối tượng ở định dạng JSON hoặc XML, các đối tượng như trong các kiểu POJO / Entity xây dựng một đối tượng dữ liệu điển hình được xây dựng với chủ yếu là State và getters và setters cho trạng thái đó. Bằng cách đó, không cần phải tuần tự hóa các đối tượng cho mục đích lưu trữ chúng vì tất cả những gì bạn quan tâm là trạng thái đối tượng
Jonathan


1

Tôi chỉ sử dụng GSON -> Serialise to JSON String -> Restore Object from JSON String.


Điều này là tốt cho các đối tượng nhỏ, không quá nhiều khi bạn có một mảng lớn các đối tượng với nhiều thuộc tính trong mỗi đối tượng.
Nickmccomb

0

Cũng có thể Parcelable cung cấp triển khai tùy chỉnh trong đó người dùng có cơ hội phân loại từng đối tượng của mình bằng cách ghi đè writeToParcel (), Tuy nhiên, việc tuần tự hóa không triển khai tùy chỉnh này vì cách truyền dữ liệu của nó liên quan đến API phản chiếu JAVA.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.