0

I keep seeing "The application may be doing too much work on its main thread."

Is this being caused by swapCursor() in my code below? It appears so: if I remove it, the above warning disappears.

I still don't understand why it claims to be the main thread that is causing the problem. I have moved the swapCursor() around into various places such as onLoadFinished() and in loadInBackground() but I get the same result.

How can I call swapCursor() so that this warning doesn't appear? The whole reason I used SimpleCursorLoader was avoid holding up the main thread but its still happening.

package com.example.sqlitetest;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;

import android.app.Activity;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v4.app.FragmentActivity;
import android.support.v4.app.LoaderManager;
import android.support.v4.app.LoaderManager.LoaderCallbacks;
import android.support.v4.content.Loader;
import android.support.v4.content.CursorLoader;
import android.support.v4.widget.SimpleCursorAdapter;
import android.support.v4.widget.SimpleCursorAdapter.ViewBinder;
import android.view.Gravity;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnTouchListener;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.AdapterView;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.LinearLayout.LayoutParams;
import android.widget.ListView;
import android.widget.TextView;

public class MainActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<Cursor> 
{
  public static Context mContext;

  private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallbacks;

  public static SQLiteDatabase      db;
  public static SimpleCursorAdapter cAdapter;
  public static ListView            lvCustomList;


  public static final class MyCursorLoader extends SimpleCursorLoader 
  {
    public MyCursorLoader( Context context )
    {
      super( context );
    }

    @Override
    public Cursor loadInBackground()
    {
      Cursor cursor = null;

      cursor = db.rawQuery( "SELECT rowid _id, Name, Rating FROM Tune ORDER BY Name", null );

      cAdapter.swapCursor( cursor );

      return cursor;
    }
  }  

  @Override
  public Loader<Cursor> onCreateLoader(int id, Bundle args) 
  {
    return new MyCursorLoader( this );
  }

  @Override
  public void onLoadFinished( Loader<Cursor> loader, Cursor cursor ) 
  {
//    cAdapter.swapCursor( cursor );
  }

  @Override
  public void onLoaderReset( Loader<Cursor> loader ) 
  {
    cAdapter.swapCursor( null );
  }

  @Override
  public void onCreate( Bundle savedInstanceState )
  {
    super.onCreate( savedInstanceState );
    setContentView( R.layout.tune_artist_album_view );

    mContext = this;

    String path = "/sdcard/MyDb.sqlite";

    db = SQLiteDatabase.openDatabase( path, null, 0 );

    lvCustomList = (ListView) findViewById( R.id.lv_custom_list );

    String[] columns = new String[] { "Name", "Rating" };

    int[] to = new int[] { R.id.lv_tune };  

    cAdapter = new SimpleCursorAdapter( mContext, R.layout.like_hate_row, null, columns, to, 0 );

    lvCustomList.setAdapter( cAdapter );

    mLoaderCallbacks = this;

    LoaderManager lm = getSupportLoaderManager();

    lm.initLoader( 0, null, mLoaderCallbacks );
  }
}

Here is SimpleCursorLoader:

package com.example.sqlitetest;

import android.content.Context;
import android.database.Cursor;
import android.support.v4.content.AsyncTaskLoader;

/**
 * Used to write apps that run on platforms prior to Android 3.0. When running
 * on Android 3.0 or above, this implementation is still used; it does not try
 * to switch to the framework's implementation. See the framework SDK
 * documentation for a class overview.
 * 
 * This was based on the CursorLoader class
 */
public abstract class SimpleCursorLoader extends AsyncTaskLoader<Cursor>
{
  private Cursor mCursor;

  public SimpleCursorLoader( Context context )
  {
    super( context );
  }

  /* Runs on a worker thread */
  @Override
  public abstract Cursor loadInBackground();

  /* Runs on the UI thread */
  @Override
  public void deliverResult( Cursor cursor )
  {
    if( isReset() )
    {
      // An async query came in while the loader is stopped
      if( cursor != null )
      {
        cursor.close();
      }
      return;
    }

    Cursor oldCursor = mCursor;
    mCursor = cursor;

    if( isStarted() )
    {
      super.deliverResult( cursor );
    }

    if( oldCursor != null && oldCursor != cursor && !oldCursor.isClosed() )
    {
      oldCursor.close();
    }
  }

  /**
   * Starts an asynchronous load of the contacts list data. When the result is
   * ready the callbacks will be called on the UI thread. If a previous load has
   * been completed and is still valid the result may be passed to the callbacks
   * immediately.
   * <p/>
   * Must be called from the UI thread
   */
  @Override
  protected void onStartLoading()
  {
    if( mCursor != null )
    {
      deliverResult( mCursor );
    }
    if( takeContentChanged() || mCursor == null )
    {
      forceLoad();
    }
  }

  /**
   * Must be called from the UI thread
   */
  @Override
  protected void onStopLoading()
  {
    // Attempt to cancel the current load task if possible.
    cancelLoad();
  }

  @Override
  public void onCanceled( Cursor cursor )
  {
    if( cursor != null && !cursor.isClosed() )
    {
      cursor.close();
    }
  }

  @Override
  protected void onReset()
  {
    super.onReset();

    // Ensure the loader is stopped
    onStopLoading();

    if( mCursor != null && !mCursor.isClosed() )
    {
      mCursor.close();
    }
    mCursor = null;
  }
}
SparkyNZ
  • 6,266
  • 7
  • 39
  • 80
  • 1
    Use Traceview and find out where your time is being spent, rather than guessing. – CommonsWare Jul 07 '14 at 11:05
  • @CommonsWare: Thanks I will give it a go and see what it shows. Tried it on my phone at lunchtime and no warnings appeared. Problem may only be occurring on my tablet. – SparkyNZ Jul 08 '14 at 03:24
  • @CommonsWare: OK, so TraceView tells me that my guess was correct - 1.5 seconds is spent doing nativeExecuteForCursorWindow() on the main thread. Why on the main thread? I obviously haven't achieved my goal of having the datbase retrieval take place in the background. – SparkyNZ Jul 08 '14 at 09:28

1 Answers1

6

Methods like query() and rawQuery() don't do what you think they do. Notably, they do not actually query the database. They merely configure the SQLiteCursor that they return. When you attempt to use that Cursor, it executes the query.

Hence, in your loadInBackground() method, in between rawQuery() and swapCursor(), call getCount() on the Cursor you got back from rawQuery(). That is enough to force the Cursor to really do the work for the query, and therefore that work is done on the background thread.

CommonsWare
  • 986,068
  • 189
  • 2,389
  • 2,491
  • Aha! Thanks for that. I saw a getCount() call in some of the examples. The return value was ignored and I thought it was just a bit of left over accidental code - that explains its presence! I'll give that a go. If that helps I could be accepting your answer. – SparkyNZ Jul 08 '14 at 09:44
  • 1
    @SparkyNZ: Alas, there is no `yoYouLazyObjectDoTheQueryAlready()` method, otherwise, we'd use that for this use case. :-) – CommonsWare Jul 08 '14 at 09:48
  • Yes, that did the trick. Instead of seeing a bit 1.5 second block on the main thread, I see ModernAsyncTask doing that amount of work.. and no warnings about missing frames. I've accepted your answer - thank you very much for that.. and an interesting journey with TraceView. – SparkyNZ Jul 08 '14 at 09:49
  • its March 2016 now and it's still happening for CursorLoader. Lucky I found it – nbtk Mar 21 '16 at 11:47