2012-09-06 23 views
26

Một hành vi tôi đang quan sát wrt đi qua dữ liệu serializable như ý định thêm là khá lạ, và tôi chỉ muốn làm rõ cho dù có cái gì tôi không bỏ lỡ .LinkedList đưa vào Intent thêm được recast để ArrayList khi lấy trong hoạt động tiếp theo

Vì vậy, điều tôi đang cố gắng làm là trong ActivtyA Tôi đặt một ví dụ LinkedList vào số intent Tôi đã tạo để bắt đầu hoạt động tiếp theo - ActivityB.

LinkedList<Item> items = (some operation); 
Intent intent = new Intent(this, ActivityB.class); 
intent.putExtra(AppConstants.KEY_ITEMS, items); 

Trong onCreate của ActivityB, tôi đã cố gắng để lấy LinkedList thêm như sau -

LinkedList<Item> items = (LinkedList<Item>) getIntent() 
          .getSerializableExtra(AppConstants.KEY_ITEMS); 

: Khi chạy phần này, tôi liên tục nhận được ClassCastException trong ActivityB, tại dòng trên. Về cơ bản, ngoại lệ nói rằng tôi đã nhận được ArrayList. Một khi tôi thay đổi mã ở trên để nhận được một ArrayList thay vào đó, tất cả mọi thứ đã làm việc tốt.

Bây giờ tôi không thể chỉ ra từ tài liệu hiện có cho dù đây là hành vi mong đợi trên Android khi thực thi danh sách tuần tự. Hoặc có lẽ, có điều gì đó về cơ bản sai với những gì tôi đang làm.

Cảm ơn.

+0

sử dụng Ghim thay thế. –

+0

nhưng có lý do cụ thể nào khiến 'LinkedList' xảy ra, trong khi, nếu tôi đã thêm một cá thể' ArrayList' làm dữ liệu phụ trên 'intent' thì mọi thứ sẽ ổn. Và, tôi sẽ không cần phải sử dụng 'Parcelable'? – anirvan

+0

+1 cho câu hỏi thú vị. Tôi đã dành một chút thời gian suy nghĩ về điều này và rất hấp dẫn, tôi đã tìm ra nó cho bản thân mình. Bây giờ bạn và tôi đều thông minh hơn (xem câu trả lời của tôi). –

Trả lời

49

tôi có thể cho bạn biết lý do tại sao điều này xảy ra, nhưng bạn sẽ không thích nó ;-)

Lần đầu tiên một chút thông tin cơ bản:

Extras trong một Intent về cơ bản Android Bundle về cơ bản là các cặp khóa/giá trị HashMap. Vì vậy, khi bạn làm điều gì đó như

intent.putExtra(AppConstants.KEY_ITEMS, items); 

Android tạo ra một Bundle mới cho tính năng bổ sung và thêm một mục bản đồ để các Bundle nơi quan trọng là AppConstants.KEY_ITEMS và giá trị là mục (đó là đối tượng LinkedList của bạn).

Điều này là tốt và tốt, và nếu bạn xem xét gói tính năng bổ sung sau khi mã của bạn thực hiện, bạn sẽ thấy rằng nó có chứa LinkedList. Bây giờ đến phần thú vị ...

Khi bạn gọi startActivity() với Intent có tính năng bổ sung, Android cần phải chuyển đổi phần bổ sung từ bản đồ các cặp khóa/giá trị thành luồng byte. Về cơ bản nó cần phải serialize Bundle. Nó cần phải làm điều đó bởi vì nó có thể bắt đầu hoạt động trong một quá trình khác và để làm điều đó nó cần serialize/deserialize các đối tượng trong Bundle để nó có thể tái tạo chúng trong quá trình mới. Nó cũng cần phải làm điều này bởi vì Android lưu nội dung của Intent trong một số bảng hệ thống để nó có thể tạo lại Intent nếu nó cần sau này.

Để tuần tự hóa Bundle thành luồng byte, nó đi qua bản đồ trong gói và nhận mỗi cặp khóa/giá trị. Sau đó, nó mất mỗi "giá trị" (đó là một số loại đối tượng) và cố gắng để xác định loại đối tượng nó là gì để nó có thể serialize nó một cách hiệu quả nhất. Để thực hiện điều này, nó sẽ kiểm tra kiểu đối tượng dựa vào danh sách các loại đối tượng đã biết.Danh sách "loại đối tượng đã biết" chứa những thứ như Integer, Long, String, Map, Bundle và không may là List. Vì vậy, nếu đối tượng là List (trong đó có nhiều loại khác nhau, bao gồm LinkedList), nó sẽ nối tiếp nó và đánh dấu nó là đối tượng thuộc loại List.

