cursor管理

  使用cursor的时候需要注意在使用完之后将其关闭,什么时候关闭也是一个需要注意的问题,稍不小心就可能会出错。我们自己管理cursor可能不是那么容易,问题出现这个或那样的问题,Android系统提供了一套curosr的管理,下面让我们来了解一下。

managedQueryquery的区别

  我们都知道在Android系统中,SQLite数据库的相关操作方式被封装为内容提供 Content Provider,可以帮助那些不会SQL语言的开发者快速实现Android平台上的数据库操作,但是平时我们在查询时一般返回的是Cursor对象,从本质上来看这两个API是不同的类提供的。比如 ContentResolver.query(),以及 Activity.managedQuery()所以,我们看到一个是ContentResolver提供的查询方法,位于android.content.ContextWrapper.getContentResolver(),另一个则为Activity。

  这两种方法的参数是一样的,但是Activity类的方法在整个生命周期中受Activity的影响,而常规我们处理数据逻辑可能单独分成一个类,直接使用Context对象传递实例句柄,对于数据库查询操作如果数据较为庞大尽量使用异步的 AsyncQueryHandler方法防止阻塞线程。

  Activity 里面提供了一个 managedQuery() 方法,按照 Android SDK 里面的说明,“the activity will manage its lifecycle for you.” 听起来很好,Activity 可以替你管理 Cursor 的生命周期了,就不用记着去 close() 了,代码可以更简洁。

  但是 Activity 是怎么去管理 Cursor 的生命周期的呢?SDK 文档没说。最近遇到一个 bug,在一个 Activity 中,用 managedQuery() 查询数据库,将查询得到的 Cursor 用 CursorAdapter 与 ListView 绑定。然后在 Activity 里面执行批量删除数据表记录操作,因为耗时比较长,所以用了多线程处理。测试团队发现的 bug 是,在删除操作进行过程中,如果按下 Home 键,应用就崩溃了。崩溃原因是 Cursor 被释放了,导致工作线程的删除操作异常。

  看了 Activity.java 的源码之后就明白为什么会崩溃了。managedQuery() 其实无非就是把查询得到的 Cursor 放到了 Activity 类的一个数组成员变量mManagedCursors中,然后当 Activity stop 的时候,将这个数组里的每个 cursor 都调用mCursor.deactivate();使curosr无效,直到下次再调用requery()方法,以及在 restart()的时候,将数组里的每个 cursor 都重新查询一次。所以在按下 Home 键之后,Activity 调用了 OnStop 了,cursor 也就无效了,如果有个线程还在继续使用这个 cursor,就会抛异常了。

  因此,在用 managedQuery() 的时候,需要清楚 cursor 什么时候会被释放,并考虑好自己的代码在 cursor 被释放后不再需要使用这个 cursor.

  可以用普通的query,然后运行 startManagingCursor(cursor),同样可以把cursor交给系统去管理,不用担心cursor没有close的情况了。 

 

使用CurosrLoader管理cursor

  Android 3.0引入了CursorLoader实现异步加载数据,为了避免同步查询数据库时阻塞UI线程的问题。在API 11之前可以通过下载支持库,来使之前的系统支持此功能.

  在Android 3.0之后,官方推荐使用CursorLoader来对curosr进行管理,如果想在3.0之前的版本使用,需要继承FragmentActivity,该类位于android-support-v4.jar包中,直接使用FragmentActivity会报错,需要导入该包(该包位于F:\android-sdk\extras\android\support)。

  相信不少人都知道Android compatibility这个兼容包的存在。是的,Android compatibility包里面就有FragmentActivity和Fragment这套东西。它有两个版本v4和v13,其中v4就支持支持从android sdk1.6开始,可以使用Fragment。Android compatibility包是一个静态的jar包,我们只需要将它置于工程中,导入进工程,就能很方便的引用到FragmentActivity和Fragment了。

下面的代码展示了在Android 3.0之前使用Loader的用法:

import android.os.Bundle;

import android.support.v4.app.FragmentActivity;

import android.support.v4.app.LoaderManager;

import android.support.v4.content.Loader;

import android.widget.Toast;

 

