Android - Create a very large ListView based on a SQL Cursor
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); } }
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.
- 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
- load data async in ViewHolder - remember to cancel work if viewHolder gets rebinded and cache loaded data if needed
精彩评论