美文网首页Android开发Android开发经验谈Android开发
Android如何从数据库中加载海量数据

Android如何从数据库中加载海量数据

作者: 生活简单些 | 来源:发表于2019-02-19 09:50 被阅读17次

  在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

相关文章

网友评论

    本文标题:Android如何从数据库中加载海量数据

    本文链接:https://www.haomeiwen.com/subject/agpjeqtx.html