• Android中CursorLoader的使用、原理及注意事项


    前言

    最近在项目中涉及到了即时聊天,因此不可避免地要用到实时刷新的功能,因为在以前的项目中见到别人使用CursorLoader+CursorAdapter+ContentProvider的机制来实现实时刷新,于是没有多研究就直接照搬了这个机制,直到后来出现了发送消息后不能更新到界面上的问题,查了很久也查不出原因,于是就想从这个机制本身出发,看看有可能是在哪个环节出了问题。

    使用

    1.让Activity或Fragment实现LoaderManager.LoaderCallbacks< D >接口

        由于我们的数据存储在数据库中,因此这里的泛型应该替换为Cursor 
        这个接口中有三个方法:

    // 这个方法在初始化Loader时回调,我们要在这个方法中实例化CursorLoader
    public Loader<D> onCreateLoader(int id, Bundle args);
    // 加载数据完成后回调到这个方法,我们一般在这里调用CursorAdapter的changeCursor或swapCursor进行界面刷新的操作
    public void onLoadFinished(Loader<D> loader, D data);
    // 这个方法是在重启Loader时调用,一般可以不管
    public void onLoaderReset(Loader<D> loader);

    2.创建对应的ContentProvider

    @Override
    public Cursor query(Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder) {
            synchronized (DBLOCK) {
                SQLiteDatabase db = WeChatDBManager.getInstance(getContext()).getDatabase();
                SQLiteQueryBuilder queryBuilder = new SQLiteQueryBuilder();
                Cursor cursor = null;
                switch (sUriMatcher.match(uri)) {
                    case CODE_CHAT_HISTORY:
                        queryBuilder.setDistinct(false);
                        queryBuilder.setTables(uri.getQuery());
                        cursor = queryBuilder.query(db, 
                                projection, 
                                selection, 
                                selectionArgs,
                                null, 
                                null, 
                                sortOrder 
                        );
                        break;
                }
                // 对查询到的结果集对应的Uri设置观察者
                if (cursor != null)
                    cursor.setNotificationUri(getContext().getContentResolver(), uri);
                return cursor;
            }
    }
    
    @Override
        public Uri insert(Uri uri, ContentValues values) {
            ...
            // 通知对应的Uri数据发生改变
            getContext().getContentResolver().notifyChange(uri, null);
        }
    
        @Override
        public int delete(Uri uri, String selection, String[] selectionArgs) {
            ...
            // 通知对应的Uri数据发生改变
            getContext().getContentResolver().notifyChange(uri, null);
        }
    
        @Override
        public int update(Uri uri, ContentValues values, String selection, String[] selectionArgs) {
            ...
            // 通知对应的Uri数据发生改变
            getContext().getContentResolver().notifyChange(uri, null);
        }

    3.调用getLoaderManager().initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks< D> callback)初始化

    原理

    1.initLoader

    public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
            if (mCreatingLoader) {
                throw new IllegalStateException("Called while creating a loader");
            }
    
            LoaderInfo info = mLoaders.get(id);
    
            if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
    
            if (info == null) {
                // 创建Loader
                info = createAndInstallLoader(id, args,  (LoaderManager.LoaderCallbacks<Object>)callback);
                if (DEBUG) Log.v(TAG, "  Created new loader " + info);
            } else {
                if (DEBUG) Log.v(TAG, "  Re-using existing loader " + info);
                info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
            }
    
            if (info.mHaveData && mStarted) {
                // Loader中已经有数据,这里最终会回调到onLoadFinished方法
                info.callOnLoadFinished(info.mLoader, info.mData);
            }
    
          return (Loader<D>)info.mLoader
    }

    这里主要关注createAndInstallLoader方法

      private LoaderInfo createAndInstallLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<Object> callback) {
            try {
                mCreatingLoader = true;
                // 这里首先会创建LoaderInfo对象
                LoaderInfo info = createLoader(id, args, callback);
                // 然后启动LoaderInfo
                installLoader(info);
                return info;
            } finally {
                mCreatingLoader = false;
            }
        }

    首先来看看createLoader

      private LoaderInfo createLoader(int id, Bundle args,
                LoaderManager.LoaderCallbacks<Object> callback) {
            LoaderInfo info = new LoaderInfo(id, args,  callback);
            // 这里回调到了我们要实现的onCreateLoader方法
            Loader<Object> loader = callback.onCreateLoader(id, args);
            info.mLoader = loader;
            return info;
        }

    在onCreateLoader中我们创建了具体的Loader,即CursorLoader

      @Override
        public Loader<Cursor> onCreateLoader(int id, Bundle args) {
            return new CursorLoader(...);
        }

    接着执行到installLoader

    void installLoader(LoaderInfo info) {
            // 把上一步创建的LoaderInfo对象存到列表中
            mLoaders.put(info.mId, info);
            if (mStarted) {
                // 启动Loader
                info.start();
            }
        }
    
        void start() {
                ...
                // start方法中我们只关注startLoading方法
                mLoader.startLoading();
                ...
            }
    
        public final void startLoading() {
            mStarted = true;
            mReset = false;
            mAbandoned = false;
            // onStartLoading是个空方法,我们要看CursorLoader中的具体实现
            onStartLoading();
        }
    
        @Override
        protected void onStartLoading() {
            // 更新数据
            if (mCursor != null) {
                deliverResult(mCursor);
            }
            // 初始化时调用 主要看这里,这里又调到父类Loader中的forceLoad
            if (takeContentChanged() || mCursor == null) {
                forceLoad();
            }
        }
    
        public void forceLoad() {
            // 这里的onForceLoad又是一个空方法,调用的是子类AsyncTaskLoader中的onForceLoad
            onForceLoad();
        }
    
        @Override
        protected void onForceLoad() {
            super.onForceLoad();
            cancelLoad();
            // 这里执行了一个异步任务,接下来看看这个异步任务具体做了什么事
            mTask = new LoadTask();
            if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
            executePendingTask();
        }

    接下来具体看一下这个异步任务,具体关注其中的doInBackground和onPostExecute

     @Override
        protected D doInBackground(Void... params) {
            if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
            try {
                // 这里执行了onLoadInBackground
                D data = AsyncTaskLoader.this.onLoadInBackground();
                if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    throw ex;
                }
                if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
                return null;
            }
        }
    
        protected D onLoadInBackground() {
            // 这里调用到CursorLoader的loadInBackground
            return loadInBackground();
        }
    
        @Override
        public Cursor loadInBackground() {
            synchronized (this) {
                if (isLoadInBackgroundCanceled()) {
                    throw new OperationCanceledException();
                }
                mCancellationSignal = new CancellationSignal();
            }
            try {
                // 这里调用ContentResolver进行查询,查询条件就是前面我们在onCreateLoader创建CursorLoader对象时
                // 传入的,这里最终会调用我们的ContentProvider,我们在ContentProvider的query中对Cursor对象设置了监听的Uri
                Cursor cursor = ContentResolverCompat.query(getContext().getContentResolver(),
                        mUri, mProjection, mSelection, mSelectionArgs, mSortOrder,
                        mCancellationSignal);
                if (cursor != null) {
                    try {
                        // 这里给Cursor对象注册了一个内容观察者,而在上面我们设置了要监听的Uri,因此当数据变化时,首先会通知Cursor,然后Cursor再触发ForceLoadContentObserver中的onChange
                        cursor.getCount();
                        cursor.registerContentObserver(mObserver);
                    } catch (RuntimeException ex) {
                        cursor.close();
                        throw ex;
                    }
                }
                return cursor;
            } finally {
                synchronized (this) {
                    mCancellationSignal = null;
                }
            }
        }
    
        public final class ForceLoadContentObserver extends ContentObserver {
            public ForceLoadContentObserver() {
                super(new Handler());
            }
    
            @Override
            public boolean deliverSelfNotifications() {
                return true;
            }
    
            @Override
            public void onChange(boolean selfChange) {
                onContentChanged();
            }
        }
    
        public void onContentChanged() {
            if (mStarted) {
                // 这里又回到了forceLoad方法,接下来就是重复一遍上面的流程
                forceLoad();
            } else {
                mContentChanged = true;
            }
        }

    异步任务最后会执行onPostExecute

      @Override
        protected void onPostExecute(D data) {
            if (DEBUG) Log.v(TAG, this + " onPostExecute");
            try {
                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
            } finally {
                mDone.countDown();
            }
        }
    
        void dispatchOnLoadComplete(LoadTask task, D data) {
            if (mTask != task) {
                if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
                dispatchOnCancelled(task, data);
            } else {
                if (isAbandoned()) {
                    // This cursor has been abandoned; just cancel the new data.
                    onCanceled(data);
                } else {
                    commitContentChanged();
                    mLastLoadCompleteTime = SystemClock.uptimeMillis();
                    mTask = null;
                    if (DEBUG) Log.v(TAG, "Delivering result");
                    // 传递数据
                    deliverResult(data);
                }
            }
        }
    
        public void deliverResult(D data) {
            if (mListener != null) {
                // 回调到LoaderManager中的onLoadComplete
                mListener.onLoadComplete(this, data);
            }
        }
    
        @Override
        public void onLoadComplete(Loader<Object> loader, Object data) {
            ...
            // 这里我们只关注callOnLoadFinished,这个方法中最终会回调到我们的onLoadFinished
            if (mData != data || !mHaveData) {
                mData = data;
                mHaveData = true;
                if (mStarted) {
                    callOnLoadFinished(loader, data);
                }
            }
            ...
        }
    
        void callOnLoadFinished(Loader<Object> loader, Object data) {
                if (mCallbacks != null) {
                    String lastBecause = null;
                    if (mHost != null) {
                        lastBecause = mHost.mFragmentManager.mNoTransactionsBecause;
                        mHost.mFragmentManager.mNoTransactionsBecause = "onLoadFinished";
                    }
                    try {
                        if (DEBUG) Log.v(TAG, "  onLoadFinished in " + loader + ": "
                                + loader.dataToString(data));
                        // 回调到我们的onLoadFinished
                        mCallbacks.onLoadFinished(loader, data);
                    } finally {
                        if (mHost != null) {
                            mHost.mFragmentManager.mNoTransactionsBecause = lastBecause;
                        }
                    }
                    mDeliveredData = true;
                }
            }

    整个流程还是比较清晰的,再梳理一遍:

    1. 初始化并启动Loader,会在createLoader方法中回调我们的onCreateLoader,我们在这里生成一个CursorLoader对象,其中设置了要查询的条件
    2. 在CursorLoader的onForceLoad实现中有一个异步任务,这个异步任务的loadInBackground方法中根据我们设置的查询条件查询数据库,最终会调用到我们的ContentProvider的query方法进行查询
    3. 查询完成会得到一个Cursor对象,我们调用cursor.setNotificationUri(getContext().getContentResolver(), uri)为这个Cursor设置要监听的Uri,同时CursorLoader会为这个Cursor对象注册一个内容观察者ForceLoadContentObserver
    4. 异步任务执行完成后会回调我们的onLoadFinished方法,我们在onLoadFinished方法中调用CursorAdapter的changeCursor或swapCursor方法,最终就能让我们的界面自动刷新
    5. 当我们用ContentProvider增删改数据时,只需在最后调用getContext().getContentResolver().notifyChange(uri, null),就会通知到上面的Cursor对象(因为Uri相同),再由Cursor触发内容观察者的onChange方法,最终又会调用到onForceLoad,重复上述过程

    遇到的问题

    根据上面的流程我们可以知道,每次数据发生改变时,最后都会触发loadInBackground中的查询,但是这里的查询条件一直是我们在创建CursorLoader时设置的查询条件,而我的项目中涉及到了分页查询(应用场景就是类似手机qq查看历史聊天记录),发生的问题就是当新增的数据达到一定数量时,界面就不会更新了,即如果在查询条件中含有动态改变的limit条件(如分页查询时的页数),就会产生问题。

    解决方法

    我的解决方法是每次数据库变化之后都通过CursorLoader的一系列set方法更新查询条件

  • 相关阅读:
    RabbitMQ的ACK机制
    Flex保存文件 FileReference.save(data,filename)
    Flex Builder cannot locate the required debugger version of Flash Player
    Flex每日小记
    IT民工
    R读取文件内容到Frame
    ArcGIS9.2 9.3
    超时空的心情
    ArcMap中设置.mxd相对路径
    MyEclipse Flex Tomcat BlazeDS
  • 原文地址:https://www.cnblogs.com/zhujiabin/p/6595135.html
Copyright © 2020-2023  润新知