Android学习笔记系列六 —— Loaders加载器

原创
2016/03/18 16:06
阅读数 55

Loaders

从Android 3.0开始引进了loader(加载器)技术, 在activity或者fragment中,loaders可以把异步地加载数据变得更简单。Loaders具有以下特性:

  • 他们对于每一个ActivityFragment都是有效的。

  • 他们可以提供异步加载数据的能力。

  • 他们监视数据源,并当内容改变时传递当前最新的结果。

  • 当他们因为配置的改变而重新连接的时候,他们会自动地重连到上一个loader的游标。因此,他们不需要重新查询数据。

Loader API汇总



在应用中使用loaders可能会涉及到多个类和接口。他们被汇总到了下表中:

类/接口 描述
LoaderManager 一个关联到Activity或 Fragment的抽象类,用来管理一个或多个Loader实例 这样可以帮助一个应用管理那些跟Activity 或者Fragment的生命周期联系到一起的长时间运行的操作;这样的最常见的使用就是 CursorLoader,然而应用可以自由地写自己的加载器来加载其他类型的数据。 

每一个activity或者fragment都只有一个LoaderManager。但是一个LoaderManager可以拥有多个加载器(loaders)。
LoaderManager.LoaderCallbacks 一个用来去为客户端和LoaderManager提供交互的回调接口。 举个例子,你可以使用onCreateLoader() 回调方法来创建一个新的loader(加载器)。
Loader 一个执行异步的数据加载的抽象类。这是加载器的基类。你可以使用典型的CursorLoader,但是也可以实现你自己的子类。 当loaders被激活的时候,它们应该见识数据源并且当内容改变的时候传递最新结果。
AsyncTaskLoader 提供一个AsyncTask来执行异步加载数据的抽象loader。
CursorLoader AsyncTaskLoader的一个子类,查询 ContentResolver然后返回一个Cursor。这个类用标准的方式实现了 Loader的协议以此来查询cursors, AsyncTaskLoader在后台线程中执行cursor查询所以它不会阻塞应用的UI。 使用loader是从ContentProvider异步加载数据的最好的方式, 相对于通过fragment或activity的API来执行查询

上表中的那些类和接口都是你将会用来在应用中实现loader的极其重要的组件。你不必在创建每一个loader的时候全部使用,但是你总是需要一个 LoaderManager的引用,用来初始化一个loader Loader类的实现,类似于CursorLoader。 下面的章节将会向您展示怎样在应用中使用这些类和接口。

在应用中使用Loader



这一节描述了怎样在Android应用中使用loaders。一个典型地使用了loaders的应用应该包含以下内容:

开始使用Loader

LoaderManager管理一个Activity或 Fragment范围内的一个或多个Loader实例。每一个Activity或者Fragment都只有一个 LoaderManager

你可以典型地初始化一个 Loader,可以在activity的 onCreate()方法,也可以在fragment的onActivityCreated()方法中。你可以像下面一样做这件事:

//准备loader,无论是与一个已经存在的loader重连,//还是新建一个。getLoaderManager().initLoader(0, null, this);

initLoader()方法需要以下参数:

  • 一个可以标识loader的唯一的ID。本例中的ID是0。

  • 一个可选参数,当loader初始化时提供给它(在本例中是null)。

  • 一个LoaderManager.LoaderCallbacks的实现,将会被 LoaderManager调用,用来报告loader的时间。本例中,本地类实现了 LoaderManager.LoaderCallbacks借口,所以它传递了一个自身的引用 this

initLoader()方法调用确保了一个loader会被初始化以及激活 它有两种可能的后果:

无论在哪一种情况中,传入的LoaderManager.LoaderCallbacks 实现都会跟loader绑定在一起,它将会在loader状态改变时被调用。如果在本次调用时,调用者处于开始状态,并且所请求的loader已经存在并产生了数据,那么系统就会立马调用 onLoadFinished() (即在initLoader()过程中), 所以你必须为这种情况做好准备。更多关于这个回调方法的讨论,请参看 onLoadFinished

