Android API Guides 阅读笔记(5)----Loader

使用Loader,是为了方便在Activity或者Fragment中异步加载数据及监听数据源的变化,Loader的一些特征:

  • 每一个Activity或者Fragment都能使用Loader

  • Loader提供异步加载数据的数据

  • Loader可以监听数据资源,有数据内容改变时Loader可以提供新的数据

  • 当配置改变导致Activity或者Fragment重新创建后,Loader可以自动连接上次的游标(cursor),这样就不用重新执行查询操作了

在一个应用程序中使用Loader可能会用到的类文件(class)和接口(interface)如下:

  • LoaderManager:用来管理Loader的实例,通常和CursorLoader结合起来用,一个Activity或者Fragment中只能有一个LoaderManager,但是一个LoaderManager可以有多个Loaders

  • LoaderManager.LoaderCallbacks:用来和LoaderManager进行交互的回调接口

  • Loader:抽象类,用于执行异步加载数据,通常使用其子类CursorLoader,但也可以自定义一个子类使用

  • AsyncTaskLoader:Loader的子类,提供一个异步任务(AsyncTask)进行相关处理

  • CursorLoader:是AsycnTaskLoader的子类,用于查询ContentResolver并且返回一个Cursor,常用于从ContentProvider中异步加载数据

一个使用Loader的应用程序的组成:

  • 在一个Activity或者Fragment中

  • 定义LoaderManager的实例

  • 定义一个CursorLoader的实例用以从ContentProvider中加载数据,或者自定义一个Loader或者AsyncTaskLoader的子类去执行这个操作

  • 实现LoaderManager.LoaderCallbacks接口,后面暂时略过。。。

  • 接下来就是展示加载到的数据了,可以使用SimpleCursorAdapter等

  • 当然,最重要的是要有数据源,比如一个ContentProvider提供的数据源

启动一个Loader

  • 使用LoaderManager,在Activity的onCreate()或者在Fragment的onActivityCreated()方法中初始化Loader:
   getLoaderManager.initLoader(0,null,this);

initLoader中的参数如下:

  • 一个唯一识别Loader的id

  • 构造方法的可选参数

  • LoaderManager.LoaderCallbacks的实现,用以报告Loader的事件(先写着,再改)

initLoader方法用来确保Loader初始化完成并且处于活动状态,调用此方法的结果如下:

  • 如果通过id标识的这个Loader已经存在了,上次创建的Loader将会被重用

  • 如果指定id标识的Loader不存在,则通过LoaderManager.LoaderCallbacks的onCreateLoader()方法回调得到一个新的Loader实例

通常,Loader的生命周期由LoaderManager管理,所以我们很少直接与Loader交互

重新启动一个Loader

使用initLoader()方法,如果一个Loader已经存在了,则会重新使用这个存在的,所以当我不想使用这个存在的,就需要重新启动一个Loader,这时就可以使用restartLoader()方法

使用LoaderManager的回调方法

使用LoaderManager.LoaderCallbacks实现与LoaderManager的交互

Loader特别是CursorLoader,通常会在停止后仍然保留数据,这样就可以在Activity或者Fragment的onStop()和onStart()方法中保留应用程序的数据,这样,当用户返回到当前应用程序时,无需等待数据重新加载(对这句话不太理解)

LoaderManager.LoaderCallbacks包含如下回调方法:

  • onCreateLoader():通过Id进行实例化并返回一个新的Loader,通常在使用initLoader()时,如果指定Id的Loader不存在,就会新建一个Loader,就会用到这个回调方法(下面的实例中会说明其返回的Cursor的组成)

  • onLoadFinished():当之前创建的Loader加载完成时调用

  • onLoaderReset():当之前创建的Loader被重置时调用,这样,之前的Loader中的数据就不可用了

下面是官方文档的实例,通过实例能更好的理解怎样使用Loader:


public class CursorLoaderListFragment extends ListFragment
        implements SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {

    // 这是用于显示列表数据的Adapter
    SimpleCursorAdapter mAdapter;

    // 如果非null,这是当前的搜索过虑条件
    String mCurFilter;

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);

        // 如果列表中没有数据,就给控件一些文字去显示.在一个真正的应用
        // 中这应用资源中取得.
        setEmptyText("No phone numbers");

        // 设置在顶部栏中有菜单项.也就是允许Fragment使用菜单,这在之前的Fragment中有笔记
        setHasOptionsMenu(true);

        // 创建一个空的adapter,将用它显示加载后的数据
        mAdapter = new SimpleCursorAdapter(getActivity(),
                android.R.layout.simple_list_item_2, null,
                new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS},
                new int[]{android.R.id.text1, android.R.id.text2}, 0);

        //填充ListView
        setListAdapter(mAdapter);

        // 准备loader.可能是重连到一个已存在的或开始一个新的
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
        // 顶部栏的菜单项,用于点击出现搜索框
        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) {

        //获取搜索过滤条件(也就是变化是如果输入了文字就获取到)
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;

        //并重启loader来执行一个新的查询
        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) {
        // ListView中的点击事件,写入你想写的代码
        Log.i("FragmentComplexList", "Item clicked: " + id);
    }

    // 这是想获取的联系人中一行的数据.
    static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
            ContactsContract.Contacts._ID,
            ContactsContract.Contacts.DISPLAY_NAME,
            ContactsContract.Contacts.CONTACT_STATUS,
            ContactsContract.Contacts.CONTACT_PRESENCE,
            ContactsContract.Contacts.PHOTO_ID,
            ContactsContract.Contacts.LOOKUP_KEY,
    };

    /**
     * 当一个新的loader需被创建时调用.本例仅有一个Loader,所以不需要ID.首先设置baseUri,URI指向的是联系人
     */
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {

        Uri baseUri;

        //如果有搜索条件,就过滤出条件匹配的数据,如果没有搜索条件,就直接获取所有的数据
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }

        // 现在创建并返回一个CursorLoader,它将负责创建一个Cursor用于显示数据
        String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";

        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
        //CursorLoader的参数解释:CursorLoader(Context ctx,Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder)
        // ctx:为上下文,不多说
        // uri:需要搜索的内容的uri
        // projection:指定需要返回的列的集合,如果要返回所有列,使用null,但这样效率比较低
        // selection:过滤需要返回的行,就像是SQL中的where语句后面的条件,使用null返回所有行
        // selectArgs:过滤的条件,可以在上面的selection中使用"?"隔开来设置条件,也可以在这里写下条件,效果是一样的
        // sortOrder:对列进行排序,就像是SQL中的“order by”后面的内容
    }

    /**
     * 当之前创建的Loader加载完成时调用,将新的cursor换进来
     */
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {

        mAdapter.swapCursor(data);
    }

    /**
     * 在最后一个Cursor准备进入上面的onLoadFinished()之前,Cursor要被关闭了,需要确保不再使用它
     */
    public void onLoaderReset(Loader<Cursor> loader) {

        mAdapter.swapCursor(null);
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章