屏幕旋转时加载器重新启动

18

http://developer.android.com/guide/components/loaders.html中的Android加载器文档中,它指出加载器的一个特性是:

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

以下代码似乎没有反映出这种行为,一个新的加载器被创建并完成查询ContentResolver,然后我旋转屏幕,加载器被重新创建!

public class ReportFragment extends Fragment implements LoaderCallbacks<Cursor> {

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        getLoaderManager().initLoader(1, null, this);
    }

    @Override
    public View onCreateView(LayoutInflater inflater, ViewGroup container,
            Bundle savedInstanceState) {
        View v = inflater.inflate(R.layout.fragment_report, container, false);
        return v;
    }

    public Loader<Cursor> onCreateLoader(int arg0, Bundle arg1) {
        Log.d("TEST", "Creating loader");
        return new CursorLoader(getActivity(), ResourcesContract.Reports.CONTENT_URI, null, null, null, null);
    }

    public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
        Log.d("TEST", "Load finished");
    }

    public void onLoaderReset(Loader<Cursor> arg0) {

    }

}

以下是我的logcat输出:
08-17 16:49:54.474: D/TEST(1833): Creating loader
08-17 16:49:55.074: D/TEST(1833): Load finished
*Here I rotate the screen*
08-17 16:50:38.115: D/TEST(1833): Creating loader
08-17 16:50:38.353: D/TEST(1833): Load finished

请问我在这里做错了什么?

编辑:

我应该指出,我正在构建到Android Google API的版本8,并使用v4支持库。

第二次编辑:

这很可能是由于支持库中的一个bug导致的,如果您想要更多信息,请查看此错误提交:

http://code.google.com/p/android/issues/detail?id=20791&can=5&colspec=ID%20Type%20Status%20Owner%20Summary%20Stars


1
请查看此帖子以获取更多信息:http://www.androiddesignpatterns.com/2012/08/implementing-loaders.html - Alex Lockwood
2
此外,你应该在 onActivityCreated 中调用 getLoaderManager()... 如果在 Fragment 第一次创建时 Activity 仍然为 null,你当前的代码将抛出一个 IllegalStateException - Alex Lockwood
1
@AlexLockwood,在你的例子中(做得很好!)你正在使用support-v4:18.0.0。当前版本是23.0.1,它包含了这个bug。我测试过的最新可用版本是22.0.0。 - Dzwiedziu-nkg
1
我将其作为错误提交:https://code.google.com/p/android/issues/detail?id=187391 支持库最新版本是22.2.1,可以正常工作。 - Dzwiedziu-nkg
6个回答

1
在我看来,你误解了文档的意思。文档说的是它们不需要重新查询数据,也没有这样做。
尝试在ContentProvider#query()方法中记录/插入断点!查询仅在Activity启动时调用,而不是在方向更改后调用。
但这对于LoaderCallbacks#onCreateLoader()方法并非如此。它将在每次方向更改后调用,但这并不意味着重新查询,它只是调用该方法,因此您可以更改CursorLoader(如果需要)。

是的。在每次旋转时都会创建一个新的CursorLoader实例(这是一个相当轻量级的操作),但不会创建新的Cursor实例(这将需要潜在的重量级数据库查询)。对游标保持引用,并且新的加载器“重新连接”到它。 - HendrikFrans

1
尽管这是一个老问题,我一直遇到与 OP 相同的问题。使用加载器时,我需要在导航到新活动后重新启动它,然后返回。但同时,我不希望加载器在旋转手机屏幕时重新启动。
我发现,在调用其超类之前重新启动加载器可以在 onRestart() 中实现此目的。
public class MainActivity extends AppCompatActivity implements
LoaderManager.LoaderCallbacks<Cursor> {

    ...

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        ...

        //Initialize the loader.
        getSupportLoaderManager().initLoader(0, null, this);
    }

    @Override
    protected void onRestart() {
        //Restart the loader before calling the super.
        getSupportLoaderManager().restartLoader(LOADER_ID, null, this);

        super.onRestart();
    }

    ...

}

+1 谢谢 :) 这对我的问题很有帮助 :) 我正在使用 AdapterViewFlipper,并遵循您的建议来管理我的加载器,现在翻转器不再受屏幕旋转的影响 :) - Nikolai Kanchev

0

虽然这是一个有点老的问题,但我想在这里表达我的观点。

在onSaveInstanceState中没有必要存储额外的信息。

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

这意味着在onCreate函数中,只有在savedInstanceState为null时才需要调用loaderManager。

例如:

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if(savedInstanceState == null) {
        getLoaderManager().initLoader(1, null, this);
    }
}

0
到目前为止,我发现保留片段 Fragment.setRetainInstance(true) 可以防止使用支持库在方向更改时重新创建加载器。加载器的最后结果可以在 onLoadFinished() 中很好地传递。它至少在活动管理单个片段并且片段使用 FragmentTransaction 添加到活动时有效。

-1

你可以在onCreate中检查加载器是否已经存在,然后可以初始化或重新启动。

@Override
public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    if (getLoaderManager().getLoader(LOADER_ID) == null) {
        getLoaderManager().initLoader(LOADER_ID, null, this);
    } else {
        getLoaderManager().restartLoader(LOADER_ID);
    }
}

通常你需要向加载器传递一个 ID,这样你之后才能通过加载器管理器引用它。
希望这能有所帮助!

嗨,尼尔森,如果已经存在一个加载器,这将重新启动它,这有点违背了它的目的。即使你交换了两个语句(init和restart),结果也会与一开始调用initLoader的行为相同! - soren.qvist
2
getLoaderManager().restartLoader(LOADER_ID); 的作用与整个 "if() {} else {}" 语句完全相同,因为如果 Loader 不存在,restartLoader 就会执行完全相同的操作。 - Emanuel Moecklin

-3

由于活动被销毁并重新创建,因此在屏幕方向更改期间调用onCreate()。

除非您正在加载大量数据,否则不会有影响,但如果您想尝试以下操作(我没有测试过,但理论上我认为它会起作用),可以尝试以下操作。

在类的顶部全局声明一个静态布尔值。我认为您还需要一个静态游标来引用。

private static boolean dataDownloaded = false;
private static Cursor oldCursor;

然后在 onLoadFinished 中设置 dataDownloaded = true

重写 onSaveInstanceState 以保存布尔值

@Override
protected void onSaveInstanceState(Bundle outSave) {
    outSave.putBoolen("datadownloaded", dataDownloaded);
    oldCursor = adapter.swapCursor(null);
}

在onCreate中添加以下内容。
if (savedInstanceState != null) {
    this.dataDownloaded = savedInstanceState.getBoolean("datadownloaded", false);
}

调整你的onCreateLoader

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    CursorLoader cursorLoader;
    if (dataDownloaded) {
        cursorLoader = new CursorLoader(getActivity(),
            null, projection, null, null, null);
        cursorLoader.deliverResult(oldCursor);
    } else {
        CursorLoader cursorLoader = new CursorLoader(getActivity(),
            URI_PATH, projection, null, null, null);
    }

    return cursorLoader;
}

这个查询不管 dataDownloaded 是 true 还是 false,ContentResolver 都会被查询吗?不过,我认为这实际上是由于支持库中的一个 bug 导致的,请参阅此帖子获取更多信息:https://groups.google.com/forum/?fromgroups#!topic/android-developers/DbKL6PVyhLI%5B1-25%5D - soren.qvist
我进行了调整,如果数据已经下载,则URI路径应该为“null”。 - ainesophaur
现在有意义了。好的,就我所看到的,这是一个解决方案,因此我会将其标记为解决方案。感谢您的回答。 - soren.qvist

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