Android中loader的学习:

装载器从android3.0开始引进。它使得在activity或fragment中异步加载数据变得简单。装载器具有如下特性:
1.它们对每个Activity和Fragment都有效。
2.他们提供了异步加载数据的能力。
3.它们监视数据源的一将一动并在内容改变时传送新的结果。
4.当由于配置改变而被重新创建后,它们自动重连到上一个加载器的游标,所以不必重新查询数据。
这里写图片描述

一个使用装载器的应用会典型的包含如下组件
一个Activity或Fragment.
一个LoaderManager的实例.
一个加载被ContentProvider所支持的数据的CursorLoader.或者,你可以从Loader或AsyncTaskLoader实现你自己的装载器来从其它源加载数据.
一个LoaderManager.LoaderCallbacks的实现.这是你创建新的装载器以及管理你的已有装载器的引用的地方.
一个显示装载器的数据的途径,例如使用一个SimpleCursorAdapter.
一个数据源,比如当是用CursorLoader时,它将是一个ContentProvider.
**onCreateLoader,在创建activity时跟着onCreate会调用一次
onLoadFinished,每次改变和Loader相关的数据库记录后会调用一次
onLoaderReset,在关闭Activity时调用,释放资源**
启动一个装载器
LoaderManager管理一个Activiry或Fragment中的一个或多个装载器.但每个activity或fragment只拥有一LoaderManager.
你通常要在activity的onCreate()方法中或fragment的onActivityCreated()方法中初始化一个装载器.你可以如下创建:

        /**
         * initLoader(int id, Bundle args, LoaderCallbacks<Cursor> callback)
         * 参数1:0 表示的是对loader唯一标示,用来区分是哪一个加载器,只有一个的时候一般默认为0 参数2:null
         * 是loader构造函数需要提供的一些参数 一般我们提供null 参数3:this
         * 表示的是我们loader的回调函数对象,由于我们实现了这个回调函数,因此传入this
         * 注意initLoader不一定会在onCreateLoader之前调用,如果没有创建loader,那么会先调用onCreateLoader然后再initLoader
         * */
        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 onQueryTextChange(String newText) {
        // 在动作栏上的搜索字串改变时被调用.更新
        // 搜索过滤器,并重启loader来执行一个新的查询
        // 执行的顺序是onQueryTextChange---------->onCreateLoader------->onLoadFinished
        System.out.println("onQueryTextChange-----call");
        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将使用默认顺序,默认顺序可能是无顺序.
代码例子:

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // 当一个新的loader需被创建时调用.本例仅有一个Loader,
        // 所以我们不需关心ID.首先设置base URI,URI指向的是联系人
        System.out.println("onCreateLoader-----call");
        mUserNew.clear();
        Uri baseUri;
        if (mCurFilter != null) {
            // 如果需要通过部分名字的匹配查找,用CONTENT_FILTER_URI
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // 现在创建并返回一个CursorLoader,它将负责创建一个
        // Cursor用于显示数据
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        // 第3个参数需要从baseUri中选择的字段,如果为空,则返回的Cursor将包含所有的字段
        // 第4个参数 select 表示选择语句,第五个参数null是选择参数,因为选择语句中没有出现?,因此没有选择参数,那么我们直接传入null
        // 第6个参数是按什么排序
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

onLoadFinished
这个方法是在前面已创建的装载器已经完成其加载过程后被调用.这个方法保证会在应用到装载器上的数据被释放之前被调用.在此方法中,你必须删除所有对旧数据的使用(因为它将很快会被删除),但是不要自己去释放它们,因为它们的装载器会做这些事情.
装载器一旦了解到应用不再使用数据时,将马上释放这些数据.例如,如果数据是一个从CursorLoader来的游标,你不应调用游标的close().如果游标被放置在一个CursorAdapter中,你应使用swapCursor()方法,以使旧的游标不被关闭.例如:

//这个Adapter被用于显示列表的数据.  
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():

public void onLoaderReset(Loader<Cursor> loader) {  
    //此处是用于上面的onLoadFinished()的游标将被关闭时执行, 我们需确保我们不再使用它.  
    mAdapter.swapCursor(null);  
} 

下面是一个完整的例子,如果需要使用SimpleCursorAdapter mAdapter去接收参数并且显示那么请打开setListAdapter(mAdapter),关闭setListAdapter(mAdapterNew) 代码如下:

package com.example.cursorloaderfragment;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import android.app.ListFragment;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.provider.ContactsContract.Contacts;
import android.text.TextUtils;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.ListView;
import android.widget.SearchView;
import android.widget.SearchView.OnQueryTextListener;
import android.widget.SimpleAdapter;
import android.widget.SimpleCursorAdapter;

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

    // 这是用于显示列表数据的Adapter
     SimpleCursorAdapter mAdapter;
    SimpleAdapter mAdapterNew;
    // 如果非null,这是当前的搜索过虑器
    String mCurFilter;
    private List<Map<String, String>> mUserNew = new ArrayList<Map<String, String>>();

    @Override
    public void onActivityCreated(Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        // 如果列表中没有数据,就给控件一些文字去显示.在一个真正的应用中这应用资源中取得.
        setEmptyText("No phone numbers");
        // 我们在动作栏中有一个菜单项.
        setHasOptionsMenu(true);
         //创建一个空的adapter,我们将用它显示加载后的数据
         mAdapter = new SimpleCursorAdapter(getActivity(),
         android.R.layout.simple_list_item_2, null, new String[] {
         Contacts.DISPLAY_NAME, Contacts._ID },
         new int[] { android.R.id.text1, android.R.id.text2 }, 0);

        mAdapterNew = new SimpleAdapter(getActivity(), mUserNew, R.layout.cell,
                new String[] { "username", "phonenum" }, new int[] {
                        R.id.username, R.id.phonenum });
         mAdapter = new SimpleCursorAdapter(getActivity(),
         android.R.layout.simple_list_item_2, null, new
         String[]{Contacts.DISPLAY_NAME,Contacts._ID}, new
         int[]{android.R.id.text1,android.R.id.text2},0);
         //这里我们实现了两种方式的显示,一种是可以使用自己的数据显示,一种是使用Cursor数据显示,分别开启不同的setListAdapter即可
        //setListAdapter(mAdapter);
        setListAdapter(mAdapterNew);
        /**
         * initLoader(int id, Bundle args, LoaderCallbacks<Cursor> callback)
         * 参数1:0 表示的是对loader唯一标示,用来区分是哪一个加载器,只有一个的时候一般默认为0 参数2:null
         * 是loader构造函数需要提供的一些参数 一般我们提供null 参数3:this
         * 表示的是我们loader的回调函数对象,由于我们实现了这个回调函数,因此传入this
         * 注意initLoader不一定会在onCreateLoader之前调用,如果没有创建loader,那么会先调用onCreateLoader然后再initLoader
         * */
        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) {
        // 在动作栏上的搜索字串改变时被调用.更新
        // 搜索过滤器,并重启loader来执行一个新的查询
        // 执行的顺序是onQueryTextChange---------->onCreateLoader------->onLoadFinished
        System.out.println("onQueryTextChange-----call");
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        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) {
        System.out.println("Item clicked: " + id);
    }

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

    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        // 当一个新的loader需被创建时调用.本例仅有一个Loader,
        // 所以我们不需关心ID.首先设置base URI,URI指向的是联系人
        System.out.println("onCreateLoader-----call");
        mUserNew.clear();
        Uri baseUri;
        if (mCurFilter != null) {
            // 如果需要通过部分名字的匹配查找,用CONTENT_FILTER_URI
            baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = Contacts.CONTENT_URI;
        }

        // 现在创建并返回一个CursorLoader,它将负责创建一个
        // Cursor用于显示数据
        String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + Contacts.DISPLAY_NAME + " != '' ))";
        // 第3个参数需要从baseUri中选择的字段,如果为空,则返回的Cursor将包含所有的字段
        // 第4个参数 select 表示选择语句,第五个参数null是选择参数,因为选择语句中没有出现?,因此没有选择参数,那么我们直接传入null
        // 第6个参数是按什么排序
        return new CursorLoader(getActivity(), baseUri,
                CONTACTS_SUMMARY_PROJECTION, select, null,
                Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    }

    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        System.out.println("onLoadFinished-----call");
        if (data != null) {
            // data.getColumnCount()------>这个是指的我们的有多少列
            // data.getCount()------>这个表示我们有多少行数据
            while (data.moveToNext()) {
                // 这些index的顺序与我们的CONTACTS_SUMMARY_PROJECTION 排序有关,因为打印出来的都是:
                // index_id=0 index_name=1 index_status=2
                // 这顺序正好是我们CONTACTS_SUMMARY_PROJECTION数组中的前三个
                String username = data.getString(data
                        .getColumnIndex(Contacts.DISPLAY_NAME));
                String userid = data.getString(data
                        .getColumnIndex(Contacts._ID));
                getPhoneNum(userid, username);
            }
        }
        mAdapterNew.notifyDataSetChanged();
        // 将新的cursor换进来.(框架将在我们返回时关心一下旧cursor的关闭)
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        System.out.println("onLoaderReset-----call");
        // 在最后一个Cursor准备进入上面的onLoadFinished()之前.
        // Cursor要被关闭了,我们需要确保不再使用它.
         mAdapter.swapCursor(null);
    }

    public void getPhoneNum(String id, String name) {
        ContentResolver contentResolver = getActivity().getContentResolver();
        StringBuffer phoneNumber = new StringBuffer();
        String phonenum = null;
        Cursor phoneCursor = contentResolver
                .query(android.provider.ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
                        null,
                        android.provider.ContactsContract.CommonDataKinds.Phone.CONTACT_ID
                                + "=" + id, null, null);
        while (phoneCursor.moveToNext()) {
            String temp = phoneCursor
                    .getString(phoneCursor
                            .getColumnIndex(android.provider.ContactsContract.CommonDataKinds.Phone.NUMBER));
            phoneNumber.append(temp + ",");
        }
        phoneCursor.close();
        //处理多个手机号码的显示,去掉最后面的","
        phonenum = phoneNumber.toString();
        if (phonenum.endsWith(",")) {
            phonenum = phonenum.substring(0, phonenum.length() - 1);
        }
        Map<String, String> maps = new HashMap<String, String>();
        maps.put("username", name);
        maps.put("phonenum", phonenum);
        mUserNew.add(maps);
    }
}

