Android Loader

        本文出自:http://developer.android.com/guide/components/loaders.html#summary

      

        Loaders

        Android 3.0引入了Loader用于异步加载数据,Loaders使得在Activity和Fragment里异步加载数据变得更加容易。Loader有如下一些特性:

        1.they are available to every activity and fragment.//activity和fragment里都能使用

        2.they provide asynchronous loading of data.//提供数据的异步加载

        3.they monitor the source of their data and deliver new results when the content changes.//自动监控源数据改变并通知数据、更行数据结果

        4.They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data. //当配置改变时自动重连到最近使用的loader游标,这样不需要重复查询数据

        Loader API Summary

        在app里有多个相关与loader使用相关的类和接口,其摘要如下表:

Class/Interface

Description

LoaderManager

An abstract class associated with an Activity or Fragment for managing one or more Loader instances. This helps an application manage longer-running operations in conjunction with theActivity or Fragment lifecycle; the most common use of this is with a CursorLoader, however applications are free to write their own loaders for loading other types of data. 

There is only one LoaderManager per activity or fragment. But aLoaderManager can have multiple loaders.

LoaderManager.LoaderCallbacks

A callback interface for a client to interact with the LoaderManager. For example, you use the onCreateLoader() callback method to create a new loader.

Loader

An abstract class that performs asynchronous loading of data. This is the base class for a loader. You would typically useCursorLoader, but you can implement your own subclass. While loaders are active they should monitor the source of their data and deliver new results when the contents change.

AsyncTaskLoader

Abstract loader that provides an AsyncTask to do the work.

CursorLoader

