14

ローダーに特有の問題があります。現在、これが私のコードのバグなのか、ローダーを誤解しているのかはわかりません。

アプリ

この問題は会話で発生します (Whatsapp に似たものを想像してください)。私が使用するローダーは、 AsyncTaskLoader の例に基づいて実装されています。サポートライブラリを使用しています。

  • OnCreate では、キャッシュされたメッセージを取得するローダーを開始します。
  • CachedMessageLoader が終了すると、RefreshLoader を開始して最新のメッセージを (オンラインで) 取得します。
  • 個別の ID としての各ローダー タイプ (たとえば、offline:1 online:2)

これは、次の例外を除いて、非常にうまく機能します。

問題

別のフラグメントを開き (そしてトランザクションをバックスタックに追加し)、Back-Key を使用して conversationFragment に戻ると、以前の両方の結果でonLoadFinished再度呼び出されます。この呼び出しは、フラグメントがローダーを再度開始する前に発生します...

以前に取得した「古い」結果を配信すると、メッセージが重複します。

質問

  • それらの結果が再度配信されるのはなぜですか?
  • これらのローダーを間違って使用していますか?
  • 結果を「無効化」して、結果が 1 回だけ配信されるようにすることはできますか? それとも、自分で重複を排除する必要がありますか?

呼び出しのスタック トレース

MyFragment.onLoadFinished(Loader, Result) line: 369 
MyFragment.onLoadFinished(Loader, Object) line: 1   
LoaderManagerImpl$LoaderInfo.callOnLoadFinished(Loader, Object) line: 427   
LoaderManagerImpl$LoaderInfo.reportStart() line: 307    
LoaderManagerImpl.doReportStart() line: 768 
MyFragment(Fragment).performStart() line: 1511  
FragmentManagerImpl.moveToState(Fragment, int, int, int, boolean) line: 957 
FragmentManagerImpl.moveToState(int, int, int, boolean) line: 1104  
BackStackRecord.popFromBackStack(boolean) line: 764 
...

更新 1 ここで言及されているローダーは、会話フラグメントによって開始されます。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setHasOptionsMenu(true);
    Bundle args = getArguments();
    m_profileId = args.getString(ArgumentConstants.ARG_USERID);
    m_adapter = new MessageAdapter(this);

    if (savedInstanceState != null) {
        restoreInstanceState(savedInstanceState);
    }
    if (m_adapter.isEmpty()) {
        Bundle bundle = new Bundle();
        bundle.putString(ArgumentConstants.ARG_USERID, m_profileId);
        getLoaderManager().restartLoader(R.id.loader_message_initial, bundle, this);
    } else {
        // Omitted: Some arguments passed in Bundle
        Bundle b = new Bundle(). 
        getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this);
    }
}

@Override
public void onResume() {
    super.onResume();
    // Omitted: setting up UI state / initiating other loaders that work fine
}

@Override
public AbstractMessageLoader onCreateLoader(final int type, final Bundle bundle) {
    final SherlockFragmentActivity context = getSherlockActivity();
    context.setProgressBarIndeterminateVisibility(true);
    switch (type) {
        case R.id.loader_message_empty:
            return new EmptyOnlineLoader(context, bundle);
        case R.id.loader_message_initial:
            return new InitialDBMessageLoader(context, bundle);
        case R.id.loader_message_moreoldDB:
            return new OlderMessageDBLoader(context, bundle);
        case R.id.loader_message_moreoldOnline:
            return new OlderMessageOnlineLoader(context, bundle);
        case R.id.loader_message_send:
            sendPreActions();
            return new SendMessageLoader(context, bundle);
        case R.id.loader_message_refresh:
            return new RefreshMessageLoader(context, bundle);
        default:
            throw new UnsupportedOperationException("Unknown loader");
    }
}

@Override
public void onLoadFinished(Loader<Holder<MessageResult>> loader, Holder<MessageResult> holder) {
    if (getSherlockActivity() != null) {
        getSherlockActivity().setProgressBarIndeterminateVisibility(false);
    }
    // Omitted: Error handling of result (can contain exception)
    List<PrivateMessage> unreadMessages = res.getUnreadMessages();
    switch (type) {
        case R.id.loader_message_moreoldDB: {
            // Omitted error handling (no data)
            if (unreadMessages.isEmpty()) {
                m_hasNoMoreCached = true;
                // Launch an online loader
                Bundle b = new Bundle();
                // Arguments omitted
                getLoaderManager().restartLoader(R.id.loader_message_moreoldOnline, b, ConversationFragment.this);
            }
            // Omitted: Inserting results into adapter
        }
        case R.id.loader_message_empty: { // Online load when nothing in DB
            // Omitted: error/result handling handling
            break;
        }
        case R.id.loader_message_initial: { // Latest from DB, when opening
            // Omitted: Error/result handling

            // If we found nothing, request online
            if (unreadMessages.isEmpty()) {
                 Bundle b = new Bundle();
                 // Omitted: arguments
                 getLoaderManager().restartLoader(R.id.loader_message_empty, b, this);
             } else {
                // Just get new stuff
                Bundle b = new Bundle();
               // Omitted: Arguments
               getLoaderManager().restartLoader(R.id.loader_message_refresh, b, this);
            }
            break;
        }
        // Omitted: Loaders that do not start other loaders, but only add returned data to the adapter
        default:
            throw new IllegalArgumentException("Unknown loader type " + type);
    }
    // Omitted: Refreshing UI elements
}

@Override
public void onLoaderReset(Loader<Holder<MessageResult>> arg0) { }

更新 2 My MainActivity (最終的にすべてのフラグメントをホストする) は、SherlockFragmentActivity をサブクラス化し、基本的に次のようなフラグメントを起動します。

    Fragment f = new ConversationFragment(); // Setup omitted
    f.setRetainInstance(false);
    // Omitted: Code related to navigation drawer
    FragmentManager fragmentManager = getSupportFragmentManager();
    fragmentManager.beginTransaction().replace(R.id.fragment_container_frame, f).commit();

会話フラグメントは、次のように「表示プロファイル」フラグメントを開始します。

DisplayProfileFragment f = new DisplayProfileFragment();
// Arguments omitted
FragmentManager manager = getSherlockActivity().getSupportFragmentManager();
FragmentTransaction transaction = manager.beginTransaction();
transaction.replace(R.id.fragment_container_frame, f).addToBackStack(null).commit();
4

2 に答える 2

8

Androidなど、他にも同様の質問があります。最初の結果セットを取得した後、ローダーを破棄することができます

public abstract void destroyLoader (int id)

または、onLoaderReset を処理して、UI データをローダー データにより密接に結び付けることができます。

public abstract void onLoaderReset (Loader<D> loader)

以前に作成されたローダーがリセットされ、そのデータが使用できなくなったときに呼び出されます。この時点で、アプリケーションはローダーのデータへの参照をすべて削除する必要があります。

個人的には、これには ContentProvider と CursorLoader を使用します (データの各行には一意の _ID が必要ですが、問題にならないメッセージの場合)。

于 2014-01-28T23:28:04.980 に答える