详解CursorLoader在android中的应用

CursorLoader自API 11起引入,用于解决数据库数据变化时的cursor管理问题,确保UI更新的同步。它继承自Loader,通过ContentResolver获取cursor并在后台线程加载数据。主要回调方法包括loadInBackground()和deliverResult(),前者在后台获取cursor,后者在UI线程中传递结果。使用CursorLoader时通常结合LoaderManager进行管理。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

CursorLoader是从API 11开始引入的。一般涉及数据库的时候,都会用到cursor,如果是你设计的应用本地应用还好,在加载数据时cursor使用场景不会很频繁复杂。如若是联网的应用,面对的场景就会比较复杂,你的应用随时会从网上获取到新的内容并存储到数据库中,与此同时,你随时需要对界面的内容进行更新,所以你要管理好你的cursor: (a)对数据库进行监听,在数据变化时更新你的cursor,(b)加载新cursor时,要关闭旧的cursor。而CursorLoader解决上述的问题,使你能更加专注于android的应用的实现,无需关注一些细枝末节。CursorLoader可以当作一个cursor装载器,负责上述与cursor相关的事情。

CursorLoader的继承关系

java.lang.Object
   ↳          android.content.Loader<D>
                   ↳          android.content.AsyncTaskLoader<D>
                                   ↳          android.content.CursorLoader

其中,LoaderAsyncTaskLoader也都是从API 11引入的。从上面图示看到它继承自Loader,所以它是Loader的一种。它同样使用contentresolver方式来请求数据库,返回一个cursor

如何使用CursorLoader?

创建 cursorloader 必须要指定请求信息,来保证 CursorLoader 内部会返回一个你需要的 cursor
创建的两种方式:
A. 直接创建。可以直接在创建时候指定请求信息。
public CursorLoader ( Context  context,  Uri  uri,  String[]  projection,  String  selection,  String[]  selectionArgs,  String  sortOrder)
Added in  API level 11
Creates a fully-specified CursorLoader. See  ContentResolver.query()  for documentation on the meaning of the parameters. These will be passed as-is to that call.
CursorLoader 会根据系统提供的请求字段在 loadInbackground() 中去查询数据库并得到一个 cursor,
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                    mSelectionArgs, mSortOrder, mCancellationSignal);
B. 填充信息。
public CursorLoader ( Context  context)
Added in  API level 11
Creates an empty unspecified CursorLoader. You must follow this with calls to  setUri(Uri) setSelection(String) , etc to specify the query to perform.
先创建一个空的实例对象,再调用以下两个函数
  setUri(Uri) setSelection(String) setSelectionArgs(String[]) setSortOrder(String)
setProjection(String[]) .
本质上上面两个函数都是一样的,在源码,可以看到下面几个变量,
Uri mUri;
    String[] mProjection;
    String mSelection;
    String[] mSelectionArgs;
String mSortOrder;
这些正是保存请求信息的几个变量的引用,两种不同的创建方式,也就是两个不同的赋值函数,它们最终都是调用 上面的 cursor 的查询函数。
了解它的调用 的过程:

几个重要的回调方法:

public  Cursor  loadInBackground ()

该方法是在一个独立于UI线线程的工作线程中执行的。在这个方法中,完成了cursor的获取过程,然后将这个结果返回。该方法不会直接对外返回该结果,返回的cursor会在deliverResult(D)中被调用,这个方法是UI线程中执行的。如果你需要在UI线程里对cursor做一些操作,就可以在deliever(D)方法里执行。你可以根据自己的需要来定制自己的cusor。

下面是loadInBackground()的源码部分:

public Cursor loadInBackground() {
        synchronized (this) {
            if (isLoadInBackgroundCanceled()) {
                throw new OperationCanceledException();
            }
            mCancellationSignal = new CancellationSignal();
        }
        try {
            Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
                    mSelectionArgs, mSortOrder, mCancellationSignal);
            if (cursor != null) {
                try {
                    // Ensure the cursor window is filled.
                    cursor.getCount();
                    cursor.registerContentObserver(mObserver);
                } catch (RuntimeException ex) {
                    cursor.close();
                    throw ex;
                }
            }
            return cursor;
        } finally {
            synchronized (this) {
                mCancellationSignal = null;
            }
        }
    }

其中查询的cursor已经注册了监听器 ForceLoadContentObserver  所以当cursor有更新变化时,它会回调其中的onChange()函数,其中onChange()会回调onContentChanged()函数。所以,你可以在此函数中重写你需要执行的操作。所以这也是CursorLoader易用的原因。
public void deliverResult (D data)

该方法将loadInbackground(D data)返回的结果发送到注册的监听器。该方法只有被继承的子类调用,也必须在UI线程中调用。

在代码中如何应用CursorLoader?

下面是使用的CursorLoader的一个例子。在android的更新里面,与CursorLoader同时引进的还有LoaderManager(在此先不做详细说明),这个类用来管理各种Loader。

