Android loader 详解

装载器从android3.0开始引进。它使得在activity或fragment中异步加载数据变得简单。装载器具备以下特性:html

  • 它们对每一个Activity和Fragment都有效。android

  • 他们提供了异步加载数据的能力。app

  • 它们监视数据源的一将一动并在内容改变时传送新的结果。异步

  • 当因为配置改变而被从新建立后,它们自动重连到上一个加载器的游标,因此没必要从新查询数据。ide

装载器API概述

在使用装载器时,会涉及不少类和接口们,咱们在下表中对它们总结一下: this

Class/Interfacespa

说明线程

LoaderManagerrest

一个抽像类,关联到一个Activity或Fragment,管理一个或多个装载器的实例。这帮助一个应用管理那些与Activity或Fragment的生命周期相关的长时间运行的的操做。最多见的方式是与一个CursorLoader一块儿使用,然而应用是能够随便写它们本身的装载器以加载其它类型的数据。
code

每一个activity或fragment只有一个LoaderManager。可是一个LoaderManager能够拥有多个装载器。

LoaderManager.LoaderCallbacks

一个用于客户端与LoaderManager交互的回调接口。例如,你使用回调方法onCreateLoader()来建立一个新的装载器。

Loader(装载器)

一个执行异步数据加载的抽象类。它是加载器的基类。你可使用典型的CursorLoader,可是你也能够实现你本身的子类。一旦装载器被激活,它们将监视它们的数据源而且在数据改变时发送新的结果。

AsyncTaskLoader

提供一个AsyncTask来执行异步加载工做的抽象类。

CursorLoader

AsyncTaskLoader的子类,它查询ContentResolver而后返回一个Cursor。这个类为查询cursor以标准的方式实现了装载器的协议,它的游标查询是经过AsyncTaskLoader在后台线程中执行,从而不会阻塞界面。使用这个装载器是从一个ContentProvider异步加载数据的最好方式。相比之下,经过fragment或activity的API来执行一个被管理的查询就不行了。

上面所列的类和接口们是你在你的应用中要实现装载器时的核心组件。你的每一个装载器并不必定须要全部的组件,可是你老是须要引用LoaderManager来初始化一个装载器。后面的章节将向你展现如何使用这些类和接口们。

 

一个使用装载器的应用会典型的包含以下组件:

  • 一个Activity或Fragment

  • 一个LoaderManager的实例.

  • 一个加载被ContentProvider所支持的数据的CursorLoader.或者,你能够从Loader或AsyncTaskLoader实现你本身的装载器来从其它源加载数据.

  • 一个LoaderManager.LoaderCallbacks的实现.这是你建立新的装载器以及管理你的已有装载器的引用的地方.

  • 一个显示装载器的数据的途径,例如使用一个SimpleCursorAdapter

  • 一个数据源,好比当是用CursorLoader时,它将是一个ContentProvider.

启动一个装载器

LoaderManager管理一个Activiry或Fragment中的一个或多个装载器.但每一个activity或fragment只拥有一个LoaderManager

你一般要在activity的onCreate()方法中或fragment的onActivityCreated()方法中初始化一个装载器.你能够以下建立:

// 准备装载器.能够重连一个已经存在的也能够启动一个新的.
getLoaderManager().initLoader(0,null, this);

initLoader()方法有如下参数:

  • 一个惟一ID来标志装载器.在这个例子中,ID是0

  • 可选的参数,用于装载器初始化时(本例中是null)

  • 一个LoaderManager.LoaderCallbacks的实现.被LoaderManager调用以报告装载器的事件,在这个例子中,类本实现了这个接口,因此传的是它本身:this

initLoader()保证一个装载器被初始化并激活.它具备两种可能的结果:

  • 若是ID所指的装载器已经存在,那么这个装载器将被重用.

  • 若是装载器不存在,initLoader()就触发LoaderManager.LoaderCallbacks的方法onCreateLoader().这是你实例化并返回一个新装载器的地方. 

  在这两种状况中,传入的LoaderManager.LoaderCallbacks的实现都与装载器绑定在一块儿.而且会在装载器状态变化时被调用.若是在调用这个方法时,调用者正处于启动状态,而且所请求的装载器已存在并产生了数据,那么系统会立刻调用onLoadFinished()(也就是说在initLoader()还在执行时).因此你必须为这种状况的发生作好准备.


  注意initLoader()返回所建立的装载器,可是你不需保存一个对它的引用.LoaderManager自动管理装载器的生命.LoaderManager会在须要时开始和中止装载动做,而且维护装载器的状态和它所关联的内容.这意味着,你不多与装载器直接交互.你一般都是使用LoaderManager.LoaderCallbacks的方法们在某个事件发生时介入到数据加载的过程当中.