A subclass of AsyncTaskLoader that queries theContentResolver and returns a Cursor. This class implements the Loader protocol in a standard way for querying cursors, building on AsyncTaskLoader to perform the cursor query on a background thread so that it does not block the application's UI. Using this loader is the best way to asynchronously load data from aContentProvider, instead of performing a managed query through the fragment or activity's APIs.

        上面的类和接口是实现loader必须要的组件,但你不需要实现上面的所有类和接口。但为了能初始化一个Loader,你必须要持有一个LoaderManager的引用。你也必须提供Loader的实现类,例如CursorLoader。下面的部分将告诉你如何使用这些类和接口在你的app里实现loader。

        Using Loaders in an application

        该部分描述了如何在Android app里使用Loader。一个典型的使用loader的app包括:

        1.一个activity或者Fragment

        2.一个LoaderManager的实例

        3.系统默认提供的Loader的实现类CursorLoader——通过ContentProvider进行数据加载。当然地,你也可以实现自己的loader或者AsyncTaskLoader来从其他的数据源加载数据

        4.展示loader数据的方式,例如像SimpleCursorAdapter

        5.数据源。例如当使用CursorLoader的数据源ContentProvider

        Starting a Loader

         LoaderManager管理一个activity或者Fragment里的一个或者多个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方法接收如下几个参数:

        1.Loader的唯一ID,上面的例子里ID是0

        2.可选参数,当Loader构造时提供给loader用于loader的实例化(上面的例子里是null)

        3.LoaderManager.LoaderCallbacks实现类。LoaderManager调用该回调类上报Loader事件。上面的例子里,该类本身实现了该接口,因此传递它自身的引用:this。

        initLoader()的调用确保了loader被初始化和是active的,有如下两个结果

        1.如果指定一个已经存在的Loader Id,将复用上次产生的Loader。

        2.如果指定的Loader ID不存在,initloader()方法将触发LoaderManager.LoaderCallbacks的onCreateLoader()回调方法。在该方法里,你实现和返回新的Loader。关于这更多的讨论,请参见onCreateLoader部分。

   

         不管上面两种情况中的哪种情况,LoaderManager.LoaderCallbacks的实现都和Loader相关,其在Loader状态改变时都将被调用。如果当loader已经是已开始状态,并且Loader已经存在,也产生了它的数据,那么系统立即的调用onLoadFinished()(在initLoader()期间),因此,你必须为这的发生做好准备。参见onLoaderFinished查看更多的回调方法讨论。

        注意initLoader()方法会返回创建的Loader对象,但我们不需要持有该loader的引用。LoaderManager自动管理Loader的生命周期。LoaderManager在必要时开始和停止加载,维护loader的状态和与其相关的内容和数据。像这表明的,我们很少直接和loader打交道(though for an example of using loader methods to fine-tune a loader's behavior, see the LoaderThrottle sample),最常见的情况是当特定的事件发生时你使用LoaderManager.LoaderCallbacks回调干涉loader的加载过程。该主题的更多讨论,参见 Using the LoaderManager Callbacks.

        Restarting a Loader

        如上所示,当使用initLoader()的时候,如果指定的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;
}

         

        Using the LoaderManger Callbacks

        LoaderManager.LoaderCallback是一个回调接口,用于客户端和LoaderManager进行交互。

        Loaders,特别地例如CursorLoader,是被期望在已经停止后保留它们的数据。这使得App能在Activity或者Fragment的onStop()或者onStart()方法期间保持它们的数据。这样,当用户重新返回到application时,他们不必等数据重新加载。你能使用LoaderManager.LoaderCallback回调方法知道什么时候产生一个新的Loader,然后告诉application什么时候是停止使用Loader的数据的时机。

        LoaderManager.LoaderCallback包括如下回掉方法:

        1.onCreateLoader() — 产生对应ID的新的Loader,并返回该Loader

        2.onLoadFinished() — 当先前已产生的Loader完成它的数据加载的时候调用

        3.onLoaderReset() — 当先前已产生的Loader正被重置时调用,Loader重置后它的数据将变得无效。

        下面将详细描述这些方法

        onCreateLoader

        当你企图访问Loader(例如,通过initLoader()),它会核查对应指定的ID的Loader是否存在,如果不存在,将触发LoaderManager.LoaderCallback的onCreateLoader方法,在这儿你可以产生一个新的Loader。例如CursorLoader,它是系统提供的一个实现了Loader的类,当然,你也可以实现自己的loader类。

        例如,在onCreateLoader回调方法里你产生了一个CursorLoader,而产生该Loader需要调用它的构造方法,其构造方法需要如下一些参数,这些一系列参数用于执行对ContentProvider的查询:

        1.uri — 检索内容的URI

        2.projection — 返回列的列表,传递null,将返回所有列,但这样效率低。

        3.selection — 返回行的过滤器,符合SQL WHERE 从句的格式(但不包括WHERE字段本身),传递null将返回对应uri的所有行(所有记录)。

        4.selectionArgs — 在参数selection里可能包含通配符?,这些通配符将由来自于selectionArgs参数里的值取代。依序出现在selection里,将包装成字符串。

        5.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关注和管理这些数据。

        一旦Loader知道application将不在使用它时,Loader将释放这些数据。例如,如果这些数据是来自于CursorLoader的游标,你不应该调用它自身的close()方法,如果游标正在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正被重置时调用,接着,数据将变得无效。这个回调告诉你数据即将被释放,因此你能移除对它的引用。

        下面的实现类调用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

        如下所示的例子,是一个利用Loader展示LIstVIiew数据的fragment的完整实现,该listview的展示数据来自于查询联系人content provider。使用CursorLoader管理和查询Provider。

        对于一个访问用户联系人的应用,像下面所示的例子,其manifest文件里必须包括READ_CONATACTS权限。

       

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 Examples

        在ApiDemos里有几个不同例子,这些例子解释了如何使用loaders:

       1.LoaderCursor — 上面所示片段的完全的版本

       2.LoaderThrottle — An example of how to use throttling to reduce the number of queries a content provider does when its data changes

       

        下载和安装SDK例子的更多信息,参见Getting the Samples

猜你喜欢

转载自xhmj12.iteye.com/blog/2208538