无法访问主线程上的数据库 - Android Room - 使用 ThreadPoolExecutor

Posted

技术标签:

【中文标题】无法访问主线程上的数据库 - Android Room - 使用 ThreadPoolExecutor【英文标题】:Cannot access database on the main thread - Android Room - Using ThreadPoolExecutor 【发布时间】:2017-11-20 14:09:55 【问题描述】:

我收到了那个著名的错误"Cannot access database on the main thread since it may potentially lock the UI for a long periods of time" 但据我了解,我没有在主线程中访问数据库,因为我在由 ThreadPoolExecutor 执行的 Runnable 中执行调用。我做错了什么?

在下面的方法中,我使用一个runnable从网络获取数据并将其存储在本地数据库中。

private void refresh() 
    executor.execute(() -> 
        if (!dataSource.hasData()) 
            recipeService.getAllRecipes().enqueue(new Callback<List<Recipe>>() 
                @Override
                public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) 
                    dataSource.save(response.body());
                

                @Override
                public void onFailure(Call<List<Recipe>> call, Throwable t) 
                    throw new RuntimeException(t);
                
            );
        

    );

DataSource.save:

@Override
    public void save(List<Recipe> recipes) 
        for (Recipe recipe : recipes) 
            recipeDao.insert(recipe);
            int count = 1;

            for (Ingredient ingredient : recipe.getIngredients()) 
                ingredient.setId(count++);
                ingredient.setRecipeId(recipe.getId());
                ingredientDao.insert(ingredient);
            

            for (Step step : recipe.getSteps()) 
                step.setRecipeId(recipe.getId());
                stepDao.insert(step);
            

        
    

执行者定义为:

@Provides
Executor executor() 
    return new ThreadPoolExecutor(4, 8, 60,
            TimeUnit.SECONDS, new LinkedBlockingQueue<>());

我得到的错误:

06-18 03:03:38.653 27231-27231/com.github.alexpfx.udacity.nanodegree.android.baking_app E/AndroidRuntime: FATAL EXCEPTION: main
Process: com.github.alexpfx.udacity.nanodegree.android.baking_app, PID: 27231
java.lang.IllegalStateException: Cannot access database on the main thread since it may potentially lock the UI for a long periods of time.
at android.arch.persistence.room.RoomDatabase.assertNotMainThread(RoomDatabase.java:137)
at android.arch.persistence.room.RoomDatabase.beginTransaction(RoomDatabase.java:184)
at com.github.alexpfx.udacity.nanodegree.android.baking_app.data.local.RecipeDao_Impl.insert(RecipeDao_Impl.java:89)
at com.github.alexpfx.udacity.nanodegree.android.baking_app.recipe.RecipeLocalDataSource.save(RecipeLocalDataSource.java:34)
at com.github.alexpfx.udacity.nanodegree.android.baking_app.recipe.RecipesRepositoryImpl$1.onResponse(RecipesRepositoryImpl.java:49)
at retrofit2.ExecutorCallAdapterFactory$ExecutorCallbackCall$1$1.run(ExecutorCallAdapterFactory.java:70)
at android.os.Handler.handleCallback(Handler.java:751)
at android.os.Handler.dispatchMessage(Handler.java:95)
at android.os.Looper.loop(Looper.java:154)
at android.app.ActivityThread.main(ActivityThread.java:6776)
at java.lang.reflect.Method.invoke(Native Method)
at com.android.internal.os.ZygoteInit$MethodAndArgsCaller.run(ZygoteInit.java:1496)
at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:1386)

【问题讨论】:

当不确定时,只需在调用Thread.currentThread() 之前调用Log.d,然后调用dataSource.save(response.body());,您会看到,是的,它是在UI 线程中调用的 我在条件(hasData?)之前和保存之前调用日志,结果是:刷新:线程[pool-2-thread-1,5,main]刷新:线程[main, 10,main] 这是什么意思? Thread[main,10,main] - 这是 UI 线程 我认为它是由 Runnable 包装的,它在不同的线程中执行。但我会尝试你的建议。 使用这个square.github.io/retrofit/2.x/retrofit/retrofit2/…来设置你自己的回调执行器 【参考方案1】:

您可以将实现更改为:

private void refresh() 

        if (!dataSource.hasData()) 
            recipeService.getAllRecipes().enqueue(new Callback<List<Recipe>>() 
                @Override
                public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) 

                executor.execute(() -> 
                    dataSource.save(response.body());
                  );
                

                @Override
                public void onFailure(Call<List<Recipe>> call, Throwable t) 
                    throw new RuntimeException(t);
                
            );
        

onResponse 函数总是在 UIThread 中调用。

【讨论】:

【参考方案2】:

您的 callback.onresponce() 方法调用似乎来自 UI 线程

像这样改变你的 onResponse 方法会有所帮助

@Override
public void onResponse(Call<List<Recipe>> call, Response<List<Recipe>> response) 
AsyncTask.execute(new Runnable() 
                    @Override
                    public void run() 
                        dataSource.save(response.body());
                    
                );

            

【讨论】:

您可以通过添加更多详细信息和如何修复它的示例来将此“评论”转变为完整的答案。

以上是关于无法访问主线程上的数据库 - Android Room - 使用 ThreadPoolExecutor的主要内容,如果未能解决你的问题,请参考以下文章

“无法访问主线程上的数据库,因为它可能会长时间锁定 UI。”我的协程错误

Android中Handler的主要作用是啥?通俗点,初学。

Android消息机制

android 上的 SQLite 数据库、多线程、锁和帐户同步

[转]Android限制只能在主线程中进行UI访问的实现原理

Android子线程访问网络