2014-04-01 20 views
17

Tôi đang tạo một ứng dụng mẫu Android mới với một hoạt động và nhiều đoạn.Không thể thực hiện tác vụ này bên trong onLoadFinished

Lúc đầu, tôi bắt đầu "OverviewFragment" (Trang tổng quan của tôi) sẽ hiển thị một số thông tin cơ bản. Tổng quan của tôiFragment tải thông tin với một "AccountProvider" (ContentProvider). Nếu cơ sở dữ liệu là trống rỗng, các OverviewFragment nên thay thế bằng WelcomeFragment tôi ...

Dưới đây là một số mã:

public class OverviewFragment extends BaseFragment 
    implements LoaderManager.LoaderCallbacks { 

private static final int LOADER_ACCOUNTS = 10; 
private static final int LOADER_TRANSACTIONS = 20; 

@Override 
public void onActivityCreated(Bundle savedInstanceState) { 
    super.onActivityCreated(savedInstanceState); 

    getLoaderManager().initLoader(LOADER_ACCOUNTS, null, this); 
    //getLoaderManager().initLoader(LOADER_TRANSACTIONS, null, this); 
} 

@Override 
public Loader onCreateLoader(int id, Bundle args) { 
    Uri uri = null; 
    String[] projection = null; 
    String selection = null; 
    String[] selectionArgs = null; 
    String sortOrder = null; 

    switch (id) { 
     case LOADER_ACCOUNTS: 
      uri = WalletProvider.CONTENT_URI; 
      projection = new String[]{DBHelper.ACC_ID, DBHelper.ACC_TITLE}; 
      sortOrder = DBHelper.ACC_TITLE; 
      break; 
    } 
    CursorLoader cl = new CursorLoader(getActivity(), 
      uri, projection, selection, selectionArgs, sortOrder); 
    return cl; 
} 

@Override 
public void onLoadFinished(Loader loader, Object data) { 
    switch (loader.getId()) { 
     case LOADER_ACCOUNTS: 
      bindAccounts((Cursor) data); 
      break; 
    } 

} 

@Override 
public void onLoaderReset(Loader loader) { 

} 

private void bindAccounts(Cursor cursor) { 
    boolean showCreateWallet = true; 


    if (cursor != null && cursor.moveToFirst()) { 
     showCreateWallet = false; 
    } 

    if (showCreateWallet) { 
     listener.changeFragment(new WalletCreateFragment()); 
    } 
} 

và đây hoạt động chính của tôi

@Override 
public void changeFragment(Fragment fragmentToLoad) { 
    FragmentManager fragmentManager = getSupportFragmentManager(); 
    fragmentManager.beginTransaction() 
      .replace(R.id.container, fragmentToLoad) 
      .commit(); 
} 

Bây giờ ... nếu Tôi đang bắt đầu ứng dụng của mình với một cơ sở dữ liệu trống, tôi gặp lỗi bạn thấy trong tiêu đề ..

Tôi biết rằng tôi không nên thay đổi đoạn trong hàm onLoadFinished .... nhưng tôi có thể làm gì nó? : P

Xin lỗi vì tiếng anh của tôi =)

+1