public class ContactFragment extends ListFragment implements OnQueryTextListener, 
        OnCloseListener, LoaderCallbacks<Cursor> {

    private SearchView mSearchView;
    private String mFilter;
    private SimpleCursorAdapter mAdapter;
    private static final String[] QUERY_PROJECTION = new String[] {
        Contacts._ID,
        Contacts.DISPLAY_NAME,
        Contacts.CONTACT_STATUS,
        Contacts.CONTACT_PRESENCE,
        Contacts.PHOTO_ID,
        Contacts.LOOKUP_KEY
    };
    
    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        
        // TODO Auto-generated method stub
        super.onActivityCreated(savedInstanceState);
        setEmptyText("No phone numbers!");
        setHasOptionsMenu(true);
        mAdapter = new SimpleCursorAdapter(getActivity(), android.R.layout.simple_list_item_2, null, 
                new String[]{Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS}, new int[]{android.R.id.text1, android.R.id.text2});
        setListAdapter(mAdapter);
        setListShown(false);
        
        //init the loader.
        getLoaderManager().initLoader(0, null, this);
    }
    
    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        
        // TODO Auto-generated method stub
        mSearchView = new MySearchView(getActivity());
        mSearchView.setOnCloseListener(this);
        mSearchView.setOnQueryTextListener(this);
        MenuItem item = menu.add("Search");
        item.setIcon(android.R.drawable.ic_menu_search);
        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM 
                | MenuItem.SHOW_AS_ACTION_COLLAPSE_ACTION_VIEW);
        mSearchView.setIconifiedByDefault(true);
        item.setActionView(mSearchView);
        MenuItem insert = menu.add("Insert");
        insert.setOnMenuItemClickListener(new OnMenuItemClickListener() {
            
            @Override
            public boolean onMenuItemClick(MenuItem item) {
                testInsert();
                return false;
            }
        });
    }
    
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        
        Uri baseUri = null;
        if (mFilter != null) {
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI, Uri.encode(mFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        
        //Create a CursorLoader to get the data.
        return new CursorLoader(getActivity(), baseUri,
                QUERY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    @Override
    public void onLoadFinished(Loader<Cursor> arg0, Cursor arg1) {
        
        //Load the data 
        mAdapter.swapCursor(arg1);
        if (isResumed()) {
            setListShown(true);
        } else {
            setListShownNoAnimation(true);
        }
        
    }

    @Override
    public void onLoaderReset(Loader<Cursor> arg0) {
        
        // TODO Auto-generated method stub
        mAdapter.swapCursor(null);
    }

    @Override
    public boolean onClose() {
        
        if (!TextUtils.isEmpty(mSearchView.getQuery())) {
            mSearchView.setQuery("", true);
        }
        return true;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        
        String newFilter = !TextUtils.isEmpty(newText) ? newText : null;
        if (null == newFilter  && null == mFilter) {
            return true;
        }
        if (null != mFilter && mFilter.equals(newFilter)) {
            return true;
        }
        mFilter = newFilter;
        getLoaderManager().restartLoader(0, null, this);
        return false;
    }

    @Override
    public boolean onQueryTextSubmit(String arg0) {
        
        // TODO Auto-generated method stub
        return false;
    }
    
    /**
     * A customized searchView used to search the information you want with key words filter.
     * ClassName: MySearchView <br/>
     * Function: TODO ADD FUNCTION. <br/>
     * Reason: TODO ADD REASON(可选). <br/>
     * date: Apr 23, 2014 12:02:57 AM <br/>
     *
     * @author Lawrence
     * @version ContactFragment
     * @since JDK 1.6
     */
    class MySearchView extends SearchView {

        public MySearchView(Context context) {
            
            super(context);
            // TODO Auto-generated constructor stub
            
        }
        
        @Override
        public void onActionViewCollapsed() {
            
            // TODO Auto-generated method stub
            super.onActionViewCollapsed();
            setQuery("", false);
        }
        
    }
    
    private int i = 0;
    
    
    //insert a contact to database.
    public void testInsert(){
        ContentValues values = new ContentValues();
        //first get a rawContactId by inserting a null value to RawContact table
        Uri rawContactUri = getActivity().getContentResolver().insert(RawContacts.CONTENT_URI, values);
        long rawContactId = ContentUris.parseId(rawContactUri);
        
        //insert a name to data table
        values.clear();
        values.put(Data.RAW_CONTACT_ID, rawContactId);
        values.put(Data.MIMETYPE, StructuredName.CONTENT_ITEM_TYPE);
        values.put(StructuredName.DISPLAY_NAME, "Lawrence" + (i++));   
        //insert the real values  
        getActivity().getContentResolver().insert(Data.CONTENT_URI,values);   
        //insert a phone number
        values.clear();   
        values.put(Phone.RAW_CONTACT_ID, rawContactId);   
        //String "Data.MIMETYPE":The MIME type of the item represented by this row  
        //String "CONTENT_ITEM_TYPE": MIME type used when storing this in data table.  
        values.put(Data.MIMETYPE,Phone.CONTENT_ITEM_TYPE);   
        values.put(Phone.NUMBER,"13410539352");   
        getActivity().getContentResolver().insert(Data.CONTENT_URI,values); 
    }

}
这是一个利用CursorLoader来查询联系人,然后加载联系人的例子,效果图如下图所示。Actionbar中的Insert按钮可以向数据库插入一条联系人数据,点击这个按钮后,程序会自动将数据更新显示到 UI界面,这个正是CursorLoader的自我更新工作,


例子源码下载地址:

http://download.csdn.net/detail/a2758963/7242579


参考文章:

http://developer.android.com/reference/android/content/CursorLoader.html

http://developer.android.com/reference/android/app/LoaderManager.html



评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值