1

I have a SQLite query returning thousands of rows, which I want to visualize using a ListView.

In order to keep my UI thread responsive, I create the ListAdapter on a background thread.

However the statement that takes most time (and can cause ANR) is ListActivity.setListAdapter which I have to execute on the UI thread... Any advice?

public class CursorTestActivity extends ListActivity {

    private static final String LOGTAG = "DBTEST";

    private DatabaseManager mDbManager;
    private Cursor mCursor;
    private HandlerThread mIOWorkerThread;
    private Handler mIOHandler;
    private Handler mUIHandler;

    @Override
    public void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        mDbManager = new DatabaseManager(this);

        mUIHandler = new Handler();
        createIOWorkerThread();

        log("creating cursor");
        mCursor = mDbManager.getCursor(); // does db.query(...)
        startManagingCursor(mCursor);

        mIOHandler.post(new Runnable() {
            @Override
            public void run() {
                setMyListAdapter();
            }
        });
        log("onCreate done");
    }

    private void setMyListAdapter() {
        log("constructing adapter");
        // CustomCursorAdapter implements bindView and newView
        final CustomCursorAdapter listAdapter = new CustomCursorAdapter(this,
                mCursor, false);
        log("finished constructing adapter");
        mUIHandler.post(new Runnable() {
            @Override
            public void run() {
                log("setting list adapter");
                setListAdapter(listAdapter); // gets slower the more rows are returned
                log("setting content view");
                setContentView(R.layout.main);
                log("done setting content view");
            }
        });
    }

    private void createIOWorkerThread() {
        mIOWorkerThread = new HandlerThread("io_thread");
        mIOWorkerThread.start();
        Looper looper = mIOWorkerThread.getLooper();
        mIOHandler = new Handler(looper);
    }

    private void destroyIOWorkerThread() {
        if (mIOWorkerThread == null)
            return;
        Looper looper = mIOWorkerThread.getLooper();
        if (looper != null) {
            looper.quit();
        }
    }

    @Override
    public void onDestroy() {
        super.onDestroy();
        if (mDbManager != null)
            mDbManager.close();
        destroyIOWorkerThread();
    }

    private static void log(String s) {
        Log.d(LOGTAG, s);
    }

}

2 Answers 2

0

Cursors are lazy loaded therefore at first data access the cursor is loaded - getCount is such access. The setAdapter method is invoking getCount on cursor - there is the performance issue.

  • You can set empty cursor in adapter and display empty list during loading cursor. Then use changeCursor method in adapter to change cursor to the new one.
  • You can fetch at first for example 100 rows in the first query, then load more rows in the background and changeCursor to the new one.
  • Or you can create own implementation of Cursor that has own implementation of getCount and fetches demanded rows on request.
Sign up to request clarification or add additional context in comments.

Comments

0
  1. consider using PagedListAdapter. it's something similar as proposed by @pawelziemba but provided by Android Framework https://developer.android.com/reference/android/arch/paging/PagedListAdapter -- if you can use Room framework you can get that for free
  2. load data async in ViewHolder - remember to cancel work if viewHolder gets rebinded and cache loaded data if needed

Comments

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.