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

来源:互联网 发布:遥感数据 分类 建模 编辑:程序博客网 时间:2024/06/06 23:25

前言

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

使用

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

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

// 这个方法在初始化Loader时回调,我们要在这个方法中实例化CursorLoaderpublic 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

@Overridepublic 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方法更新查询条件

0 0
原创粉丝点击