Khi Bundle được deserialized, ví dụ: khi bạn làm điều này:

LinkedList<Item> items = (LinkedList<Item>) 
     getIntent().getSerializableExtra(AppConstants.KEY_ITEMS); 

nó tạo ra một ArrayList cho tất cả các đối tượng trong Bundle loại List.

Không thực sự bất cứ điều gì bạn có thể làm để thay đổi hành vi này của Android. Ít nhất bây giờ bạn biết tại sao nó làm điều này.

Chỉ để bạn biết: Tôi thực sự đã viết một chương trình thử nghiệm nhỏ để xác minh hành vi này và tôi đã xem mã nguồn cho Parcel.writeValue(Object v) là phương thức được gọi từ Bundle khi chuyển đổi bản đồ thành luồng byte.

Lưu ý quan trọng: Kể từ List là một giao diện điều này có nghĩa rằng bất kỳ lớp mà thực hiện List mà bạn đưa vào một Bundle sẽ đi ra như một ArrayList. Nó cũng thú vị là Map cũng nằm trong danh sách "các loại đối tượng nổi tiếng" có nghĩa là không có vấn đề gì loại Map đối tượng bạn đưa vào một Bundle (ví dụ TreeMap, SortedMap, hoặc bất kỳ lớp mà thực hiện giao diện Map), bạn sẽ luôn nhận được HashMap trong số đó.

+3

My, oh my - tôi có thể nói rằng * yes, tôi thực sự không thích điều này *. Xét cho cùng, toàn bộ khái niệm "* các kiểu đối tượng đã biết *" nghe như một số người muốn cắt góc khi xây dựng phần logic đó. Và cũng có thể, nếu đây là trường hợp, họ nên thực sự làm cho nó trở thành một điểm để ngăn chặn tất cả các loại đối tượng "không được biết đến" từ việc thực hiện 'Serializable', hoặc không hỗ trợ' Serializable' ở tất cả trong mục đích ' extras'. Dù sao, tôi không thể cảm ơn đủ, và vâng, chúng tôi thông minh hơn cho những gì bạn đã tìm thấy. – anirvan

+3

Cảm ơn rất nhiều vì câu trả lời. Đã lưu tôi giờ gỡ lỗi. – Andree

+2

Cảm ơn David, điều này đã cứu tôi ... Tôi đã có cùng một vấn đề với savedInstanceState đã được chuyển cho mảnh của tôi - và tôi không thể tìm ra lý do tại sao Android khăng khăng đưa cho tôi một ArrayList ... – Patrick

0

Câu trả lời của @David Wasser là đúng về chẩn đoán sự cố. Bài đăng này là để chia sẻ cách tôi xử lý nó.

Vấn đề với bất kỳ đối tượng List sắp ra như một ArrayList không phải là khủng khiếp, bởi vì bạn luôn có thể làm điều gì đó như

LinkedList<String> items = new LinkedList<>(
    (List<String>) intent.getSerializableExtra(KEY)); 

mà sẽ thêm tất cả các yếu tố của danh sách deserialized để một mới LinkedList.

Sự cố tồi tệ hơn nhiều khi nói đến Map, bởi vì bạn có thể đã cố gắng sắp xếp một số LinkedHashMap và hiện đã mất thứ tự phần tử.

May mắn thay, có một cách tương đối không đau xung quanh vấn đề này: xác định lớp trình bao bọc tuần tự của riêng bạn. Bạn có thể làm điều đó với nhiều loại cụ thể hoặc làm điều đó quát:

public class <T extends Serializable> Wrapper implements Serializable { 
    private T wrapped; 

    public Wrapper(T wrapped) { 
     this.wrapped = wrapped; 
    } 

    public T get() { 
     return wrapped; 
    } 
} 

Sau đó, bạn có thể sử dụng để che giấu bạn List, Map, hoặc kiểu dữ liệu khác từ kiểm tra kiểu Android:

intent.putExtra(KEY, new Wrapper<>(items)); 

và sau:

items = ((Wrapper<LinkedList<String>>) intent.getSerializableExtra(KEY)).get(); 
Các vấn đề liên quan