12

Tôi có hoạt động bằng cách vuốt tab bằng tab ActionBar, dựa trên the android developer example.Trình tải cung cấp kết quả sai phân đoạn

Mỗi tab sẽ hiển thị Phân đoạn và mỗi Phân đoạn (thực sự, một SherlockFragment) sẽ tải một loại yêu cầu api từ xa khác thông qua AsyncTaskLoader tùy chỉnh.

Vấn đề là nếu bạn nhấn vào một tab để di chuyển 2 tab/trang trong khi đoạn cho tab bạn đang rời (đoạn cũ) đang tải kết quả, kết quả đó sẽ được phân phối tới đoạn cho tab bạn di chuyển đến (đoạn mới). Trong trường hợp của tôi, điều này dẫn đến một ClassCastException, vì kết quả mong đợi là các kiểu không tương thích.

Trong mã, các ý chính của tình trạng này là:

Máy bốc hàng:

public class FooLoader extends AsyncTaskLoader<Foo> 
public class BarLoader extends AsyncTaskLoader<Bar> 

Những mảnh vỡ: Mã quản lý

public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> { 
... 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); } 
... 
} 
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> { 
    ... 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
     super.onCreate(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); } 
    ... 
} 

Tab là như trong the aforementioned example. Có một tab thứ ba ở giữa các tab Foo và Bar (gọi là Baz). Khi chúng ta bỏ qua từ tab Foo đến tab Bar bằng cách nhấn vào tab Bar sau khi FooFragment đã gọi initLoader trên LoaderManager của nó nhưng trước khi FooFragment.onLoadFinished được gọi, chúng tôi kết thúc với một ClassCastException trên một cuộc gọi tới BarFragment.onLoadFinished:

java.lang.ClassCastException: com.example.Foo cannot be cast to com.example.Bar 
at com.example.BarFragment.onLoadFinished(BarFragment.java:1) 
at android.support.v4.app.LoaderManagerImpl$LoaderInfo.callOnLoadFinished(LoaderManager.java:427) 
at android.support.v4.app.LoaderManagerImpl.initLoader(LoaderManager.java:562) 
at com.example.BarFragment.onCreate(BarFragment.java:36) 
at android.support.v4.app.Fragment.performCreate(Fragment.java:1437) 
... 

Tại sao điều này xảy ra và làm cách nào để ngăn chặn? Nó trông giống như các bản ghi gỡ lỗi giống như LoaderManager đang được tái sử dụng trong đoạn Bar (mặc dù đoạn Baz có riêng của nó), nhưng tôi không biết tại sao điều đó lại xảy ra.

Cập nhật: Sử dụng ID trình tải khác nhau trong mỗi đoạn không loại bỏ sự cố (hoặc dường như - tôi thực sự không biết tại sao) nhưng tôi không muốn làm điều này. Trong một trong những mảnh tôi thực sự tạo ra ID động và không muốn giả định sẽ không có va chạm. Ngoài ra, giải pháp đó là lạ đối với tôi - ID trình tải phải là cục bộ cho từng đoạn (nếu không, tại sao tôi có thể có Trình tải có cùng ID trong các đoạn khác nhau trong các trường hợp bình thường?)

Có vẻ như tôi cũng có thể loại bỏ sự cố gọi setOffscreenPageLimit(2) trên ViewPager của tôi, để chế độ xem Foo không bị hủy khi chúng tôi chuyển sang chế độ xem Thanh. Nhưng đây là giải pháp thay thế, không phải là giải pháp chung.

Toàn bộ mã: Tôi đã tạo một example application demonstrating the error. Nó bao gồm một kịch bản monkeyrunner để buộc lỗi (mặc dù nó có thể không hoạt động cho tất cả các kích cỡ màn hình).

+0

Bạn có thể đăng toàn bộ mã cho một trong các trình tải không. – yarian

+1

Hãy thử chuyển cuộc gọi của bạn sang 'initLoader' thành' onActivityCreated' thay vì 'onCreate'. –

+0

Có vẻ như nó giải quyết được vấn đề. Cảm ơn! – jgiles

