Loaders

来源:互联网 发布:创维电视机推荐 知乎 编辑:程序博客网 时间:2024/05/20 06:22

Loader在android 3.0 被引进,使得在activity或fragment异步下载数据更简单。具备一下特点:

1. 对每个activity和fragment都适合。

2. 提供异步下载数据

3. 与原数据保持同步,随时更新。

4. 当配置变化导致重新创建时,自动与最近的loader的连接。这样就不需要重新查找数据。

Loader API Summary (Loader API 总结)

下面列出有可能在使用Loader用到的类和接口。


上面列出的这些类是用于实现Loader重要的组建。对于你创建的每个Loader,你不需要所有上面的逐渐,但你必须有一个LoaderManager的引用以便用来初始化一个Loader,还必须有一个实现Loader的类,比如CursorLoader。下面演示如何使用这些类和接口。

Using Loaders in an Application (在一个应用中使用Loader)

这部分讲解在android应用中如何使用Loader,一个使用Loader的应用包含下面几个部分:

一个activity或fragment

一个LoaderManager实例

一个从ContentProvider下载数据的CursorLoader。你也可以自己实现Loader或者AsyncTaskLoader接口从其它数据源下载数据。

一个用来显示Loader的数据,通常使用SimpleCursorAdapter。

一个数据源,比如当使用CursorLoader时,通常数据源是ContentProvider。

Starting a Loader (启动一个Loader)

在一个activity或fragment中,LoaderManager管理一个或多个Loader实例。但在一个activity或fragment中,只有一个LoaderManager。

通常在activity的onCreate()函数中初始化一个Loader,或者在fragment的onActivityCreate()函数中:

// Prepare the loader.  Either re-connect with an existing one,// or start a new one.getLoaderManager().initLoader(0, null, this);
初始化函数initLoader()有以下几个参数:

1. 一个用来标识Loader的标识符,比如上面例子的ID是0。

2. 可选参数,用与Loader创建,在上面例子为null。

3. 实现LoaderManager.LoaderCallbacks的类,LoaderManager调用这个函数来报告相应的loader事件,这上面例子中,这个activity类实现了LoaderManager.LoaderCallback()接口,所以使用this作为参数。

初始化函数initLoader()保证了一个loader被初始化并被激活,调用这个函数后,通常有几下两个可能的结果:

如果指定ID的Loader存在,最后创建的Loader会被重新使用。

如果制定ID的Loader不存在,那么initLoader()将会触发LoaderManager.LoaderCallbacks的onCreateLoader()函数。在这个函数中你必须实例化一个Loader并返回这个Loader。详见onCreateLoader。

无论那种情况,给定的LoadManager.LoaderCallbacks的实现都和Loader相关,当Loader的状态变化是,里面的相应的函数会被调用。如果请求的Loader已经存在并有自己的数据,那么系统将会马上调用onLoadFInished()函数,所以你必须考虑到这种情况,详见onLoaderFinished()。

注意到函数initLoader()返回了创建的Loader,但是你不必创建一个这个Loader的引用。LoaderManager能够自动的管理Loader的生命过程。LoaderManager在必要的情况下会启动或停止下载,维持Loader的状态和它关联的Content。也就是说你很少会直接和Loader交互(除了使用Loader中的函数来微调Loader的行为,参见LoaderThrottle),大多数情况你都是使用LoaderManager.LoaderCallbacks的函数 来干预下载过程。更多信息参照Using the LoaderManager Callbacks。

Restarting a Loader (重启一个Loader)

正如前面所示,当你用通过initLoader()来创建Loader时,如果制定的Loader ID存在,那么将使用这个已经存在的Loader,如果不存在,再创建一个。当有时候你相想丢弃旧的数据并 从新开始。

通过函数restartLoader()来丢弃旧的数据。比如下面的例子中,通过在函数SearchView.OnQueryTextListener检查用户查找的内容是否变化,如果变了,那么重新启动Loader以便用新的过滤来查找。

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;}

Using the LoaderManager Callbacks (使用LoaderManager)

LoaderManager.LoaderCallbacks是一个回调接口,用户通过它与LoaderManager.Loaders(一般是CursorLoader)交互,这些Loader会在fragment或activity保存数据,即在activity或fragment的onStop()和onStart()间还是会保存数据,使得当用户返回到应用的使用不用等待数据下载。使用LoaderManager.LoaderCallbacks可以直到创建一个新的Loader,并告诉应用什么时候停止使用一个Loader的数据。

LoaderManager.LoaderCallbacks包括下面的函数:

onCreateLoader():实例化一个新的指定ID的LoaderManager并返回这个Loader。

onLoadFinished():当前一个创建的Loader完成下载时被调用

onLoaderReset():当前一个创建的Loader被重置时被调用,使得之前的数据无效。

下面详细描述这些函数的用法:

onCreateLoader

当你试图访问一个Loader时(比如通过initLoader()),这个函数会检查指定ID的Loader是否存在,如果没有,会触发LoaderManager.LoaderCallbacks中的函数onCreateLoader()。在这个函数里会创建一个新的Loader,通常是CursorLoader,但你也可以通过继承Loader来创建自己的Loader。

在下面的例子中,onCreateLoader()创建一个CursorLoader。在CursorLoader的构造函数中,你必须提供完整的信息以便在ContentProvider进行查找。需要下列信息:

uri:从制定的uri获取数据。

projection:一个列表,包含需要返回的列,如果为空,那么将返回所有的列。

selection:过滤器,用来过滤想要返回的行,用SQL WHERE语句(包含WHERE关键字),如果为空,将返回所有的列。

selectionArgs:你可以在selection包含?,这个?将会被selectionArgs取代,顺序跟出现在selection的顺序一样。这个值只能是字符串。

sortOrder:指定如何对返回的行排序,用SQL ORDERBY语句(包括ORDER BY关键字),如果为null,则使用默认的排列顺序,即有可能不排序。

例如:

 // 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");}

onLoaderFinished

当之前创建的loader完成下载时调用这个函数。这个函数必须在释放有这个loader提供的数据之前被调用。在这个函数中,你必须移除所有对所有的旧数据的使用(因为这些数据待会要被释放),但不是移除数据(仅仅移除对旧数据的使用),因为数据放在loader中。

一旦直到应用不再使用这些数据,loader将会移除这些数据,比如,如果数据是在CursorLoader中,那么你不能调用CursorLoader的close()函数,如果此时这个cursor关联到CursorAdapter中,那么你只能调用swapCursor()来却换Cursor而不能直接关闭这个Cursor。

// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...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);}

onLoaderReset

当之前创建的loader被重置时这个函数被调用,使得之前的数据不可用。这个函数保证你能在数据被释放前能够移除所有对这个数据的引用。

通过调用swapCursor()并传入null参数来移除引用:

// This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...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);}


Example (例子)

下面例子是用一个fragment来显示一个listview,这个listview包含从contact content provider查询的数据,其中使用了CursorLoader来对provider进行查询。

对于想访问用户contact的应用,必须在它的manifest文件中申请READ_CONTACTS访问权限。


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

More Example (更多例子)

在ApiDemos中还有更多关于如何使用的例子

LoaderCursor:上面例子的完整版

LoaderThrottle:一个用来说明如何使用throttling来减少当ContentProvider数据变化时的查询次数。




0 0