Loader(加载器)

来源:互联网 发布:阿里云服务器怎么开通 编辑:程序博客网 时间:2024/05/22 04:56

Loader(加载器)

      Android3.0引入了Loader,它使在activity或者fragment中异步加载数据变得简单。Loader有以下特点:

  • 每个Activity和Fragment都能够使用。
  • 异步加载数据。
  • 监控数据源头的变化并且当数据源头的内容被改变时会获取新数据。
  • 当配置文件被改变后能够自动重新连接新的loader并从新的loadercursor(数据位置游标)获取数据。因此,不需要重新去查询数据。

       LoaderAPI 概要

       这里有许多类和接口在应用程序使用Loader时被调用。如下表:

      

                                                     

Class(类)/Interface(接口)                                                                                                                    

描述LoaderManager

        一个与Activity 或 Fragment相关联的抽象类,能管理一个或多个 Loader实例。这能够帮助应用程序管理在Activity 或 Fragment的生命周期内较长时间运行的Loader实例;最常使用的CursorLoader,能够使应用程序自由改写自己的Loader实例来加载各种类型的数据。

每个activity或fragment仅仅只有一个 LoaderManager。但是一个 LoaderManager拥有多个Loader实例。 LoaderManager.LoaderCallbacks                                        一个供客户端与 LoaderManager 交互的返回接口。例如:你能用该接口中的onCreateLoader()方法来创建一个新的Loader实例。Loader        一个能异步加载数据的抽象类。这是loader的基类。你可能经常使用 CursorLoader(继承Loader基类),但是也可以实现Loader的自定义子类。只要Loader实例们存在,它们就会监控数据源头的变化并且当数据源头的内容被改变时会获取新数据。AsyncTaskLoader提供一个 AsysncTask (异步任务)来处理工作的抽象类。CursorLoader         AsyncTaskLoader 的子类,能够查询 ContentResolver 并且返回一个 Cursor(数据位置游标)。这个类(实现 Loader协议)使用一个标准方法来查询cursor(数据位置游标),通常被AsyncTaskLoader创建在后台线程,用来查询cursor(数据位置游标)以至于不阻塞应用程序UI主线程。使用loader从 ContentProvider中异步加载数据是获取数据的一个较好方式,代替在fragment或activity API代码中执行数据查询。

       表格上是一些Loader比较重要的类和接口,你将使用它们在你的应用程序上来实现一个Loader实例。虽然你创建每个Loader实例不需要把这些类和接口都全部用上,但是你总是需要使用到 LoaderManager来初始化一个Loader实例和实现一个 Loader类(例如 CursorLoader)。接下来的章节将展示在应用程序上如何使用这些类和接口。

      在应用程序上使用Loader(加载器)

        这个章节描述怎么在android应用程序上使用loaders(加载器)。在应用程序上会经常使用到loaders(加载器)是以下这些:     

  •  Activity 或者 Fragment
  • LoaderManager的实例。
  • CursorLoader 加载 ContentProvider返回的数据。或者实现 LoaderAsyncTaskLoader的自定义子类来加载数据(来自许多其它数据源)。
  • 实现 LoaderManager.LoaderCallbacks 接口的类。在这个类里你能够创建新的Loader实例且能够管理一些与该类有关且已经存在的Loader实例。
  • 一个显示加载器数据的方式,例如: SimpleCursorAdapter
  •  一个数据源,例如:使用 CursorLoader 时用到的ContentProvider

       启动一个Loader(加载器)

       LoaderManager 管理ActivityFragment的一个或多个 Loader 实例。每个activity或fragment仅仅只有一个 LoaderManager

通常会使用activity的 onCreate( ) 方法或者是fragment的 onActivityCreated( )方法来初始化一个 Loader实例。根据下面的写法:

// Prepare the loader.  Either re-connect with an existing one,// or start a new one.getLoaderManager().initLoader(0, null, this);

        initLoader() 方法带有以下参数:

        第一个参数:区分Loader实例的唯一标识符ID。在例子上,ID的值为0。

        第二个参数:这是一个可填可不填且传递给Loader构造方法的参数(例子上为null)。

        第三个参数:实现 LoaderManager.LoaderCallbacks 接口的类实例(通过 LoaderManager 派发loader event事件来响应)。在例子上的类是实现了                           LoaderManager.LoaderCallbacks 接口,所以传递了类本身 this

        执行 initLoader() 方法来使Loader实例被初始化和存活。会出现两种可能的结果:

  • 如果ID标识的Loader实例已经存在,则上次创建的Loader实例(该ID标识的)将被重新使用。
  • 如果ID标识的Loader实例不存在,则 initLoader() 会触发 LoaderManager.LoaderCallbacks 接口中的onCreateLoader() 方法。在这个方法里你可以编写代码来实例化并且返回一个新的Loader实例。更多的讨论,请看 onCreateLoader 章节。

