【问题标题】:Play Store crash report - IllegalStateException in Room + AsyncTask but only on Android 10Play 商店崩溃报告 - Room + AsyncTask 中的 IllegalStateException 但仅在 Android 10 上
【发布时间】:2020-06-02 08:55:03
【问题描述】:

报告中我的应用似乎有一个导致崩溃的主要原因:java.lang.IllegalStateException

我可以看到它来自哪里,它是在白天,当应用程序不活动时,在后台进程中从数据库内容构造 通知 的功能。有趣的是,这似乎只发生在最新的 Android 10 版本上:

您在非活动应用状态下处理数据库数据的方式是否发生了变化?


这是崩溃报告:

java.lang.RuntimeException: 
  at android.os.AsyncTask$4.done (AsyncTask.java:399)
  at java.util.concurrent.FutureTask.finishCompletion (FutureTask.java:383)
  at java.util.concurrent.FutureTask.setException (FutureTask.java:252)
  at java.util.concurrent.FutureTask.run (FutureTask.java:271)
  at android.os.AsyncTask$SerialExecutor$1.run (AsyncTask.java:289)
  at java.util.concurrent.ThreadPoolExecutor.runWorker (ThreadPoolExecutor.java:1167)
  at java.util.concurrent.ThreadPoolExecutor$Worker.run (ThreadPoolExecutor.java:641)
  at java.lang.Thread.run (Thread.java:919)