public class MyActivity extends FragmentActivity implements LoaderManager.LoaderCallbacks<Object> {

public void onCreate(Bundle savedInstanceState) {

    super.onCreate(savedInstanceState);

    setContentView(R.layout.main);

    getSupportLoaderManager().initLoader(0, null, this);

}

 

public Loader<Object> onCreateLoader(int i, Bundle bundle){

    return null; // TODO

}

 

public void onLoadFinished(Loader loader, Object o) {

    Toast.makeText(this, "onLoadFinished", Toast.LENGTH_SHORT).show();

}

 

public void onLoaderReset(Loader loader)    {

    Toast.makeText(this, "onLoaderReset", Toast.LENGTH_SHORT).show();

}

}

 

 

Loaders,中文可理解为“加载器”,在Android3.0中新增。从字面含义可见其功能,即提供数据加载。特别地,加载数据的方式为异步。它具有以下特点:

l  Loaders用于所有的Activity和Fragment;

l  提供异步数据装载机制;

l  监控他们的来源数据变化情况,在数据发生变化的时候传递新的结果;

l  自动重连到最后一个数据加载器游标,因此不需要重新查询数据

如何在应用中使用Loaders

使用Loaders的先决条件:

l  需要一个Activity 或者 Fragmnet

l  一个LoaderManager实例

l  一个用于加载数据的的CursorLoader对象(依赖于ContentProvider)

l  一个LoaderManager.LoaderCallbacks的实现类.

l  一个数据展现适配器,比如SimpleCursorAdapter

l  一个数据源,比如ContentProvider

启动数据加载器Loaders

LoaderManager管理者一个Activity或者Fragment中的一个或多个Loader实例,每个Activity或者Fragment只有对应一个LoaserManager。

         一般在Activity的onCreate方法或者Fragment的onActivityCreated方法中初始化一个Loader:

getLoaderManager().initLoader(0, null, this);

参数:

1、  第一个参数:0 为Loader的唯一标识ID;

2、  第二个参数: 为Loader的构造器可选参数,这里为null;

3、  第三个参数:this,这里表示当前Activity对象或者Fragment对象,提供给LoaderManager对象进行数据汇报。

InitLoader()方法保证了Loader初始化及对象激活,执行这个方法有2个可能的结果:

1、  如果ID存在,则重复利用;

2、  如果ID不存在,则出发LoaderManager.LoaderCallbacks的onCreateLoader()方法新创建一个Loader并返回;

 

不管在什么情况下,只有Loader状态发生了变化,与之关联的LoaderManager.LoaderCallbacks实现类都会被告知;

  你可能注意到了,initLoader返回的Loader对象并未与任何变量关联,那是因为LoaderManager有自动的Loader管理功能;LoaderManager在必要的时候自动启动及停止数据加载操作,并且维护者Loader的状态;这就意味着,你很少直接与Loader进行交互。一般地,使用LoaderManager.LoaderCallbacks的onCreateLoader()方法去干预数据加载的过程中的特殊事件。

如何重启数据加载器Loaders

  在上面创建Loaders时,如果ID不存在则创建,否则使用旧的Loader,但有些时候,我们需要清理掉旧的数据重新开始。

  使用restartLoaser()可以做到。比如,SearchView.OnQueryTextListener的实现类,在查询条件发生改变时重启Loaders,以便获取最新的查询结果。

public boolean onQueryTextChanged(String newText) {

    // Called when the action bar search text has changed.  Update

    // the search filter, and restart the loader to do a new query

    // with this filter.

    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;

    getLoaderManager().restartLoader(0, null, this);

    return true;}

如何使用LoaderManager的回调方法

  LoaderManager.LoaderCallbacks 接口是客户端与LoaderManager进行交互的腰带。

  Loader ,特别是CursorLoader,期望在停止状态后保存它们的状态。这样的话,用户在交互过程中,就避免了数据的重新加载而导致UI卡死的局面。使用回调函数,就可以知道什么时候去创建一个新的Loader,并且告知应用程序什么时候停止使用Loader加载的数据。

回调方法有:

l  onCreateLoader():根据给定的ID创建新的Loader;

l  onLoaderFinished():当Loader完成数据加载后调用;

l  onLoaderReset():Loader重置,使之前的数据无效;

 

onCreateLoader使用实例:

// If non-null, this is the current filter the user has provided.

