この記事では、最近発見されたソリューションを共有したいと思います。このソリューションを使用すると、標準のListViewで大量のデータを簡単に表示できます。
問題
Androidのデータベースからリストを表示するための標準的なメカニズムは次のようになります。
- アクティビティにはListViewが含まれます
- ListViewはCursorAdapterインスタンスにアクセスします
- CursorAdapterは、Cursorインターフェイスを実装するオブジェクトからデータを取得します
- ContentProviderから、またはSQLiteDatabaseからすぐに取得したカーソル
Cursorの行数が比較的少ない限り、すべて正常に機能します。 しかし、5万行、10万行、またはそれ以上の行が含まれている場合(行数だけでなく、後でさらに多くなります)、リストは時々遅くなります。 これは、ListViewのfastScrollEnabledプロパティがtrueに設定されている場合、高速スクロール中に特に顕著です。
ListViewに配置するのに膨大な量のデータが必要なのに、なぜかっこから外してみましょう。 これは顧客の要件であり、影響を与えることはできません。 また、Twitterの精神でプリローダーを使用することや、既にダウンロードされたデータの最後に達したときにロードされるデータの次の部分である「エンドレスリスト」を回避することは不可能だと考えています。
目立ったインターフェイスのハングなしに、いつでも10万のリストアイテムのいずれかにスクロールできる必要があります。 これを行う方法? まず始めに、ブレーキの原因を見つけてみましょう。
理由
ViewHolderは考慮しません-多かれ少なかれ有能なandroid-developerがこのパターンを知っており、使用していると思います。 また、ガベージコレクターの前での報復は避けられないため 、 getViewメソッドで多数のオブジェクトを作成することは許されません。
データベースへのカーソル操作に興味があります。
SQLiteDatabaseから取得するCursorは、 AbstractWindowedCursorを継承するSQLiteCursorクラスのインスタンスです。 次に、このクラスにはCursorWindowのインスタンスが含まれます。
最後のクラスでは、問題は正確にあります。 CursorWindowのソースを見ると、ウィンドウサイズがcom.android.internal.R.integer.config_cursorWindowSizeという名前の定数によって制限されていることがわかります。 ユーザーインターフェイスは、ウィンドウ内のスペースがなくなるとすぐに遅くなり(選択範囲の行数だけでなく、各行の長さも重要です)、AbstractWindowedCursorは新しいウィンドウのデータを要求し、このウィンドウにコピーします。
もちろん、ウィンドウのサイズを大きくすることもできます。 しかし、これは問題を解決するのではなく、遅らせるだけなので、まず第一に悪い決断です。 第二に、デバイスのメモリが限られているため、常に増加させることはできません。 しかし第三に、それは技術的に不合理に難しいです。
私たちは他の方法で行きます。
解決策
一般的に、SQLiteはかなり高速なデータベースであり、「ブレーキ」のほとんどはその誤用が原因です。 主キーを照会する場合、特に高速に機能します。
これは、主キーのみを要求し、各行が表示されるときに、この主キーの残りの列を照会するという考え方です。 そして、実際にはより速く動作します。
このアイデアを説明するために、 BaseAdapterクラスの実装を作成しました 。
package me.ilich.fastscroll; import android.content.Context; import android.database.Cursor; import android.view.View; import android.view.ViewGroup; import android.widget.BaseAdapter; public abstract class QuickAdapter extends BaseAdapter { private final DataSource mDataSource; private int mSize = 0; private Cursor mRowIds = null; private final Context mContext; public QuickAdapter(Context context, DataSource dataSource){ mDataSource = dataSource; mContext = context; doQuery(); } private void doQuery(){ if(mRowIds!=null){ mRowIds.close(); } mRowIds = mDataSource.getRowIds(); mSize = mRowIds.getCount(); } @Override public int getCount() { return mSize; } @Override public Object getItem(int position) { if(mRowIds.moveToPosition(position)){ long rowId = mRowIds.getLong(0); Cursor c = mDataSource.getRowById(rowId); return c; }else{ return null; } } @Override public long getItemId(int position) { if(mRowIds.moveToPosition(position)){ long rowId = mRowIds.getLong(0); return rowId; }else{ return 0; } } @Override public View getView(int position, View convertView, ViewGroup parent) { mRowIds.moveToPosition(position); long rowId = mRowIds.getLong(0); Cursor cursor = mDataSource.getRowById(rowId); cursor.moveToFirst(); View v; if (convertView == null) { v = newView(mContext, cursor, parent); } else { v = convertView; } bindView(v, mContext, cursor); cursor.close(); return v; } public abstract View newView(Context context, Cursor cursor, ViewGroup parent); public abstract void bindView(View view, Context context, Cursor cursor); public interface DataSource { Cursor getRowIds(); Cursor getRowById(long rowId); } }
このクラスを使用するには、CursorAdapterの場合と同じ方法でnewViewメソッドとbindViewメソッドを実装し、QuickAdapter.DataSourceの実装を次のように記述する必要があります。
class MyDataSource implements QuickAdapter.DataSource { @Override public Cursor getRowIds() { return mDatabase.rawQuery("SELECT rowid FROM table1", new String[]{}); } @Override public Cursor getRowById(long rowId) { return mDatabase.rawQuery("SELECT * FROM table1 WHERE rowid = ?", new String[]{Long.toString(rowId)}); } }
おわりに
Samsung Galaxy Tab 10.1では、目立ったブレーキなしで、「クイックスクロール」が30万個のアイテムのリストで機能し、各アイテムは最大2Kbでした。 標準のCursorAdapterは遅くなり、見るのが怖かった。
CursorAdapterの場合、getViewメソッドを呼び出す最大遅延は553ミリ秒でしたが、QuickAdapterの場合は47ミリ秒でした。 パフォーマンス測定は、次のコードを使用して実行されました。
@Override public View getView(int arg0, View arg1, ViewGroup arg2) { long t1 = System.currentTimeMillis(); View result = super.getView(arg0, arg1, arg2); long t2 = System.currentTimeMillis(); long dt = t2-t1; if(dt>10){ Log.i("QuickAdapter", dt+""); } return result; }
ここに提示されているアイデアは、記事Sergey Slavinに よるSQLiteの選択クエリの「パフォーマンスの向上」への非標準アプローチから引用されています 。 また、数ヶ月前に同じアプローチをiOSに実装してくれた同僚のDmitry Tukhtamanovにも感謝します。
ここから写真を撮りました 。