为啥片段恢复后再次调用onLoadFinished?

Posted

技术标签:

【中文标题】为啥片段恢复后再次调用onLoadFinished?【英文标题】:Why is onLoadFinished called again after fragment resumed?为什么片段恢复后再次调用onLoadFinished? 【发布时间】:2014-01-28 16:20:37 【问题描述】:

我对装载机有一个特殊的问题。目前我不确定这是我的代码中的错误还是我误解了加载程序。

应用程序

问题出现在对话中(想象一下类似于 Whatsapp 的东西)。 我使用的加载器是基于AsyncTaskLoader example 实现的。我正在使用支持库。

在 OnCreate 中,我启动了一个加载程序来检索缓存的消息。 当 CachedMessageLoader 完成时,它会启动 RefreshLoader 以检索(在线)最新消息。 每个加载器类型都有一个不同的 ID(例如,离线:1 在线:2)

这很好用,但有以下例外。

问题

当我打开另一个片段(并将事务添加到后台堆栈)然后使用返回键返回到会话片段时,onLoadFinished 再次调用 both 之前的结果。 这个调用发生在片段有机会再次启动加载器之前......

我之前获得的这种“旧”结果的传递会导致重复消息。

问题

为什么会再次提供这些结果? 我是不是用错了这些加载器? 我可以“无效”结果以确保我只将它们交付一次,还是我必须自己消除重复?

调用堆栈跟踪

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 我的 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();

【问题讨论】:

能否添加操作片段和加载器的Activity代码? 嗨@a.ch。感谢您的输入。我在启动加载程序的片段中添加了精简版本的代码......我将尝试在一分钟左右添加与启动片段相关的代码。 【参考方案1】:

还有其他类似的问题,例如android: LoaderCallbacks.OnLoadFinished called twice 但是加载器管理器挂钩的行为就是它们的样子。您可以在获得第一组结果后销毁加载程序

public abstract void destroyLoader (int id)

或者您可以处理 onLoaderReset 并将您的 UI 数据与加载器数据更紧密地联系起来

public abstract void onLoaderReset (Loader<D> loader)

在重置先前创建的加载程序时调用,因此 使其数据不可用。应用程序此时应 删除它对加载程序数据的任何引用。

就个人而言,我会为此使用 ContentProvider 和 CursorLoader(每行数据都需要有一个唯一的 _ID,但对于不应该成为问题的消息)。

【讨论】:

您能否详细说明这些回调的行为?该文档仅说明 onLoadFinished 是“在先前创建的加载程序完成加载时调用的”。 - 但并不是说这可以任意经常发生或(另外)在生命周期中的某个时间点发生。我是否正确解释了您的答案(如果我使用加载器从 Web 服务轮询新数据),一旦我使用(并存储)数据,我将不得不明确地销毁加载器? 该文档还声明“保证在发布为此加载程序提供的最后一个数据之前调用此函数。”在 onResume 期间,加载程序在 onResume 期间释放其数据并重新加载是完全合理的。是的,如果您看到加载器可能回调的行为并且您不希望该回调,则销毁加载器。 调用 destroyLoader 确实解决了我的问题,谢谢。你让我意识到我并不像我想象的那样熟悉这些回调。 调用destroyLoader()也是在销毁游标,有没有办法销毁loader但保留游标??【参考方案2】:

在 onResume() 中使用下面这个

 @Override
public void onResume() 
    super.onResume();
    getLoaderManager().initLoader(0, null, this);

我由此解决了我的问题 this is same type Q

【讨论】:

以上是关于为啥片段恢复后再次调用onLoadFinished?的主要内容,如果未能解决你的问题,请参考以下文章

在 Activity 内部,如何暂停 for 循环以调用片段,然后在按钮单击片段后恢复循环以重新开始

通过底部导航栏更改片段时恢复片段状态

未调用加载程序 onLoadFinished()

恢复后停止重新创建片段?

ViewPager PagerAdapter with cursor - CursorLoader.onLoadFinished 不会被不同的查询调用

为啥我的 Activity 在恢复时再次从顶部运行而不是啥都不做?