Trả lời

2

Bạn có thể tránh vấn đề này bằng cách gọi initLoader trong onActivityCreated hơn là trong onCreate - như đã nêu bởi Alex Lockwood trong các ý kiến ​​câu hỏi. Mã được sửa đổi bên dưới.

Những mảnh vỡ Corrected:

public class FooFragment extends Fragment implements LoaderManager.LoaderCallbacks<Foo> { 
... 
    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Foo> onCreateLoader(int id, Bundle args) { return new FooLoader(); } 
... 
} 
public class BarFragment extends Fragment implements LoaderManager.LoaderCallbacks<Bar> { 
    ... 
    @Override 
    public void onActivityCreated(Bundle savedInstanceState) { 
     super.onActivityCreated(savedInstanceState); 
     getLoaderManager().initLoader(0, null, this); 
    } 
    public Loader<Bar> onCreateLoader(int id, Bundle args) { return new BarLoader(); } 
    ... 
} 
+2

Tôi không nghĩ rằng điều này sẽ làm việc trong trường hợp bạn có một viewpager với các mảnh cùng loại. Trong trường hợp đó, viewpager sẽ phá hủy fragment và tái tạo nó sau khi bạn vuốt trở lại, tại thời điểm đó fragment sẽ không bao giờ nhận được một sự kiện onActivityCreated mà chỉ có onAttach(). IMHO bạn có thể cần ot đảm bảo rằng các id của trình tải khác nhau. Nếu bạn đang sử dụng cùng một phân đoạn nhiều lần trong cùng một hoạt động, bạn cần phải vượt qua các đối số để đảm bảo rằng các id trình tải không va chạm. – RaB

10

Không sử dụng 0 làm ID của bạn. Theo như các LoaderManager biết họ đang có nghĩa là ông cùng một bộ nạp.

Bạn có thể xác định ID duy nhất trong một tập tin tài nguyên XML

<item type="id" name="loader_foo" /> 
<item type="id" name="loader_bar" /> 

Và truy cập chúng từ R.

loaderManager.initLoader(R.id.loader_foo, null, new LoaderCallbacks(){}); 

Các tài liệu cho LoaderManager nói "định danh được scoped để một trường hợp đặc biệt LoaderManager". Và các trường hợp của LoaderMananger được gắn với Activities.

Để tạo ID duy nhất, bạn có thể chỉ định ID theo cách thủ công có khoảng trống lớn giữa chúng.

private static final int LOADER_FOO = 1; 
private static final int LOADER_BAR = 100; 

for(int i = 0; i < 10; ++i){ 
    loaderManager.initLoader(LOADER_FOO + i, null, new LoaderCallbacks(){}); 
} 
+0

Tôi có thể loại bỏ sự cố bằng cách sử dụng các ID khác nhau trong từng đoạn, nhưng không cần phải làm điều này và không muốn. (xem cập nhật cho câu hỏi gốc). – jgiles

+0

Xem chỉnh sửa. Chúc mừng. – alex

+0

Các ID tôi đang tạo không có bất kỳ ràng buộc cụ thể nào, vì vậy không có kích thước khoảng trống nào tôi có thể giả định sẽ đủ lớn. Thêm vào đó, điều đó vẫn cảm thấy như một cách giải quyết khác. – jgiles

0

Gần đây tôi đã làm việc với SimpleCursorAdapter và bộ tải để hiển thị dữ liệu từ các bảng Lite sql. Tôi đã dành một thời gian dài để gỡ lỗi "không có cột nào" _id "." xuất hiện trong tập 2 của tôi onLoadFinished(). Bảng đầu tiên của tôi có cột '_id' nhưng bảng thứ hai của tôi không làm được, điều này khiến tôi tin rằng Loader đang phân phối đến đoạn sai. Vì vậy, chỉ trong trường hợp bạn đã làm một cái gì đó tương tự, đảm bảo rằng các bảng sql của bạn có cột '_id' đầu tiên. Hy vọng điều này sẽ giúp bất cứ ai khác có cùng vấn đề với tôi.

Các vấn đề liên quan