美文网首页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