rawQuery() always executes immediately, no matter how long the actual query takes

118 views Asked by At

I have been pulling my hair out for hours trying to understand why a pretty straight-forward Android code breaks the fundamental synchronization in my app.

The method below always executes immediately, despite the fact that it has an SQLiteDatabase.rawQuery() call in it. It doesn't matter how long the SQL query takes:

// this method is always executed instantly, despite the rawQuery() call
private Cursor runRawQueryTest() {

    Log.i(TAG,"runRawQueryTest() started");
    long timer = System.currentTimeMillis();

    // SLOW_SQL_QUERY runs at least a couple seconds:
    Cursor c = getDb().rawQuery( SLOW_SQL_QUERY, null );

    timer = System.currentTimeMillis() - timer;
    // timer below always reports a few milliseconds:
    Log.i(TAG,"runRawQueryTest() finished in " + timer + "ms.");

    return c;
}

Here is the Logcat:

2020-10-06 23:57:07.609 ... I/MainActivity: runRawQueryTest() started
2020-10-06 23:57:07.621 ... I/MainActivity: runRawQueryTest() finished in 12ms. <<<< "incorrect" timer 

It looks like the method runRawQueryTest() is somehow inlined. It wouldn't be a problem by itself (who cares about logs anyway?), because the database query actually happens in the method that calls runRawQueryTest():

    public void onClick(View view) {
        long timer = System.currentTimeMillis();
        Log.i(TAG, "onClick()" );
        Cursor c = runRawQueryTest();
        //  database query actually happens here:
        Log.i(TAG, "cursor.getCount() " + c.getCount());
        if ( !c.isClosed() ) c.close();
        timer = System.currentTimeMillis() - timer;
        Log.i(TAG, "onClick() - done in " + timer + "ms. ");
    }

The Logcat:

2020-10-06 23:57:07.609 ... I/MainActivity: onClick()
2020-10-06 23:57:07.609 ... I/MainActivity: runRawQueryTest() started
2020-10-06 23:57:07.621 ... I/MainActivity: runRawQueryTest() finished in 12ms.
2020-10-06 23:57:14.223 ... I/MainActivity: cursor.getCount() 1           <<<< actual query
2020-10-06 23:57:14.223 ... I/MainActivity: onClick() - done in 6614ms.   <<<< correct timer 

The real problem is that in my app these database calls are made asynchronously, using RxJava. rawQuery() is wrapped in an Observable, something like this:

// RxJava wrapper
public Single<Cursor> makeObservable(final String query) {
    return Single
            .fromCallable( () -> getCursorOrThrow(query) )
            .subscribeOn(Schedulers.io())
            .observeOn(AndroidSchedulers.mainThread());
}

As you may have already guessed, Observable emits an item almost immediately, so the actual SQL query runs after the onNext() call. As a result, too much work happens on the main thread, and the UI is messed up, especially progress bars.

How can I make sure that the rawQuery() method executes synchronously?

My only guess was that all this happens because of non-ideal R8 optimization. But my project has pretty standard "out of the box" settings. I was trying to fix the problem, playing with ProGuard "-dontoptimize" key and trying to use different Gradle versions. Unfortunately, it didn't help. I'm running out of ideas. Am I doing something conceptually wrong? Would really appreciate any help!

Gist: https://gist.github.com/DmitryOganezov/c52fd8fcfa4f5be3e6ae5016a6ef7b4d

1

There are 1 answers

3
CommonsWare On BEST ANSWER

How can I make sure that the rawQuery() method executes synchronously?

Have getCursorOrThrow() call getCount() on the Cursor before returning.

The Cursor is a SQLiteCursor, and it lazy-executes the query when the data is first used. This is another one of those "really cool ideas that are awful for how we do things nowadays". Calling getCount() while on the background thread forces the SQLiteCursor to actually load its data.