在这两种情况下,给出的 LoaderManager.LoaderCallbacks 实现类与该Loader实例有关联,并且当该Loader实例状态改变时将被调用。如果在调用时Loader实例是已启动状态,请求的Loader实例已经存在并且已经产生了数据,这时系统会立即调用 onLoadFinished() 方法(在 initLoader() 执行过程中),所以你必须对此情况有所准备。

       注意:initLoader() 方法返回一个已被创建的 Loader 实例,而且你不需知道关于它是怎么实现。
LoaderManager 自动地管理Loader实例的生命。当 LoaderManager 有必要启动和停止加载数据时,它能够维持Loader实例的状态和相关的内容信息。这意味着,你基本不用深入去接触Loader实例(尽管有一个关于使用Loader实例的方法去微调Loader实例的行为的 LoaderThrottle 例子)。当相关的事件发生,在加载数据过程中你通常会使用 LoaderManager.LoaderCallbacks 实现类中的方法来控制。关于这个的更多讨论,请看使用 LoaderManager 的 Callbacks 方法。
     重新启动Loader实例

     当你使用 initLoader() 方法时会出现一些情况,如果Loader实例已经存在,则使用该带有指定ID的Loader实例。如果Loader实例不存在,则重新创建。但是大部分情况下你都想丢弃旧数据并且更换新数据。

     可以使用 restartLoader() 方法来丢弃旧数据。例如,但用户查询改变时,SearchView.OnQueryTextListener 的实现类将重新启动Loader实例。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;}

      使用LoaderManager的Callbacks方法

        LoaderManager.LoaderCallbacks 是一个返回接口,能够使客户端与LoaderManager 交互。

        Loader中,特别是 CursorLoader,期望在停止运行后能够保持数据。这使得应用程序在activity或fragment执行了 onStop() onStart() 方法后能够保持数据,以至于当用户返回应用程序时,不需要等待数据重新加载。当知道要创建新的Loader实例和通知应用程序停止使用Loader实例加载的数据时,你会使用 LoaderManager.LoaderCallbacks 的方法。

        LoaderManager.LoaderCallbacks 包括以下方法:

  • onCreateLoader() :通过提供的ID,实例化并返回一个新的Loader实例。
  • onLoadFinished() :当一个早期创建的Loader实例完成数据加载时将调用此方法。
  • onLoaderReset() :当一个早期创建的Loader实例被重置时将调用此方法,因而会使它的数据不可用。

       这些方法在下面章节中有详细描述。

     onCreateLoader

       当你尝试去访问一个Loader实例(例如:通过 initLoader() 方法)时,它将检查是否Loader实例(ID指定的)已经存在。如果不存在,则将触发 LoaderManager.LoaderCallbacks 中的onCreateLoader() 方法。在该方法里你能创建一个新的Loader实例。该实例通常会是 CursorLoader 实例,但是也可以是实现自定义Loader子类的实例。

      在这个例子上,onCreateLoader() 方法将创建一个 CursorLoader实例。你必须用CursorLoader 的构造方法来生成实例,这个构造方法需要传递一些信息参数以至于能够查询 ContentProvider 。它需要的参数是:

  • uri :内容检索的URI。
  • projection :要返回的列元素。如果为 null 将返回所有的列元素,效率会降低。
  • selection :声明返回行的过滤器,格式如一个SQL WHERE语句(不包括WHERE本身)。
  • selectionArgs :在selection中包含一些?,将被selectionArgs中值所代替。这些值将被绑定为字符串。
  • sortOrder :如何对行元素进行排序,格式如一个SQL ORDER BY语句(不包括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");}

      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) {    // Swap the new cursor in.  (The framework will take care of closing the    // old cursor once we return.)    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) {    // 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);}

      例子

作为一个例子,这里将是一整个 Fragment 的实现代码,显示了一个 ListView(包含通讯录查询结果数据)。ListView使用 CursorLoader 实例来对提供器的查询进行管理。

例子中关于访问用户通讯录的一个应用程序代码,它的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);    }}

        更多例子

这里有许多不同的例子在ApiDemos里,这些例子阐明了如何使用Loader:

1、  LoaderCursor  : 上面显示的片段是一个LoaderCursor使用的完整版本。

2、  LoaderThrottle :是一个当内容提供器的数据改变时如何使用throttling来减少对内容提供器的查询次数的例子。

想了解更多信息,可以通过下载和安装SDKsamples,看SDK提供的例子。


原文网址:http://developer.android.com/guide/components/loaders.html