从 Fragment 内的 AsyncTask 重新启动 Loader (SQLite) 时出现 IllegalStateException

Posted

技术标签:

【中文标题】从 Fragment 内的 AsyncTask 重新启动 Loader (SQLite) 时出现 IllegalStateException【英文标题】:IllegalStateException when restarting Loader (SQLite) from an AsyncTask inside of a Fragment 【发布时间】:2014-03-28 01:02:20 【问题描述】:

我注意到我的分析报告中有一些 IllegalStateException 崩溃,我似乎无法重现。我在这里做一些结构上不正确的事情吗?我已经包含了一些伪代码,希望可以显示我的片段的骨架而不会太杂乱。在我重构代码以使用片段之前,我不记得看到过这个错误。错误发生在 AsyncTask 的onPostExecute 中。如果我能更好地澄清我的问题/伪代码,请告诉我。

public class MyListFragment extends ListFragment 
    implements LoaderManager.LoaderCallbacks<Cursor> 

    Fragment fragment = this;
    SimpleCursorAdapter adapter;

    // onActivityCreated initializes the adapter and the loader

    public AsyncTaskLoader<Cursor> onCreateloader(int id, Bundle bundle) 
        return new CustomCursorLoader(this);
    

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) 
        adapter.swapCursor(data);
    

    public void onLoaderReset(Loader<Cursor> loader) 
        adapter.swapCursor(null);
    

    private class CustomCursorLoader extends CursorLoader 
        public CustomCursorLoader(Fragment fragment) 
            // get a handle to the OrmLite database helper class
            databaseHelper = (DatabaseHelper) OpenHelperManager.getHelper(fragment.getActivity(), DatabaseHelper.class);
        

        public Cursor loadInBackground() 
            // construct the Cursor that will be used for loading results from the SQLite database
        
    

    private class Task extends AsyncTask 
        // pre execute

        // grab some new database results from the server and repopulate the SQLite database using OrmLite

        public void onPostExecute() 
            fragment.getLoaderManager().restartLoader(LOADER_ID, null, fragment);
        
    

更新

我能够通过启动Task 然后立即回击来重现该问题。当Task 完成时,包含该片段的活动未处于活动状态,因此对getLoaderManager 的调用失败。现在的问题是,处理这个问题的最优雅的方法是什么?我想我可以抓住异常。

更新 2

我相信我可以利用cancel 来阻止onPostExecute 执行。如果我这样做,我可能需要将 initLoader 移动到 onActivityCreated 以外的其他方法,以便在用户返回页面时刷新加载器(因为更新了结果,而不是加载器)。

更新 3

我想知道,就像 Robby Pond 在 cmets 中提到的那样,在这里使用 Loader 是否过大?

【问题讨论】:

请发布 logcat 输出(stacktrace)以获取更多信息 @stan0 我希望我可以。我在分析中使用EasyTracker,所以我得到的只是异常名称和行号。我似乎无法在本地重现该问题。 你什么时候创建和执行任务? @RobbyPond 如果数据库中当前没有结果,则在onActivityCreated 中。否则,在onOptionsItemSelected 时点击刷新操作栏项。 我不认为我们也可以用代码量。为什么要同时使用 Loader 和 AsyncTask 来查询数据库? 【参考方案1】:

我对您的案例的一些想法:

    Loader 存在各种问题,尤其是与片段一起使用时。我可以建议您使用 ViewModel,但我认为它们都基于相同的代码。您可以使用普通线程,然后使用 EventBus 之类的东西来发布结果,但这可能有点过头了

    我可以看到您忘记将类设置为静态,这意味着它们具有对托管类的引用。

    是否可以将异步代码合并到一个 AsyncTaskLoader 中?

    您可以尝试我使用 AsyncTaskLoader 的一般解决方案,here

    也许不是重新启动,而是先销毁,然后再次调用 initLoader。

    AsyncTask 只应在小情况下使用,在这种情况下您不关心结果是否消失(例如,当您改变方向时)。一个很好的例子是要显示的项目列表。

    我认为 Google 在 Google IO 2018 上提到,Loaders 的问题将得到修复。不确定是在支持库 28 中还是在 androidX 库中。我建议检查一下。要记住的主要事情是只在 UI 线程上使用它。

【讨论】:

以上是关于从 Fragment 内的 AsyncTask 重新启动 Loader (SQLite) 时出现 IllegalStateException的主要内容,如果未能解决你的问题,请参考以下文章

Asynctask结果显示重新创建片段后

使用 recyclerview 和 Asynctask 设置 Viewpager

打开新的 Fragment 和 AsyncTask 时显示 ProgressDialog

自定义对话框片段内的进度条 - 如何从 AsyncTask 传递进度?

使用 asynctask 在所有 Fragment 中的 Activity 中显示 ListView

使用 AsyncTask 在 Fragment 的 RecyclerView 中显示来自 json 的数据?