无法在onLoadFinished中执行此操作

时间:2014-04-01 14:20:22

标签: android android-fragments

我正在创建一个包含一个活动和多个片段的新示例Android应用。

首先,我开始了#34; OverviewFragment" (我的仪表板)应该显示一些基本信息。我的OverviewFragment使用" AccountProvider"加载信息。 (内容提供商)。如果数据库为空,则应将OverviewFragment替换为我的WelcomeFragment ...

以下是一些代码:

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());
    }
}

这是我的主要活动

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

现在......如果我使用空数据库启动我的应用程序,我会收到您在标题中看到的错误..

我知道我不应该改变onLoadFinished函数中的片段....但我在哪里可以做到这一点? :P

抱歉我的英文=)

3 个答案:

答案 0 :(得分:30)

这是android的错误。要解决这个问题,请使用Handler来替换/添加/调用onLoadFinished中的Fragment,如下所示:

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

答案 1 :(得分:9)

这不是一个错误,但它不是一个简单的问题。 onLoadFinished可以随时运行,包括在保存活动状态之后。然后,框架将不会自动保存生成的片段更改。您必须使用commitAllowingStateLoss

明确允许此操作

onLoadFinished的文档中:

  

在先前创建的加载程序完成其加载时调用。请注意,通常,在此调用中,不允许应用程序提交片段事务,因为它可能在保存活动状态后发生。有关此内容的进一步讨论,请参阅FragmentManager.openTransaction()。

the thread referred to in the comment on the other answer引用Dianne Hackborn:

  

请理解,如果活动的片段状态已经保存,如果稍后从状态重新启动,您的代码将需要正确更新它。

     

[...]

     

作为加载器的结果,显示对话框(或在UI中执行任何其他重大转变)是一种糟糕的用户体验。这就是你正在做的事情:将一些操作设置为在后台运行一段不确定的时间,一旦完成,可能会在用户面前抛出一些东西,将它们从他们正在做的事情中拉出来。

     

我的建议是以与显示数据相同的方式向用户显示有关加载器结果的任何信息。

如果您(和您的用户)对用户体验感到满意,您可以使用commitAllowingStateLoss更新片段。

答案 2 :(得分:2)

两个答案都缺少一个好的解决方案。确实,在onLoadfinished上做某事并不是一个好主意,因为活动或片段可能已被破坏。

在单独的线程上的某些内容发生更改之后更新UI也不是一个好的用户体验,因为它们可能即将执行操作。但是可以通过刷新按钮修复,然后更新用户看到的内容。

但是,在某些情况下您可能需要实际更新UI。例如,如果我们需要执行一个操作,要求用户在按下 next 按钮后等待。

在您的示例中,您直接通过Callbacks扩展Fragment,这样您就可以在片段上创建一个方法,允许您安全地执行这样的UI更新:

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

检查片段是否处于可以处理UI更新并运行命令的状态。它可以像这样使用:

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

也就是说,使用你的片段作为回调并不是一个明智的想法,因为它可能会泄漏活动中存在的LoaderManager中的片段。

处理这些问题的另一种安全方法是使用广播接收器(确保它们未注册)。

然而,最安全的选择是在准备生产时使用新发布的Android架构组件:

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