当我们使用的是setListAdapter(mAdapterNew)的时候我们是可以显示我们的手机联系人姓名和手机号码,支持多个手机号码的显示,中间用”,”隔开。
然后就是我们Activity的文件code,其实什么都没做:

package com.example.cursorloaderfragment;

import android.app.Activity;
import android.os.Bundle;

public class MainActivity extends Activity{
    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
    }
}

下面就是我们的布局文件activity_main.xml:

<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
    xmlns:tools="http://schemas.android.com/tools"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    android:paddingBottom="@dimen/activity_vertical_margin"
    android:paddingLeft="@dimen/activity_horizontal_margin"
    android:paddingRight="@dimen/activity_horizontal_margin"
    android:paddingTop="@dimen/activity_vertical_margin"
    tools:context="com.example.cursorloaderfragment.MainActivity" >

<fragment
    class="com.example.cursorloaderfragment.CursorLoaderListFragment"
    android:id="@+id/Container"
    android:layout_width="match_parent"
    android:layout_height="match_parent"
    />

</RelativeLayout>

然后是为了显示手机号码而建的另一个cell.xml,在setListAdapter(mAdapterNew)开启的时候使用:

<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
    android:layout_width="match_parent"
    android:layout_height="wrap_content"
    android:orientation="horizontal" >

<TextView 
    android:id="@+id/username"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:textSize="20sp"
    android:textStyle="bold"
    android:layout_height="wrap_content"
    />
<TextView 
    android:id="@+id/phonenum"
    android:layout_width="0dp"
    android:layout_weight="1"
    android:layout_height="wrap_content"
    />
</LinearLayout>

注意:因为是需要联系人,所以我们得加上权限:

   <uses-permission android:name="android.permission.READ_CONTACTS"/>

显示效果(带手机号码):
这里写图片描述
显示效果(CursorLoader不带手机号码用户名下面是数据的id号)
这里写图片描述
最后工程的code给一个百度云盘地址:
链接:http://pan.baidu.com/s/1hqIpzU0 密码:4djd

然后是再给出一个项目工程里面使用比上面的复杂,也值得学习学习:

http://terryblog.blog.51cto.com/1764499/794139

链接:http://pan.baidu.com/s/1c0hPJMS 密码:vcxr

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值