使用Cursor和ListView适配器处理大量数据

7

我正在使用自定义CursorAdapter从SQLite数据库获取数据并在ListView中显示。该数据库包含大约8000行的2列。因此,我正在寻找一种尽可能快地查询和显示所有数据的方法。我已经使用asyncTask完成了这个操作,以下是代码:

private class PrepareAdapter extends AsyncTask<Void,Void,CustomCursorAdapter > {

 @Override
 protected void onPreExecute() {
     dialog.setMessage("Wait");
     dialog.setIndeterminate(true);
     dialog.setCancelable(false);
     dialog.show();


     Log.e("TAG","Posle nov mAdapter");
 }

 @Override
 protected CustomCursorAdapter doInBackground(Void... unused) {

   Cursor cursor =  myDbNamesHelper.getCursorQueryWithAllTheData();
   mAdapter.changeCursor(cursor);
   startManagingCursor(cursor);
   Log.e("TIME","posle start managing Cursor" + String.valueOf(SystemClock.elapsedRealtime()-testTime)+ " ms");
     testTime=SystemClock.elapsedRealtime();

     mAdapter.initIndexer(cursor);
     return mAdapter;
 }

 protected void onPostExecute(CustomCursorAdapter result) {

     TabFirstView.this.getListView().setAdapter(result);
     Log.e("TIME","posle adapterSet" + String.valueOf(SystemClock.elapsedRealtime()-testTime)+ " ms");
    testTime=SystemClock.elapsedRealtime();
     dialog.dismiss();
 }

这个方法很好,但是当我需要将结果设置到适配器中时出现了问题。我进行了一些时间测试,发现经过startManagingCursor大约需要700毫秒,但经过setAdapter(result)需要大约7秒钟。由于这是在UI线程中运行的,因此会导致我的应用程序不响应(进度对话框冻结并且有时应用程序也会冻结)。我该如何减少这段时间?我能否使其在后台运行或以任何方式提高响应性?

谢谢。

public class CustomCursorAdapter extends SimpleCursorAdapter implements OnClickListener,SectionIndexer,Filterable,
                                    android.widget.AdapterView.OnItemClickListener{

    private Context context;
    private int layout;
    private AlphabetIndexer alphaIndexer;

    public CustomCursorAdapter (Context context, int layout, Cursor c, String[] from, int[] to) {
        super(context, layout, c, from, to);
        this.context = context;
        this.layout = layout;

    }
    public void initIndexer(Cursor c){
         alphaIndexer=new AlphabetIndexer(c, c.getColumnIndex(DataBaseNamesHelper.COLUMN_NAME), " ABCDEFGHIJKLMNOPQRSTUVWXYZ");
    }

    @Override
    public View newView(Context context, Cursor cursor, ViewGroup parent) {

        Cursor c = getCursor();

        final LayoutInflater inflater = LayoutInflater.from(context);
        View v = inflater.inflate(layout, parent, false);

        int nameCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_NAME);

        String name = c.getString(nameCol);

        /**
         * Next set the name of the entry.
         */
        TextView name_text = (TextView) v.findViewById(R.id.name_entry);
        if (name_text != null) {
            name_text.setText(name);
        }

        int favCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_FAVOURITED);
        int fav = c.getInt(favCol);

        int idCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_ID);

        Button button = (Button) v.findViewById(R.id.Button01);
        button.setOnClickListener(this);
        button.setTag(c.getInt(idCol));
        if(fav==1){
            button.setVisibility(View.INVISIBLE);
        }
        else button.setVisibility(View.VISIBLE);

        return v;
    }

    @Override
    public void bindView(View v, Context context, Cursor c) {

        int nameCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_NAME);

        String name = c.getString(nameCol);

        /**
         * Next set the name of the entry.
         */
        TextView name_text = (TextView) v.findViewById(R.id.name_entry);
        if (name_text != null) {
            name_text.setText(name);
        }
        int favCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_FAVOURITED);
        int fav = c.getInt(favCol);

        Button button = (Button) v.findViewById(R.id.Button01);
        button.setOnClickListener(this);
        int idCol = c.getColumnIndex(DataBaseNamesHelper.COLUMN_ID);
        button.setTag(c.getInt(idCol));
      //  Log.e("fav",String.valueOf(fav));
        if(fav==1){
            button.setVisibility(View.INVISIBLE);
        } else button.setVisibility(View.VISIBLE);
    }



    @Override
    public int getPositionForSection(int section) {
        return alphaIndexer.getPositionForSection(section);
    }

    @Override
    public int getSectionForPosition(int position) {
          return alphaIndexer.getSectionForPosition(position);
    }

    @Override
    public Object[] getSections() {
          return alphaIndexer.getSections();
    }
    @Override
    public void onItemClick(AdapterView<?> arg0, View arg1, int arg2, long arg3) {
        Log.e("item Click", arg1.toString()+ " position> " +arg2);
    }

    @Override
    public void onClick(View v) {
            if(v.getId()==R.id.Button01){
                //Log.e("Button Click", v.toString()+ " position> " +v.getTag().toString());
                v.setVisibility(View.INVISIBLE);
                DataBaseNamesHelper dbNames = new DataBaseNamesHelper(context);
                dbNames.setFavouritesFlag(v.getTag().toString());
            }

        }



}