Caused by: java.lang.IllegalStateException: 
  at androidx.room.RoomOpenHelper.checkIdentity (RoomOpenHelper.java:139)
  at androidx.room.RoomOpenHelper.onOpen (RoomOpenHelper.java:119)
  at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.onOpen (FrameworkSQLiteOpenHelper.java:151)
  at android.database.sqlite.SQLiteOpenHelper.getDatabaseLocked (SQLiteOpenHelper.java:428)
  at android.database.sqlite.SQLiteOpenHelper.getWritableDatabase (SQLiteOpenHelper.java:317)
  at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper$OpenHelper.getWritableSupportDatabase (FrameworkSQLiteOpenHelper.java:96)
  at androidx.sqlite.db.framework.FrameworkSQLiteOpenHelper.getWritableDatabase (FrameworkSQLiteOpenHelper.java:54)
  at androidx.room.RoomDatabase.query (RoomDatabase.java:256)
  at androidx.room.util.DBUtil.query (DBUtil.java:54)

  (the next 3 lines are where it's at)
  at com.dev.solidmind.db.dao.MNotificationDao_Impl.getNextNotification (MNotificationDao_Impl.java:75)
  at com.dev.solidmind.repository.NotificationRepository$FetchNextNotifAsyncTask.doInBackground (NotificationRepository.java:58)
  at com.dev.solidmind.repository.NotificationRepository$FetchNextNotifAsyncTask.doInBackground (NotificationRepository.java:41)
  at android.os.AsyncTask$3.call (AsyncTask.java:378)
  at java.util.concurrent.FutureTask.run (FutureTask.java:266)

至于代码,我有一个AlarmManager,它会在一天中的某个时间点触发一个监听器。在那个监听器中,我调用了一个AsyncTask,它在后台从数据库中检索数据,并在完成时调用一个接口方法来通知监听器:

public class NotificationReceiver extends BroadcastReceiver implements NotificationRepository.FetchNextNotifAsyncTask.AsyncResponse {

    private Context context;
    private NotificationRepository repository;
    private MessageNotification nextNotification;

    @Override
    public void onReceive(Context context, Intent intent) {
        this.context = context;

        SharedPreferences sharedPreferences = PreferenceManager.getDefaultSharedPreferences(context);
        SharedPreferences.Editor editor = sharedPreferences.edit();
        editor.putLong("lastTimeOfNotifTrigger", System.currentTimeMillis());
        editor.apply();

        repository = new NotificationRepository(context);
        repository.startGetNextNotificationAsync(this);
    }

    @Override
    //Callback for when notification object has been retrieved from DB
    public void processFinish(MessageNotification notification) {
        if (notification != null) {
            nextNotification = notification;
            constructNotification();
            setNextAlarm();
        }
    }
...
public class NotificationRepository {
    private MNotificationDao mNotificationDao;
    private SuggestionDao suggestionDao;

    public NotificationRepository(Context context) {
        AppRoomDatabase db = DatabaseCopier.getInstance(context).getRoomDatabase(); //get db from existing file
        mNotificationDao = db.mNotificationDao();
        suggestionDao = db.suggestionDao();
    }

    public void startGetNextNotificationAsync(FetchNextNotifAsyncTask.AsyncResponse delegate) {
        new FetchNextNotifAsyncTask(delegate, mNotificationDao).execute();
    }

    public void setNewNextNotification(int id) {
        new UpdateNextNotifAsyncTask(mNotificationDao).execute(id);
    }

    public void revealSuggestion(int id) {
        new UpdateIsRevealedAsyncTask(suggestionDao).execute(id);
    }

    public void revealPremiumSuggestions() {
        new RevealPremiumAsyncTask(suggestionDao).execute();
    }


    //=== Inner AsyncTask class to run updates in the background ===

    public static class FetchNextNotifAsyncTask extends AsyncTask<Void, Void, MessageNotification> {

        //Interface delegate to notify Activity when done
        public interface AsyncResponse {
            void processFinish(MessageNotification notification);
        }

        private AsyncResponse delegate;
        private MNotificationDao mAsyncTaskDao;

        FetchNextNotifAsyncTask(AsyncResponse delegate, MNotificationDao dao) {
            this.delegate = delegate;
            mAsyncTaskDao = dao;
        }

        @Override
        protected MessageNotification doInBackground(Void... voids) {
            return mAsyncTaskDao.getNextNotification();
        }

        @Override
        protected void onPostExecute(MessageNotification notification) {
            delegate.processFinish(notification);
        }
    }
...

我找到了相关问题,但它们涵盖了其他特定方面,例如对话框解除或 ClickListener:
IllegalStateException on AsyncTask
IllegalStateException asynctask - onPostExecute
Play Store Crash Report: IllegalStateException on android.view.View$DeclaredOnClickListener.onClick


如 Android Dev 页面所述:

抛出 IllegalStateException
如果 getStatus() 返回 AsyncTask.Status#RUNNING 或 AsyncTask.Status#FINISHED。

所以这似乎是原因?或者接口委托?

【问题讨论】:

  • 通常不鼓励直接从广播接收器类调用 AsyncTask(后台 API 调用)。您可以尝试调用一个服务,该服务反过来调用 api。你可以在这里阅读更多内容stackoverflow.com/questions/21743800/…
  • 不建议启动 AsyncTask 并提供您的 BroadcastReceiver 作为回调,因为在 onReceive 方法完成之后,您的广播接收器类的执行将不再有效。尝试使用一个单独的类来执行此操作。我可以从您的代码中看到您不需要操作上下文,因此最好将其放在其他类中关于后台进程,尝试使用 java Executors 非常简单
  • @SridharS 谢谢你的解释。你能写一个更详细的答案(和一个例子)吗?当涉及到这样的后端时,我不是很专业,所以我会很感激一些起点。
  • @Big_Chair : 我不能在评论部分写太多,我将在这里给出一个关于 Executor Service 的要点,你可以从我的一个实际应用程序中找到如何在 android 中有效地使用它已在 Github 中分享 github.com/half-blood-prince/Contacts/blob/master/app/src/main/… Executors.newFixedThreadPool(1).execute(new Runnable() { public void run() { .... /* 在这里执行你的长时间运行操作 */ .... } });您可以使用 Executors 在后台执行任何任务,而不是 AsyncTask

标签: android push-notification android-asynctask illegalstateexception


【解决方案1】:

checkIdentity 抛出如果

Room 无法验证数据完整性。看起来您已更改架构但忘记更新版本号。您可以通过增加版本号来解决此问题。

有一个related issue 在哪里

Room v1 存在一个错误,即如果重新排序字段,哈希值会不一致。

因此,您可能在未更新版本的情况下更改了数据库方案。如果您使用的是旧版本的 Room,您可能只是重新排列了列的定义。

这两个问题都可以通过升级你的方案来解决。

但是,当您刚刚更新数据库方案时,您的进度可能会被终止。因此没有写入新的哈希,数据库进入了不一致的状态。

这可能是由 Android 10 中的后台限制引起的。我建议您将 BroadcastReceiver 替换为您在前台模式下启动的 IntentService

或者,它可能是由打包的数据库引起的。

没有 room_master_table,这可能是一个预填充的数据库,我们必须验证它是否适合使用。

这会导致错误说明:

预打包的数据库的架构无效:...

因此您需要确保数据库包含正确的方案。

【讨论】:

  • 但是它会在所有 Android 版本中发生,而不仅仅是 10。当我尝试从 SQLite DB 访问下一个通知时,我很确定错误出在 AsyncTask 内部类中。似乎更高的 API 对后台进程的限制更大,并且更容易杀死它们,使得 AsyncTask 无法及时到达它的回调,从而处于“非法状态”。
  • IllegalStateException 来自RoomOpenHelper 第139行。它没有消息,因此更难识别,但堆栈跟踪很清楚。您可以在 Android Studio 中打开该类并查看该行中的代码。我使用的是其他版本,但我发布的消息与您的问题范围有关。
  • 实际上,现在我更清楚地阅读了您的答案并检查了第 139 行,看起来确实如此。但问题是,这是该应用程序的第一个版本。所以它没有另一个版本(至少在用户的手机上)。那么方案怎么可能已经不一致呢?我在 Room v 2.1.0-alpha04
  • 您使用的是预打包的数据库吗?
  • 我有一个简单的 sqlite 文件,仅此而已。但是当用户主动使用应用程序时,一切都是通过ViewModel 完成的,但是在这种情况下,即使没有运行,也必须创建通知。不知何故,这似乎是问题所在,因为在活动状态下我没有收到任何有关此错误的报告,仅在 NotificationRepository.FetchNextNotifAsyncTask 的这种情况下。这就是为什么我还假设它必须连接到AsyncTask
猜你喜欢
  • 2017-10-19
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 2019-07-13
  • 2013-10-21
  • 1970-01-01
  • 1970-01-01
  • 2021-04-12
相关资源
最近更新 更多