请注意,initLoader() 方法返回被创建的Loader,但是你不必保留它的引用, LoaderManager会自动管理loader的生命, LoaderManager 会在必要的时候启动和终止,以及维护loader的状态和它关联的内容。这就意味着,你几乎不用和loader进行直接交互 (寻求使用loader方法来调整loader行为的例子,请参看 LoaderThrottle实例)。 你最常用的手段是当特定事件发生时,使用LoaderManager.LoaderCallbacks方法来介入到加载过程中。 更多关于本话题的讨论,请参看Using the LoaderManager Callbacks

重启一个Loader

当你像上面展示的那样使用initLoader()的时候, 如果有的话,它会使用已存在的带有标识ID的loader。 如果没有,它会创建一个。但是有时你会想要丢掉旧的数据,开始新的过程。

为了丢弃旧的数据,你要使用restartLoader()。 例如,SearchView.OnQueryTextListener的实现在用户查询改变时重启了, loader需要被重启从而能够使用修改过的搜索过滤进行新的查询:

public boolean onQueryTextChanged(String newText) {
    //当action bar的搜索文本改变时调用。
    //更新搜索过滤器,然后重启loader用当前的过滤器
    //做一次新的查询。
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;}

使用LoaderManager回调

LoaderManager.LoaderCallbacks是一个回调接口,它为客户端提供与 is a callback interfaceLoaderManager交互的能力。

Loaders,尤其是CursorLoader,大家都希望当它被停止以后仍然可以保持数据。 这样允许应用在activity或fragment的 onStop()onStart()方法之间保持数据, 以至于当用户返回到一个应用的时候,他们不必再等待数据的重新加载。 你可以使用LoaderManager.LoaderCallbacks方法 当你知道合何时需要创建新的loader,以及高速应用何时停止使用loader的数据。