1
你的用户真的想要在ListView中滚动浏览8000行吗?他们是否有一种方式先过滤列表呢? - EboMike
我已经实现了带索引的快速滚动。稍后我会添加搜索选项以进行过滤。 - DArkO
无论如何,你可以在单独的线程中执行查询,然后将其分配给适配器,但这仍然会使您的应用程序黑屏7秒钟。除非你有办法让用户度过7秒钟? - EboMike
整个应用程序的重点在于拥有那么多的数据。用户应该能够自由滚动,然后选择某些内容以过滤更相关的信息,正如我所说的搜索选项也会这样做。但他需要能够滚动,以便他可以了解要搜索什么。 - DArkO
我正在这里做类似的事情 https://dev59.com/xeo6XIcBkEYKwwoYQiO7 - Etienne Lawlor
4个回答

24
加载适配器缓慢的原因是CursorAdapter内部调用了Cursor.getCount()方法。在Android中,Cursors是惰性加载的,即结果只有在需要时才会被加载。当CursorAdapter调用getCount()方法时,这会强制执行查询并计算结果数量。以下是两个讨论此问题的链接:http://groups.google.com/group/android-developers/browse_thread/thread/c1346ec6e2310c0chttp://www.androidsoftwaredeveloper.com/2010/02/25/sqlite-performance/。我的建议是将查询拆分为多个部分。仅加载屏幕上可见的列表项数量。随着用户滚动,加载下一组数据。这与Gmail和Market应用程序非常相似。不幸的是,我手边没有示例:)这虽然没有回答你的问题,但希望提供了一些见解:)

3

不确定为什么setAdapter需要这么长时间。我已经比那快多了,处理了约5000行数据。然而,我通常先创建一个没有游标的适配器,使用“空”适配器调用setAdapter,然后使用AsyncQueryHandler子类启动异步查询。然后,在onQueryComplete中,我调用适配器上的changeCursor。


2

目前,我只能提供一个愚蠢的建议 - 在单独的线程中开始一个查询,该查询将遍历整个数据库并创建包含8000行数据的游标。

在您的UI线程中,创建一个游标,将其限制设置为100或更少,并将其用于适配器。如果用户滚动到列表底部,您可以添加一行带有进度条或其他指示的内容,以表明还有更多内容。

完成第二个线程后,替换适配器。

或者,您可以使用数组适配器或类似的东西,在第二个线程中每次使用100行进行一堆查询,并将它们添加到您的数组适配器中。这样做也会更容易在底部添加一个虚拟行,告诉用户要耐心等待。

需要注意的是,我认为同时从两个线程中读取数据库是安全的,但写入时要小心。我的应用程序曾经出现过一个奇妙的错误,当我在单独的线程中进行数据库操作时,它会毁坏数据库,直到我添加了适当的锁定(您也可以在数据库中启用)。

编辑:当然,AsyncQueryHandler,我知道有这样的东西,但我记不起名字了。比单独的线程更优雅。

顺便问一下 - 您是如何存储数据库的?它只是您内部存储中的常规数据库文件吗?


是的,只是一个普通的内部数据库。我没有使用内容提供程序,因为我不需要在应用程序之间共享数据。我试图先将适配器设置为空,然后在适配器中更改游标,但到目前为止还没有成功,它只给我一个空的listView。令我困扰的是,查询只需要约600毫秒就能完成,但列表分配需要7秒钟,所以我想这个慢响应不是来自查询,对吗?或者我有什么遗漏了吗? - DArkO
哦,太棒了,我看错了。你在使用什么类型的适配器?我看到CustomCursorAdapter - 它的自定义部分是什么? - EboMike
它扩展了SimpleCursorAdapter。这是我用来制作它的链接:http://thinkandroid.wordpress.com/2010/01/11/custom-cursoradapters/ - DArkO
2
嗯...非常无害。我在快速谷歌搜索中找到的唯一一件事就是我建议的那种 - 首先使用一个小集合并扩展它。看看这个线程。Romain说也有一个API演示:http://groups.google.com/group/android-developers/browse_thread/thread/c226f792613850b0 - EboMike

1

嗨,我正在尝试在50000行以上的数据上实现这一目标。我认为我的代码可以为您提供更好的结果,因为您只有8000条记录。

1)使用内容提供程序而不是SQLite。 2)使用LoaderManager回调异步加载光标。 3)对于搜索,请使用FTS表。 4)使用索引优化数据库。

相信我,在8000行甚至带有SectionIndexer和快速滚动时,它的效果非常好。

如果您发现了更好的解决方案,可以处理50000行,请告诉我。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接