方向改变后无法重新启动加载程序

9
我将用一个演示来展示这个问题:
enter code here
@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.main);

    Button button = (Button) findViewById(R.id.button1);
    button.setOnClickListener(buttonClickListener);
}

private OnClickListener buttonClickListener = new OnClickListener() {

    @Override
    public void onClick(View v) {
        // TODO Auto-generated method stub
        startMyLoader();
    }

};

private void startMyLoader() {
    getLoaderManager().destroyLoader(0);
    getLoaderManager().restartLoader(0, null, myLoaderListener);
}

/**
 * The listener for the group metadata loader.
 */
private final LoaderManager.LoaderCallbacks<Cursor> myLoaderListener 
    = new LoaderCallbacks<Cursor>() {

    @Override
    public CursorLoader onCreateLoader(int id, Bundle args) {
        return new CursorLoader(LoaderDemoActivity.this, 
        ContactsContract.Contacts.CONTENT_URI, 
        null, null, null, null);
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        cursor.moveToPosition(-1);
        if (cursor.moveToNext()) {
            Context context = getApplicationContext();
            CharSequence text = "Load finished!";
            int duration = Toast.LENGTH_SHORT;

            Toast toast = Toast.makeText(context, text, duration);
            toast.show();
        }
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }
};
enter code here

方向改变后,我点击了按钮, onCreateLoader方法被调用了, 但是onLoadFinished方法没有被调用。

这看起来很奇怪。

提前感谢您的帮助。

5个回答

14

我遇到了同样的问题。请尝试在onCreate中调用this.getSupportLoaderManager()。这解决了我的问题。希望它也能帮到你。


1
非常感谢!那真的很有帮助!我所需要做的就是在我的onCreate中调用这个方法,我的加载器就可以在屏幕方向改变时继续工作了! - devmiles.com
1
我的 CursorLoader 位于一个 fragment 中。我发现必须从拥有该 fragment 的 activity 的 onCreate 方法中调用 getLoaderManager() 方法。 - daveywc
1
谢谢!它救了我的一天!对于标准的LoaderManager也是如此。如果您在onCreate()中调用getLoaderManager()来初始化它,即使您不需要它,它也会更好地工作。 - philips77

5
我想我已经找到原因了。
在Activity的onCreate方法中,它将从NonConfigurationInstances加载其自身或子Fragment的所有LoaderMangers。
    if (mLastNonConfigurationInstances != null) {
        mAllLoaderManagers = mLastNonConfigurationInstances.loaders;
    }

在Activity的onStart方法中,它将尝试启动自己的LoaderManger。

    if (!mLoadersStarted) {
        mLoadersStarted = true;
        if (mLoaderManager != null) {
            mLoaderManager.doStart();
        } else if (!mCheckedForLoaderManager) {
            mLoaderManager = getLoaderManager(-1, mLoadersStarted, false);
        }
        mCheckedForLoaderManager = true;
    }

但是在配置更改后,mLoaderManager == null,因此它将无法启动。 这就是问题所在! 如果您尝试启动属于此loaderManager的loader,它将失败。

void installLoader(LoaderInfo info) {
    mLoaders.put(info.mId, info);
    if (mStarted) {
        // The activity will start all existing loaders in it's onStart(),
        // so only start them here if we're past that point of the activitiy's
        // life cycle
        info.start();
    }
}

请注意mStarted值,当LoaderManager启动时将设置为'true'。

解决此问题有两种方法。

  1. 在onCreate()中调用getLoaderManger(),它将重新分配mLoaderManager并准备在随后的onStart()中启动。

    public LoaderManager getLoaderManager() {     if (mLoaderManager!= null) {         返回mLoaderManager;     }     mCheckedForLoaderManager = true;     mLoaderManager = getLoaderManager(-1,mLoadersStarted,true);     返回mLoaderManager; }

  2. 将加载器放置在片段中。因为在片段的onStart()中,它会启动自己的LoaderManager。

    if(!mLoadersStarted){     mLoadersStarted = true;     if(!mCheckedForLoaderManager){         mCheckedForLoaderManager = true;         mLoaderManager = mActivity.getLoaderManager(mIndex,mLoadersStarted,false);     }     if(mLoaderManager!= null){         mLoaderManager.doStart();     } }


但是在配置更改后,mLoaderManager == null,因此它将不会启动。如果是这样,当调用getLoaderManager().destroyLoader(0);时,您将获得NullPOinterException。您可能没有理解重点;),使用forceLoad而不是每次销毁-重新创建加载器,因为这违反了其设计原则。 - Tomasz Gawel
如果mLoaderManager为null,那么它将由“getLoaderManager(-1, mLoadersStarted, false)”赋值。因此,它不会抛出NullPointerException。我必须重新创建加载器,因为创建时的某些内容(例如URI)需要更改。 - user1335719
这绝对是一个 bug。查看 LoaderManagerImpl 中的源代码,它的 onRetain 将 mStarted 设置为 false。唯一将 mStarted 设置回 true 的地方是 doStart()。如果 doStart() 没有被调用,任何新创建的加载器都不会被启动(请参见 LoaderManagerImpl.installLoader())。仅仅在 Activity.onStart() 被调用之前调用 Acticity.getLoaderManager(),似乎表明这只是一个状态 bug。我通过在重写的 Activity.onStart 中添加一个调用 getLoaderManager(),然后再调用 super.onStart() 来解决我的问题。 - SurlyDre

3
您不需要(也不应)销毁您的Loader来重新加载它。 Loader类旨在被重复使用。
请改用initLoader。例如:
getLoaderManager().initLoader(0, null, myLoaderListener);

如果您想强制重新加载已经注册的加载器:
getLoaderManager().getLoader(0).forceLoad();

如果您不确定在配置更改事件发生后是否存在 Loader 实例,请使用 initLoader 而不是 getLoader 来检索您的 Loader 实例,您可以在该实例上调用forceLoad()

getLoaderManager()。initLoader(0,null,myLoaderListener)。forceLoad();

如果您使用支持库,则即使在第一次实例化之后也要使用 forceLoad - 可能存在错误-我提醒自己这个论坛上有一些关于它的问题 - 尝试搜索旧帖子。


我认为如果你打算使用loaderManager来控制所有内容,那么手动调用forceLoad()并不是正确的方式。 - user1335719
@user1335719 没有这样的限制,只需从进程的主线程中调用。并且必须启动加载器(检查 isStarted()),在使用 initLoader 检索后很明显。startLoading()stopLoading 方法是内部使用的,而不是 forceLoad - Tomasz Gawel

1

在调用活动onCreate中的加载器之前,确保您不会在使用片段时检查savedStateInfo

@Override
public void onCreate(Bundle savedInstanceState) {

    // used to not overlap fragments
    if (savedInstanceState != null) {
        return null;
    }

    loadFragments();

    getSupportLoaderManager().restartLoader(LISTS_LOADER, null, this);
}

如果您需要检查savedInstanceState片段,您可以检查任何在加载器完成加载后应该创建的类变量,因为当旋转时活动会被销毁,但从先前状态中升起时会重新启动。


0

来自Android开发网站

"当在配置更改后重新创建时,它们会自动重新连接到上一个加载器的光标。因此,它们不需要重新查询其数据。"

据我所知,即使我们明确启动加载器,加载器也不会启动。因为我们调用的销毁应该实际上在销毁时调用onLoaderReset()。但是这个方法在方向改变之前被调用,而不是之后。

我可能还有错误。这是我的假设。欢迎进一步讨论。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接