LoaderManager.LoaderCallbacks包括以下方法: 

  • onCreateLoader() — 根据传入的ID初始化并返回一个新的Loader

    在下面的章节中会更详细地描述这些方法的细节

    onCreateLoader

    当你试图操作一个loader的时候,(例如通过initLoader()), 会检查被赋予唯一ID的loader是否存在。如果不存在,它会触发LoaderManager.LoaderCallbacksonCreateLoader()方法。 这是你创建新loader的地方。一般来说被创建的都是CursorLoader,但是你可以实现你自己的Loader子类。 

    在本例中,onCreateLoader() 回调方法创建了一个CursorLoader。你必须使用它的构造方法建造这个CursorLoader,构造方法需要向 which ContentProvider执行一次查询的完整信息作为参数。它还需要:尤其地,它还需要:

    例如:

     //如果不是null,这就是当前的用户提供的过滤器。String mCurFilter;...public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        //当新的Loader需要被创建的时候调用此方法。 
        //本例仅有一个Loader,所以不必关心ID的问题。
        //首先,根据我们是否正在过滤,
        //选择base URI来使用。
        Uri baseUri;    if (mCurFilter != null) {        baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,                  Uri.encode(mCurFilter));    } else {        baseUri = Contacts.CONTENT_URI;    }    //现在创建并返回一个CursorLoader,     //它会创建一个用来显示数据的Cursor。    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");}

    onLoadFinished

    当上一个被创建的loader已经结束数据加载的时候调用此方法。这个方法被保证会在提供给这个loader的数据被释放之前调用。 这个时候,你应该移除所有旧数据的使用(因为它们马上就会被释放),但是不应该自己去释放它们,因为它们的loader会做这些事。

    一旦知道了应用将不会再使用这些数据,loader就应该立即释放它们。 例如,数据是来自CursorLoader的一个cursor, 你就不应该再调用close()。如果这个cursor正要在 CursorAdapter中被替换,你应该使用swapCursor()方法使得旧的 Cursor不被关闭。例如:

    // This is the Adapter being used to display the list's data.
    SimpleCursorAdapter mAdapter;...public void onLoadFinished(Loader<Cursor> loader, Cursor data) {    //把新cursor换进来  (一旦我们返回了,框架将会管理    //关闭旧cursor的事情)    mAdapter.swapCursor(data);}

    onLoaderReset

    当之前创建的loader被重置使得数据不可用的时候,此方法被调用。这个回调方法让你弄清楚数据何时会被释放,进而你可以移除对它的引用 

    下面的实现调用了参数为null的 swapCursor() :

    // This is the Adapter being used to display the list's data.SimpleCursorAdapter mAdapter;...public void onLoaderReset(Loader<Cursor> loader) {
        //当最后一个Cursor进入到onLoadFinished()时被调用,
        //Cursor将要被关闭, 我们要确保
        //不会再使用到它。
        mAdapter.swapCursor(null);}

    Example



    下面的例子完整实现了一个Fragment显示一个包含了从联系人content provider返回的查询数据的ListView的内容的功能。 它使用一个CursorLoader来管理对provider的查询。

    一个应用想要实现操作用户的联系人,如例子中那样,它的manifest一定要包含 READ_CONTACTS权限。

    public static class CursorLoaderListFragment extends ListFragment        implements OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
    
        //这就是用来展示列表信息的Adapter。    SimpleCursorAdapter mAdapter;    //如果不是null,这就是当前的搜索过滤器。    String mCurFilter;    @Override public void onActivityCreated(Bundle savedInstanceState) {        super.onActivityCreated(savedInstanceState);        //如果没有数据,就给控件一些文本去显示。          //在真正的应用中,信息来自应用资源。        setEmptyText("No phone numbers");        //我们在action bar中显示一个菜单项。        setHasOptionsMenu(true);        //创建一个新的adapter,我们将用它来显示加载的数据。        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);        //准备loader, 重连到一个已存在的loader,        //或者启动一个新的loader。        getLoaderManager().initLoader(0, null, this);    }    @Override public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {        //放置一个action bar用于搜索。        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) {        //action bar上的搜索文本改变的时候被调用。         //更新搜索过滤器,并且重启loader用当前的过滤器        //来做新的查询。        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;        getLoaderManager().restartLoader(0, null, this);        return true;    }    @Override public boolean onQueryTextSubmit(String query) {        //不必关心这个方法。        return true;    }    @Override public void onListItemClick(ListView l, View v, int position, long id) {        // Insert desired behavior here.        Log.i("FragmentComplexList", "Item clicked: " + id);    }    //我们想获取的联系人中的行数据。    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) {        //当需要创建一个新的loader时被调用。         //本例中仅有一个loader,所以我们不必关心ID的问题。        //首先,根据我们当前是否正在过滤,        //选择base URI来使用。        Uri baseUri;        if (mCurFilter != null) {            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,                    Uri.encode(mCurFilter));        } else {            baseUri = Contacts.CONTENT_URI;        }        //现在创建并返回一个CursorLoader,        //它将会为被显示的数据创建一个Cursor。        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) {        //把新的cursor换进来。  (框架将会在我们返回的时候        //管理旧cursor的关闭事宜。)        mAdapter.swapCursor(data);    }    public void onLoaderReset(Loader<Cursor> loader) {        //当最后一个Cursor进入onLoadFinished()的时候被调用。        //cursor将要被关闭, 我们应该确保        //不再使用它。        mAdapter.swapCursor(null);    }}

    更多实例

    ApiDemos中有一些不同的例子,它们说明了怎样使用loader:

    • LoaderCursor — 上面展示的片段的汇总的完整版本。

    • LoaderThrottle — 一个例子,用来展示当数据改变时,怎样使用throtting来减少对content provider做查询的次数

    • uri — 要获取的内容的URI。

    • projection — 返回的列组成的列表,传入null将会返回所有列,但是效率很低。

    • selection — 一个声明返回哪些行的过滤器,被格式化成类似SQL中WHERE子句的形式(除了没有WHERE自己)。传入null将会返回给定URI的所有行。

    • selectionArgs — 你可能在Selection中包含一些‘?’,他们将会被selectionArgs的值给替换掉,顺序与它们在selection中出现的顺序一致。 这些值被约束为String类型

    • sortOrder — 怎样给这些行排顺序,被格式化为类似SQL中ORDER BY子句的形式(除了没有ORDER自己)。传入null 将会使用默认的排序方式,可能是没有顺序。

    • onLoaderReset() — 当一个之前创建的loader被重置的时候会调用此方法,这样会导致它的数据不可用。

    • onLoadFinished() — 当一个之前被创建的loader已经结束加载数据的时候会调用此方法。

参考资料:http://www.android-doc.com/guide/components/loaders.html

展开阅读全文
打赏
1
2 收藏
分享
加载中
更多评论
打赏
0 评论
2 收藏
1
分享
返回顶部
顶部