Android如何從數據庫中加載海量數據

  在Android3.0之前,很多應用程序響應性能方面有缺陷,其中比較典型的錯誤行爲是在UI線程中執行了查詢數據操作,尤其是一次性從database查出大量數據並加載到ListView裏,用這種方式載入數據是最差的選擇,硬件偏弱的手機會假死會兒。 其實體驗最好的還屬手機自帶通訊錄App這類應用,滑動絲般順滑。
  在Android 3.0版本之前一般的做法是用Activity提供的startManagingCursor()和stopManagingCursor(), 已經deprecated的API我們就不談了,3.0之後取而代之的是Loader,想必Loader的使用大家都有所知道:

public class CursorLoaderListFragment extends ListFragment {
    SimpleCursorAdapter mAdapter;
    SearchView mSearchView;
    String mCurFilter;


    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.CONTACT_STATUS,
    };

    private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            // This is called when a new Loader needs to be created.
            String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                    + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                    + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
            return new CursorLoader(getActivity(),
                    ContactsContract.Contacts.CONTENT_URI,
                    CONTACTS_SUMMARY_PROJECTION, select, null,
                    ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        }

        @Override
        public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
            // Swap the new cursor in.  (The framework will take care of closing the
            // old cursor once we return.)
            mAdapter.swapCursor(data);

            // The list should now be shown.
            if (isResumed()) {
                setListShown(true);
            } else {
                setListShownNoAnimation(true);
            }
        }

        @Override
        public void onLoaderReset(@NonNull Loader<Cursor> loader) {
            // This is called when the last Cursor provided to onLoadFinished()
            // above is about to be closed.  We need to make sure we are no
            // longer using it.
            mAdapter.swapCursor(null);
        }
    };

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        setEmptyText("No phone numbers");

        setHasOptionsMenu(true);
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[] { ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS },
                new int[] { android.R.id.text1, android.R.id.text2 }, 0);
        setListAdapter(mAdapter);

        LoaderManager.getInstance(this).initLoader(0, null, mLoaderCallback);
    }

    public boolean onQueryTextChange(String newText) {
        String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
        if (mCurFilter == null && newFilter == null) {
            return true;
        }
        if (mCurFilter != null && mCurFilter.equals(newFilter)) {
            return true;
        }
        mCurFilter = newFilter;
        LoaderManager.getInstance(this).restartLoader(0, null, mLoaderCallback);
        return true;
    }
}

不難看出只要實現三個回調函數就能創建出一個LoaderCallbacks,並將此丟給LoaderManager去initLoader或者restartLoader,initLoader是第一次查詢使用的,restartLoader是二次查詢使用的。簡單是簡單不過有個東西不知有沒有發現:在此demo 中onCreateLoader()方法返回值是CursorLoader對象,它的構造函數必須是Uri,意味着必須是基於Content Provider實現的數據庫纔可以使用,可是現實項目需要ContentProvider的不多吧,很多是純sqlite的,爲了此場景硬生生將sqlite的實現改成ContentProvider得不償失。

  當翻看onCreateLoader()方法定義時候,發現返回值不是CursorLoader而是LoaderCursorLoader只是Loader的一個子類而已,因此轉機來了:定義一個類繼承Loader並內部用Cursor實現,最終返回自定義類的對象給onCreateLoader():

public abstract class SQLiteCursorLoader extends AsyncTaskLoader<Cursor> {
    private Cursor lastCursor;
    private Cursor queryCursor;

    public SQLiteCursorLoader(Context context, Cursor cursor) {
        super(context);
        queryCursor = cursor;
    }

    /**
     * Runs on a worker thread, loading in our data. Delegates the real work to concrete subclass'
     * buildCursor() method.
     */
    @Override
    public Cursor loadInBackground() {
        final Cursor cursor = queryCursor;

        if (cursor != null) {
            // Ensure the cursor window is filled
            cursor.getCount();
        }

        return cursor;
    }

    /**
     * Runs on the UI thread, routing the results from the background thread to whatever is using
     * the Cursor (e.g., a CursorAdapter).
     */
    @Override
    public void deliverResult(final Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }

            return;
        }

        final Cursor oldCursor = lastCursor;
        lastCursor = cursor;

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

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

    /**
     * Starts an asynchronous load of the 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. Must be called from the UI thread.
     */
    @Override
    protected void onStartLoading() {
        if (lastCursor != null) {
            deliverResult(lastCursor);
        }

        if (takeContentChanged() || lastCursor == null) {
            forceLoad();
        }
    }

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

    /**
     * Must be called from the UI thread, triggered by a call to cancel(). Here, we make sure our
     * Cursor is closed, if it still exists and is not already closed.
     */
    @Override
    public void onCanceled(final Cursor cursor) {
        if (cursor != null && !cursor.isClosed()) {
            cursor.close();
        }
    }

    /**
     * Must be called from the UI thread, triggered by a call to reset(). Here, we make sure our
     * Cursor is closed, if it still exists and is not already closed.
     */
    @Override
    protected void onReset() {
        super.onReset();

        // Ensure the loader is stopped
        onStopLoading();

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

        lastCursor = null;
    }
}

事實上並沒有直接繼承Loader, 而是繼承的AsyncTaskLoader,從名字看就知道它是類似AsyncTask的原理實現的,SDK的CursorLoader也是基於AsyncTaskLoader實現的,當有了SQLiteCursorLoader我們就可以用它創建LoaderManager.LoaderCallbacks<Cursor>了:

private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() {

        @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            // This is called when a new Loader needs to be created.
            String sql = "SELECT * FROM TABLE_XX";
            return new SQLiteCursorLoader(getActivity(), sql);
        }

        @Override
        public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
            // ...
        }

        @Override
        public void onLoaderReset(@NonNull Loader<Cursor> loader) {
            // ...
        }
    };

關於Android數據庫方面的開發用純SQL的確有點累,可以考慮ORM的思路,以前我也寫了一個輕量級的,也一直使用中,SQLiteCursorLoader其實我提供了2個構造方法,一個如上傳Cursor,另外一個是傳BuilderSupport, 它有2個實現,分別爲ConditionBuilderMultiTableConditionBuilder, 通過他們可以以面向對象方式來查詢數據庫,一個是用於單表查詢,一個用於多表查詢,具體可以參考下:Light-DAO

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章