重启装载器

当你使用initLoader()时,若是指定ID的装载器已经存在,则它使用这个装载器.若是不存在呢,它将建立一个新的.可是有时你倒是想丢弃旧的而后开始新的数据.

要想丢弃旧数据,你应使用restartLoader().例如,下面这个SearchView.OnQueryTextListener的实如今用户查询发生改变时重启了装载器,装载器因而需重启从而能使用新的搜索过虑来进行一次新的查询.

public boolean onQueryTextChanged(String newText) { // 当动做栏的搜索字串发生改时被调用. // 更新搜索过虑,而后从新启动装载利用这个新过虑进行新的查询. 
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null; getLoaderManager().restartLoader(0, null, this); return true; }

使用LoaderManager的回调

LoaderManager.LoaderCallbacks是一个回调接口,它使得客户端能够与LoaderManager进行交互.

装载器,通常指的是CursorLoader,咱们但愿在它中止后依然保持数据.这使得应用能够在activity或fragment的 onStop() onStart() 之间保持数据,因此当用户回到一个应用时,它们不需等待数据加载.你使用LoaderManager.LoaderCallbacks 的方法们,在须要时建立新的装载器,而且告诉应用何时要中止使用装载器的数据.

LoaderManager.LoaderCallbacks 包含如下方法们:

  • onCreateLoader() —跟据传入的ID,初始化并返回一个新的装载器.

  • onLoadFinished() —当一个装载器完成了它的装载过程后被调用.

  • onLoaderReset() —当一个装载器被重置而什其数据无效时被调用.

onCreateLoader

当你试图去操做一个装载器时(好比,经过initLoader()),会检查是否指定ID的装载器已经存在.若是它不存在,将会触发LoaderManager.LoaderCallbacks 的方法onCreateLoader().这是你建立一个新装载器的地方.一般这个装载器是一个CursorLoader,可是你也能够实现你本身的装载器.

在下面的例子中,回调方法onCreateLoader() 建立一个CursorLoader.你必须使用构造方法来创建CursorLoader ,构造方法须要向ContentProvider执行一次查询的完整信息做为参数,它尤为须要:

  • uri —要获取的内容的URI

  • projection —要返回的列组成的列被.传入null 将会返回全部的列,但这是低效的.

  • selection —一个过滤器,代表哪些行将被返回.格式化成相似SQLWHERE 语句的样子(除了没有WHERE).传入null 将返回全部的行.

  • selectionArgs —你能够在selection 中包含一些'?',它将被本参数的值替换掉.这些值出现的顺序与'?'在selection中出现的顺序一至.值将做为字符串.

  • sortOrder —如何为行们排序.格式化成相似于SQLORDER BY 语句的样字(除了没有ORDERBY).传入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

  这个方法是在前面已建立的装载器已经完成其加载过程后被调用.这个方法保证会在应用到装载器上的数据被释放以前被调用.在此方法中,你必须删除全部对旧数据的使用(由于它将很快会被删除),可是不要本身去释放它们,由于它们的装载器会作这些事情.

  装载器一旦了解到应用再也不使用数据时,将立刻释放这些数据.例如,若是数据是一个从CursorLoader来的游标,你不该调用游标的close().若是游标被放置在一个CursorAdapter中,你应使用swapCursor()方法,以使旧的游标不被关闭.例如:

// 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

  当一个已建立的装载器被重置从而使其数据无效时,此方法被调用.此回调使你能发现何时数据将被釋放因而你能够釋放对它的引用.

下面这个实现调用参数为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显示一个包含从联系人contentprovider 返回查询数据的ListView的内容的功能.它使用一个CursorLoader来管理对provider的查询.

  为了能从用户的联系人中取得数据,本例的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); } }

***

相关文章
相关标签/搜索