thể trùng lặp của [Android - vấn đề sử dụng FragmentActivity + Loader để cập nhật FragmentStatePagerAdapter] (http://stackoverflow.com/questions/7746140/android-problems-using-fragmentactivity-loader-to-update-fragmentstatepagera) – rds

Trả lời

27

Đây là lỗi của Android. Để giải quyết việc này, sử dụng một Handler để thay thế/thêm/gọi một Fragment bên trong onLoadFinished như thế này:

final int WHAT = 1; 
Handler handler = new Handler(){ 
    @Override 
    public void handleMessage(Message msg) {      
     if(msg.what == WHAT) changeFragment(fragmentToLoad);    
    } 
}; 
handler.sendEmptyMessage(WHAT); 
+13

'handler.post (Runnable)' hoạt động tốt, và có vẻ ngắn gọn hơn. –

+0

lý do sendEmptyMessage? – Roel

+0

để gọi phương thức handleMessage – pablobaldez

8

Đây không phải là một lỗi, nhưng nó không phải là một vấn đề đơn giản. onLoadFinished có thể chạy bất cứ lúc nào, bao gồm sau khi trạng thái của hoạt động đã được lưu. Sự thay đổi đoạn kết quả sau đó sẽ không tự động được lưu bởi khung công tác. Bạn phải cho phép một cách rõ ràng này bằng commitAllowingStateLoss

Trong tài liệu của onLoadFinished:

Được gọi khi một bộ nạp tạo ra trước đó đã hoàn tất tải của nó. Lưu ý rằng thông thường một ứng dụng không được phép thực hiện các giao dịch phân đoạn trong khi thực hiện cuộc gọi này, vì nó có thể xảy ra sau khi trạng thái của hoạt động được lưu. Xem FragmentManager.openTransaction() để thảo luận thêm về điều này.

Trích dẫn Dianne Hackborn từ the thread referred to in the comment on the other answer:

Chỉ cần hiểu rằng nếu tình trạng đoạn của hoạt động đã được lưu, mã của bạn sẽ cần phải cập nhật một cách chính xác nó nếu nó sau này được khởi động lại từ nhà nước.

[...]

Đó là một kinh nghiệm người dùng xấu để hiển thị một hộp thoại (hoặc làm bất cứ sự thay đổi lớn khác trong UI) là kết quả của một bộ nạp. Đây là những gì bạn đang làm: thiết lập một số hoạt động để chạy trong nền cho một số lượng xác định thời gian, mà sau khi hoàn thành có thể ném một cái gì đó ở phía trước của người dùng yanking chúng ra khỏi bất cứ điều gì họ đang làm.

Đề xuất của tôi là hiển thị cho người dùng bất kỳ thông tin nào về kết quả bộ tải trong dòng giống như cách bạn hiển thị dữ liệu từ đó.

Nếu bạn (và người dùng của bạn) phù hợp với trải nghiệm người dùng, bạn có thể sử dụng commitAllowingStateLoss để cập nhật đoạn.

+0

Đây thực sự là câu trả lời đúng. Rõ ràng về các rủi ro sẽ loại bỏ sự cố, và nó làm cho nó rõ ràng rằng bạn đang chọn vào một cái gì đó mà có thể không hành xử chính xác như bạn dự định. –

1

Cả hai câu trả lời đều thiếu một giải pháp tốt.Đúng là không nên làm điều gì đó trên onLoadfinished vì hoạt động hoặc đoạn có thể đã bị phá hủy.

Nó cũng không phải là trải nghiệm người dùng tốt để cập nhật giao diện người dùng sau khi nội dung nào đó trên một chuỗi riêng biệt đã thay đổi vì chúng có thể sắp thực hiện hành động. Nhưng điều đó có thể được khắc phục bằng nút làm mới, sau đó cập nhật những gì người dùng nhìn thấy.

Tuy nhiên, có những trường hợp bạn có thể yêu cầu thực sự cập nhật giao diện người dùng. Ví dụ: nếu chúng ta cần thực hiện hành động yêu cầu người dùng chờ sau khi họ đã nhấn một nút tiếp theo.

Trong ví dụ của bạn, bạn mở rộng Callbacks trực tiếp bởi Fragment vì vậy bạn có thể tạo ra một phương pháp trên mảnh của bạn cho phép bạn thực hiện một cách an toàn một bản cập nhật giao diện người dùng như thế này:

private void update(Runnable runnable) { 
    View rootView = getView(); 
    if (isAdded() && rootView != null) { 
     rootView.post(runnable); 
    } 
} 

kiểm tra này cho dù đoạn là trong một trạng thái có thể xử lý các cập nhật giao diện người dùng và chạy lệnh. Nó có thể được sử dụng như thế này:

update(new Runnable() { 
    @Override 
    public void run() { 
     bindAccounts((Cursor) data); 
    } 
}) 

Điều đó nói rằng, nó không phải là một ý tưởng rất khôn ngoan để sử dụng mảnh của bạn như là callbacks vì nó có thể bị rò rỉ đoạn của bạn trong LoaderManager mà sống trên hoạt động.

Một cách an toàn khác để xử lý những điều này là với máy thu phát sóng (đảm bảo chúng không được đăng ký).

Tuy nhiên, lựa chọn an toàn nhất sẽ được sử dụng các thành phần kiến ​​trúc mới được công bố dành cho Android khi họ đã sẵn sàng cho sản xuất:

https://developer.android.com/topic/libraries/architecture/index.html

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