Loaders-Android 6.0开发者文档

原文地址:Loaders

一、说明

loader从Android 3.0开始引入。loader的作用是简化fragment或activity中异步加载数据的过程。loader有以下特征:

  • loader在每个activity与fragment都可用
  • loader提供数据的异步加载功能
  • loader会监听源数据,在内容变化时发送新数据
  • 当配置改变后重启时,loader会自动重连到最后loader的cursor,因此loader不需要重新查询数据

二、loader API简介

Class/Interface描述
LoaderManager抽象类,Activity或Fragment可以用它管理一个或多个loader。它可以让应用结合activity或fragment的生命周期管理长连接操作,最常见的用法是使用CursorLoader。应用也可以编写自己的loader来加载其他类型的数据。
每个activity或fragment只能有一个LoaderManager,一个LoaderManager可以有多个loader
LoaderManager.LoaderCallbacks使用方与LoaderManger交互的回调接口。例如,你可以使用onCreateLoader()回调创建一个新的loader
Loader抽象类,可以执行异步数据加载。它是loader的基类。你可以使用CurrorLoader,也可以自己继承Loader类编写loader。自定义的loader开始工作时,应该监控数据源变化,并在content发生变化时分发新的结果
AsyncTaskLoader提供AsyncTask的抽象loader
CursorLoaderAsyncTaskLoader的子类,会查询ContentResolver并返回Cursor。此类是查询cursor的标准实现,它会构建一个AsyncTaskLoader,其会在后台线程查询cursor,不会阻塞UI线程。使用此loader是从ContentProvider异步加载数据的最好方式,请尽量避免直接用activity或fragment的API进行查询

以上的类和接口是你实现一个loader必要的成分。当你创建loader时,你不需要以上的所有内容,但是LoaderManager(其会初始化loader)和Loader实例(继承了Loader类的loader,比如CursorLoader)是必须的。下面的部分描述了怎样使用这些类和接口。

三、在应用中使用loader

应用使用loader一般包括以下步骤:

  • 有activity或fragment
  • 创建LoaderManager实例
  • 创建CursorLoader加载ContentProvider的数据。你也可以实现自己的loader或AsyncTaskLoader去加载其他种类的数据
  • 实现LoaderManager.LoaderCallbacks。从这个回调中,你可以创建新的loader,或者管理已经存在的loader
  • 用某种方式显示loader的数据,如SimpleCursorAdapter
  • 指定数据源,如ContentProvider

3.1 启动loader

LoaderManager可以在activity或fragment中管理一个或多个loader。每个fragment或activity只能有一个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。本例中为0
  • 添加到loader的可选参数。本例中为null
  • LoaderManager.LoaderCallbacks实例。LoaderManager会通过此回调报告loader的事件。在本例中,此类实现了LoaderManager.LoaderCallbacks接口,所以将它自己传入initLoader中

initLoader()方法会实例化loader并激活它。此方法逻辑如下:

  • 如果为loader指定的ID已存在,那么会直接重用存在的loader
  • 如果为loader指定的ID不存在,那么initLoader()方法会调用LoaderManager.LoaderCallbacks的onCreateLoader()方法,在这个方法里,你可以实例化并返回一个新的loader

当loader状态改变时,LoaderManager.LoaderCallbacks会收到回调。如果调用initLoader方法时调用方在started状态,而且指定的loader已经存在且生成了它的数据,那么系统会立即调用onLoadFinished()方法(在initLoader()期间),你必须处理这种可能性。

initLoader()方法会返回一个它创建的Loader,但是你不必持有此loader的引用。LoaderManager会自动管理loader的生命周期。LoaderManager会在必要时启动或者停止加载、维持loader状态、维持它连接的内容。从这个角度讲,你很少会直接与loader进行交互。你更多的是通过LoaderManager.LoaderCallbacks去干涉加载过程。

3.2 重启loader

当你使用initLoader()方法时,如果指定ID的loader已经存在,那么会直接重用。如果指定ID的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;
}

3.3 使用LoaderManager回调

使用方可以通过LoaderManager.LoaderCallbacks接口与LoaderManager交互。

loader,如CursorLoader,可以在被停止时保存数据,这样的话,应用就可以在activity或fragment在onStop()并onStart()后保持数据,当用户返回到应用时,就不必等待数据重新加载了。使用LoaderManager.LoaderCallbacks可以知道什么时候创建了新的loader,并通知应用什么时候停止使用loader的数据。

LoaderManager.LoaderCallbacks包括以下方法:

  • onCreateLoader()。根据给定ID实例化并返回一个新的loader
  • onLoadFinished()。当之前创建的loader结束加载时调用
  • onLoaderReset()。当之前创建的loader被重置时调用,在这个方法中,你应该使你的数据不可见

这些方法的详细信息如下:

3.3.1 onCreateLoader

当你试图访问一个loader时(如使用initLoader()方法),会检查指定ID的loader是否存在。如果不存在,会触发LoaderManager.LoaderCallbacks的onCreateLoader()方法。在这里,你应该创建一个新的loader。

在下面的例子中,onCreaeteLoader()会创建一个CursorLoader,CursorLoader的构造函数的参数包括:

  • uri。搜索的URI
  • projection。表示返回哪些列。传入null会返回所有列,可能会影响效率
  • selection。表示返回哪些行。传入null将返回指定URI的所有行
  • selectionArgs。占位符的数据
  • sortOrder。表示返回结果的排序方式。传入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");
}

3.3.2 onLoadFinished

当之前创建的loader结束加载时调用此方法。这个方法是在loader提供的最后数据释放前调用的。在这个方法中,你应该移除对旧数据的使用,但是不要关闭数据源,loader会自己处理它。

loader会在知道应用不再使用它后释放这些数据。例如,如果数据是CursorLoader的光标,你不应该调用它的cloase()方法,如果CursorAdapter的cursor被更换了,你应该使用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);
}

3.3.3 onLoaderReset

当之前创建的loader被重置时调用此方法,会使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);
}

四、示例

下面是Fragment用listview显示content provider查询结果的示例。它使用了CursorLoader管理对content 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);
    }
}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值