3

My app seems to have one major cause of crashes in the reports: java.lang.IllegalStateException

I can see where it is coming from, it is the functionality that constructs notifications from DB content in a background process during the day, when the app is not active. The interesting thing is that this seems to happen only on the newest Android 10 version:

Pie charts of the report

Did something change about how you process database data in a non-active app state?


Here is the crash report:

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)

As for the code, I have an AlarmManager that triggers a listener at some point during the day. In that listener I call an AsyncTask that retrieves data from a DB in the background and calls an interface method to notify the listener when it's done:

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

I've found related questions, but they cover other specific aspects like dialog dismissal or ClickListener:
IllegalStateException on AsyncTask
IllegalStateException asynctask - onPostExecute
Play Store Crash Report: IllegalStateException on android.view.View$DeclaredOnClickListener.onClick


As stated on the Android Dev page:

Throws IllegalStateException
If getStatus() returns either AsyncTask.Status#RUNNING or AsyncTask.Status#FINISHED.

So that seems to be the cause? Or maybe the interface delegate?

Big_Chair
  • 2,781
  • 3
  • 31
  • 58
  • 1
    It is usually discouraged to call an AsyncTask (background API Call) directly from the broadcast receiver class. You can try invoking a Service which in turn calls the api. You can read about it more here https://stackoverflow.com/questions/21743800/call-asynctask-from-broadcast-receiver-android/21744022 – Atish Agrawal Feb 18 '20 at 10:58
  • 1
    It's not recommended to start AsyncTask and supply your BroadcastReceiver as a callback, because after the onReceive method done the execution of your broadcast receiver class will not be valid afterward. Try to use a separate class that does this. I can see that from your code that you don't need the context for your operation, so it's better to put that in other class And regarding the background process, try to use java Executors it's very simple – Sridhar S Feb 20 '20 at 13:25
  • @SridharS Thank you for the explanation. Can you write an answer with more details (and an example)? I am not so professional when it comes to backend stuff like that, so I would appreciate some starting point. – Big_Chair Feb 20 '20 at 14:11
  • 1
    @Big_Chair : I can't write much in the comment section, I'll give a gist about Executor Service here, and you can find how to use it efficiently in android from a practical application which I have shared in Github https://github.com/half-blood-prince/Contacts/blob/master/app/src/main/java/com/assessment/contacts/AppExecutorService.java Executors.newFixedThreadPool(1).execute(new Runnable() { public void run() { .... /* Perform your long running operation here */ .... } }); You can use Executors to perform any task in background instead of AsyncTask – Sridhar S Feb 22 '20 at 03:52

1 Answers1

3

checkIdentity throws if

Room cannot verify the data integrity. Looks like you've changed schema but forgot to update the version number. You can simply fix this by increasing the version number.

There's a related issue where

Room v1 had a bug where the hash was not consistent if fields are reordered.

So you might have changed the database scheme without updating the version. If you're using an old version of Room you might have just reordered the the definitions of your columns.

Both might be fixed by upgrading your scheme.

It might be possible though, that your progress was killed when you just updated the database scheme. Thus the new hash was not written and the database got into an inconsistent state.

This could be caused by background restrictions in Android 10. I'd suggest to you to replace the BroadcastReceiver with an IntentService you start in foreground mode.

Alternatively it might be caused by a packaged database.

No room_master_table, this might be a pre-populated DB, we must validate to see if it's suitable for usage.

This results in an error stating:

Pre-packaged database has an invalid schema: ...

So you need to make sure that the database contains the correct scheme.

tynn
  • 38,113
  • 8
  • 108
  • 143
  • But then it would happen across all Android versions, not just 10. I'm pretty sure the error lies in the AsyncTask inner class, when I try to access the next notification from the SQLite DB. It seems the higher API is more restrictive about background processes and kills them easier, making the AsyncTask not reach it's callback in time and thus being in an 'illegal state'. – Big_Chair Feb 20 '20 at 19:53
  • The `IllegalStateException` comes from `RoomOpenHelper` line 139. It doesn't have a message, which makes it harder to identify, but the stacktrace is clear. You could open the class in Android Studio and see which code is at this line. I'm using a different version, but the message I posted relates to the scope of your issue. – tynn Feb 20 '20 at 23:53
  • Actually, now that I read your answer more clearly and checked the line 139, it really does seem like that. But the thing is, this is the first release of the app. So it did not have another version of it to begin with (on the user's phones at least). So how can the scheme already be inconsistent? I'm on Room v 2.1.0-alpha04 – Big_Chair Feb 21 '20 at 10:35
  • Are you using prepackaged databases? – tynn Feb 21 '20 at 12:36
  • I have a simple sqlite file, nothing more. But when the user is actively using the app, everything is done through `ViewModel`, but in this case, the notification has to be created even when it's not running. And that seems to be the issue somehow, because I'm not getting any reports about this error during active state, only in this case of `NotificationRepository.FetchNextNotifAsyncTask`. That is why I also assumed it must be connected to the `AsyncTask`. – Big_Chair Feb 21 '20 at 17:19
  • Maybe the issue is there. Just not causing a crash. – tynn Feb 21 '20 at 18:39
  • After working on some other issue I actually did receive the exception `IllegalStateException: Pre-packaged database has an invalid schema`, so maybe you were on to something with your question about it being prepackaged? What were you going to suggest in that case? – Big_Chair Feb 25 '20 at 15:13
  • @Big_Chair I'd make sure the database you package was actually generated with room. You could utilize some test to write or validate these as well. – tynn Feb 27 '20 at 09:54
  • Well, this wasn't an exact fix as I'm still figuring out what exactly the issue is, but at least you took the time. Maybe it will help me in the future. – Big_Chair Feb 27 '20 at 10:55