String mCurFilter;

...

public Loader<Cursor> onCreateLoader(int id, Bundle args) {

    // This is called when a new Loader needs to be created.  This

    // sample only has one Loader, so we don't care about the ID.

    // First, pick the base URI to use depending on whether we are

    // currently filtering.

    Uri baseUri;

    if (mCurFilter != null) {

        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,

                  Uri.encode(mCurFilter));

    } else {

        baseUri = Contacts.CONTENT_URI;

    }

 

    // Now create and return a CursorLoader that will take care of

    // creating a Cursor for the data being displayed.

    String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("

            + Contacts.HAS_PHONE_NUMBER + "=1) AND ("

            + Contacts.DISPLAY_NAME + " != '' ))";

    return new CursorLoader(getActivity(), baseUri,

            CONTACTS_SUMMARY_PROJECTION, select, null,

            Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");}

CursorLoader(Context context, Uri uri, String[] projection, String selection, String[] selectionArgs, String sortOrder)构造器参数解释:

Context:上下文,这里是Activity对象或者Fragment对象

Uri:内容检索地址

Projection:要显示的列,传null表示查询所有的列

Selection:查询过滤语句,类似SQL WHERE ,传null,表示查询所有

selectionArgs:查询参数,替换在selection中定义的  ?

sortOrder:排序定义,类似SQL ORDER BY

完整的实例

public static class CursorLoaderListFragment extends ListFragment

        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

 

    // This is the Adapter being used to display the list's data.

    SimpleCursorAdapter mAdapter;

 

    // If non-null, this is the current filter the user has provided.

    String mCurFilter;

 

    @Override public void onActivityCreated(Bundle savedInstanceState) {

        super.onActivityCreated(savedInstanceState);

 

        // Give some text to display if there is no data.  In a real

        // application this would come from a resource.

        setEmptyText("No phone numbers");

 

        // We have a menu item to show in action bar.

        setHasOptionsMenu(true);

 

        // Create an empty adapter we will use to display the loaded data.

        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 }, 0);

        setListAdapter(mAdapter);

 

        // Prepare the loader.  Either re-connect with an existing one,

        // or start a new one.

        getLoaderManager().initLoader(0, null, this);

    }

 

    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {

        // Place an action bar item for searching.

        MenuItem item = menu.add("Search");

        item.setIcon(android.R.drawable.ic_menu_search);

        item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);

        SearchView sv = new SearchView(getActivity());

        sv.setOnQueryTextListener(this);

        item.setActionView(sv);

    }

 

    public boolean onQueryTextChange(String newText) {

        // Called when the action bar search text has changed.  Update

        // the search filter, and restart the loader to do a new query

        // with this filter.

        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;

        getLoaderManager().restartLoader(0, null, this);

        return true;

    }

 

    @Override public boolean onQueryTextSubmit(String query) {

        // Don't care about this.

        return true;

    }

 

    @Override public void onListItemClick(ListView l, View v, int position, long id) {

        // Insert desired behavior here.

        Log.i("FragmentComplexList", "Item clicked: " + id);

    }

 

    // These are the Contacts rows that we will retrieve.

    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {

        Contacts._ID,

        Contacts.DISPLAY_NAME,

        Contacts.CONTACT_STATUS,

        Contacts.CONTACT_PRESENCE,

        Contacts.PHOTO_ID,

        Contacts.LOOKUP_KEY,

    };

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        // This is called when a new Loader needs to be created.  This

        // sample only has one Loader, so we don't care about the ID.

        // First, pick the base URI to use depending on whether we are

        // currently filtering.

        Uri baseUri;

        if (mCurFilter != null) {

            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,

                    Uri.encode(mCurFilter));

        } else {

            baseUri = Contacts.CONTENT_URI;

        }

 

        // Now create and return a CursorLoader that will take care of

        // creating a Cursor for the data being displayed.

        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("

                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("

                + Contacts.DISPLAY_NAME + " != '' ))";

        return new CursorLoader(getActivity(), baseUri,

                CONTACTS_SUMMARY_PROJECTION, select, null,

                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");

    }

 

    public void onLoadFinished(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);

    }

 

    public void onLoaderReset(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);

    }}

 

你可能感兴趣的:(Cursor)