Android Loader用法总结

Android提供了几种异步加载数据的方式,AsyncTaskLoader就是其中一种,这里对它的用法做一个总结。

Android在3.0引入了Loader(加载器),支持在Activity或Fragment中异步加载数据。想要在低版本上使用Loader可以用v4兼容包。

3.0之后官方文档强烈推荐使用Loader加载数据。

Loader的特性

  • 可用于每个 Activity 和 Fragment。
  • 支持异步加载数据。
  • 监控其数据源并在内容变化时传递新结果。
  • 在某一配置改变(如横竖屏切换)后重建Loader时,会自动重新连接上一个Loader的游标(cursor)。 因此,它们无需重新查询数据。

LoaderManager

一个与 Activity 或 Fragment 相关联的的抽象类,用于管理一个或多个 Loader 实例。 这有助于应用管理与 Activity 或 Fragment 生命周期相关联的、运行时间较长的操作。

它最常见的用法是与 CursorLoader 一起使用,你也可以实现自己的Loader来加载其他类型的数据。

每个 Activity 或Fragment中只有一个 LoaderManager。但一个 LoaderManager 可以有多个Loader。

LoaderManager.LoaderCallbacks

一个回调接口,用于客户端与 LoaderManager 进行交互。例如,你可以使用 onCreateLoader() 回调方法创建新的Loader。

Loader

一个执行异步数据加载的抽象类,是Loader的基类。

通常使用 CursorLoader,你也可以实现自己的子类。

Loader处于活动状态时,它们应该监视它们的数据源并且在数据改变时传送新的结果。

Loader本身并不支持异步加载机制,所以当我们编写自己的Loader的时候,不应该直接继承Loader类,应该继承AsyncTaskLoader,AsyncTaskLoader支持异步加载机制。

AsyncTaskLoader

一个使用AsyncTask来执行异步加载工作的抽象类。

CursorLoader

AsyncTaskLoader的子类,查询ContentResolver然后返回一个Cursor。

该类以标准游标查询实现了Loader协议,它的查询是通过AsyncTaskLoader在后台线程中执行,所以不会阻塞UI线程。使用这个Loader是从ContentProvider异步加载数据的最好方式。

框架结构

这里写图片描述

启动Loader

通常要在Activity的onCreate()方法中或Fragment的onActivityCreated()方法中初始化Loader,可以按照下面的方式创建:

//准备Loader,重连一个已存在的Loader或者启动新的Loader
//id:一个唯一的ID用于标识这个Loader,在这个例子中是0;
//args:可选的参数,在Loader初始化时作为参数传入,本例中是null;
//callbacks:一个LoaderManager.LoaderCallbacks的实现,LoaderManager 将调用此实现来报告Loader事件;在这个例子中,当前类实现了这个接口,所以传的是自身的引用:this。
getLoaderManager().initLoader(0, null, this);

initLoader() 确保了Loader已初始化且处于活动状态。这可能会出现两种结果:

  1. 如果 ID 所指的Loader已存在,则将重复使用上次创建的Loader,这时候args会被忽略,因为重用了之前的Loader。
  2. 如果 ID 所指的Loader不存在,则 initLoader() 将触发 LoaderManager.LoaderCallbacks 中的回调方法 onCreateLoader()。在此方法中,你可以在这里实例化并返回新的Loader。

在这两种情况下,传入的LoaderManager.LoaderCallbacks的实现都与Loader绑定在一起,并且会在Loader状态变化时被调用;

如果在调用这个initLoader()方法时,Loader已经处于启动状态(也就是说这个Loader已存在),并且所请求的Loader已产生了数据,那么系统会马上调用onLoadFinished(),所以你必须为这种情况的发生做好准备。

intiLoader()会返回一个创建的Loader,但是你不用获取它的引用,因为LoadeManager会自动管理该Loader的生命周期,你只用在它回调提供的生命周期方法中做自己数据逻辑的处理即可。

重启Loader

当你使用initLoader()时,如果指定ID的Loader已经存在,则它使用这个Loader;如果不存在,它将创建一个新的。但是有的时候你却想丢弃旧的Loader然后开始一个新的Loader。

要想丢弃旧的Loader,应该使用restartLoader()。例如,下面这个SearchView.OnQueryTextListener的实现在用户查询发生改变时重启了Loader,Loader需要重启从而才能使用新的搜索过滤词来进行一次新的查询。

public boolean onQueryTextChanged(String newText) {
    // 当搜索内容变化时,更新搜索过滤词,重启Loader并用这个搜索过滤词重新查询
    mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
    getLoaderManager().restartLoader(0, null, this);
    return true;
}

使用LoaderManager中的回调

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

Loader,尤其是CursorLoader,我们希望在它停止后依然保持数据,这让应用可以在Activity或Fragment的onStop()和onStart()方法保持数据;当用户返回应用时,他们不需要再等待数据加载,你可以使用LoaderManager.LoaderCallbacks中的方法,在需要时创建新的Loader,并且告诉应用什么时候要停止使用Loader中的数据。

LoaderManager.LoaderCallbacks包含以下方法:

  • onCreateLoader()
    根据传入的ID,初始化并返回一个新的Loader。

    当你试图去操作一个Loader时(比如,调用initLoader()方法),它会检查是否指定ID的Loader已经存在,如果不存在,将会触发LoaderManager.LoaderCallbacks中的onCreateLoader(),你需要在这里创建一个新Loader并返回。

  • onLoadFinished():当一个Loader完成了数据加载之后调用。

    这个方法确保会在Loader上的数据被释放之前被调用。在此方法中,你必须移除所有对旧数据的使用(因为它们将很快会被删除),但是不要自己去释放它们,因为Loader会去做这些事情。

    Loader一旦了解到应用不再使用这些数据时,将马上释放这些数据。

    例如,如果数据是一个从CursorLoader来的Cursor,你不应该自己调用cursor的close()方法;如果cursor被放置在一个CursorAdapter中,你可以使用swapCursor()方法进行新数据交换,这样,旧的cursor就不会被关闭(CursorLoader的实现中会自动帮你关闭旧的cursor),就不会导致Adapter加载异常。

  • onLoaderReset():当一个Loader被重置,从而使得其数据无效时被调用。

    这个回调告诉你什么时候数据将被释放,所以你可以释放对它的引用。

    下面实现了调用参数为null的swapCursor()

public void onLoaderReset(Loader<Cursor> loader) {
    mAdapter.swapCursor(null);
}

例子

下面的例子实现了从通讯录加载用户昵称,通过SearchView过滤搜索结果。
因为要读取通讯录,所以要现在Manifest中声明READ_CONTACTS 权限

public class CursorLoaderActivity
        extends Activity
        implements SearchView.OnQueryTextListener,
        LoaderManager.LoaderCallbacks<Cursor> {

    private SimpleCursorAdapter mAdapter;
    private String mCurFilter;// 搜索过滤器
    private ListView mListView;
    private TextView mTxtEmpty;

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

        setContentView(R.layout.activity_loader);
        mListView = (ListView) findViewById(R.id.listview);
        mTxtEmpty = (TextView) findViewById(R.id.txt_empty);

        mListView.setEmptyView(mTxtEmpty);//没有数据时显示的view

        mAdapter = new SimpleCursorAdapter(this,
                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);
        mListView.setAdapter(mAdapter);

        // 启动loader,可以重连一个已经存在的也可以启动一个新的
        getLoaderManager().initLoader(0, null, this);
    }

    @Override
    public boolean onCreateOptionsMenu(Menu menu) {
        //在ActionBar上显示SearchView
        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(this);
        sv.setOnQueryTextListener(this);
        item.setActionView(sv);
        return super.onCreateOptionsMenu(menu);
    }


    public boolean onQueryTextChange(String newText) {
        //当搜索字符串变化时调用
        //使用新的搜索过滤器重启loader
        mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
        getLoaderManager().restartLoader(0, null, this);
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String query) {
        return true;
    }

    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,
    };

    /**
     * 创建一个CursorLoader并返回,如果initLoader()指定id的loader不存在,会触发这个回调方法
     *
     * @param id   给loader指定的id,如果只有一个loader,就不用管id
     * @param args 传递的参数
     * @return
     */
    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        //根据是否对结果过滤来确定uri
        Uri baseUri;
        if (mCurFilter != null) {
            baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
                    Uri.encode(mCurFilter));
        } else {
            baseUri = ContactsContract.Contacts.CONTENT_URI;
        }
        String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
                + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
                + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
        CursorLoader cursorLoader = new CursorLoader(
                this,
                baseUri,//uri:要获取内容的uri
                CONTACTS_SUMMARY_PROJECTION, //要返回的列的集合,传入null将会返回所有列(比较低效)
                select,//一个过滤器,表示哪些行将会被返回,格式化成类似SQL where子句的样子,传入null将返回所有行
                null,//selectionArgs:可以在selection中包含一些'?',它将被本参数的值替换掉,这些值出现的顺序与'?'在selection中出现的顺序一致,值将作为字符串
                ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"//sortOrder:排序字符串,格式化成类似于SQL ORDER BY子句样子(除去ORDER BY),传入null将使用默认顺序,默认顺序可能是无序的
        );
        return cursorLoader;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
        //释放cursor(系统会帮我们关闭旧的cursor)         
        mAdapter.swapCursor(data);
    }

    public void onLoaderReset(Loader<Cursor> loader) {
        //当onLoadFinished()中的cursor将要被关闭的时候调用,我们需要确保它不再被使用
        mAdapter.swapCursor(null);

    }
}

什么时候不该用Loader

如果你需要确保后台任务执行完成,就不要用Loader,因为当Activity或Fragment被销毁的时候,这个Activity或Fragment里面的Loader也会被销毁。如果要执行后台任务,可以使用Service。
要记得Loader是用来帮助你创建相应的UI,异步加载数据到这些UI上。这就是Loader和创建它的组件的生命周期绑定到一起的原因,不要滥用Loader!

在Service中使用

LoaderManager明显的是为Activity或Fragment生命周期而设计的来管理Loader的类,因为Service没有这些状态变化,所以没必要在Service中使用LoaderManager。
但是如果你已经为Activity或Fragment写好了Loader,现在要在Service中重用这些代码,可以不使用LoaderManager,直接操作Loader。
在Service被创建的时候,create, register, start你的Loader

@Override
public void onCreate() {
    mCursorLoader = new CursorLoader(context, contentUri, projection, selection, selectionArgs, orderBy);
    mCursorLoader.registerListener(LOADER_ID_NETWORK, this);
    mCursorLoader.startLoading();
}

使Service implements OnLoadCompleteListener接口,在回调函数onLoadComplete中绑定数据和UI

@Override
public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
}

在onDestroy中销毁Loader

@Override
public void onDestroy() {
    // Stop the cursor loader
    if (mCursorLoader != null) {
        mCursorLoader.unregisterListener(this);
        mCursorLoader.cancelLoad();
        mCursorLoader.stopLoading();
    }
}

当然你也可以这样写

private MyCursorLoader mMyCursorLoader;

Loader.OnLoadCompleteListener<Cursor> mCursorListener = new    Loader.OnLoadCompleteListener<ConnectionStatus>() {
        @Override
        public void onLoadComplete(Loader<Cursor> loader, Cursor data) {
        }
};

Bundle bundle = new Bundle();
bundle.putString(BUNDLE_KEY_UID, uid);
mMyCursorLoader = new MyCursorLoader(mContext, bundle, null, this);
mMyCursorLoader.registerListener(LOADER_1, mCursorListener);
mMyCursorLoader.startLoading();

AsyncTaskLoader & AsyncTAsk

AsyncTaskLoader
优势:会自动刷新数据变化,会自动处理Activity配置变化造成的影响,适合处理纯数据加载。
劣势:不能实时通知UI刷新,不能在onLoadFinished时主动切换生命周期(比如replace fragment)。
AsyncTask
优势:可以与UI实时交互及replace操作。
劣势:不会自动处理Activity配置变化造成的影响。

Loader生命周期

几个坑

Loader id重复导致数据逻辑异常
多线程中restartLoader导致Loader抛出异常(都在UI线程中执行就行了)

参考:

  1. https://developer.android.com/guide/components/loaders.html?hl=zh-cn#app
  2. http://2dxgujun.com/post/2014/11/14/Use-Loaders-in-Android.html
  3. 源码分析:http://wangkuiwu.github.io/2014/06/25/Loader/
  4. http://www.grokkingandroid.com/using-loaders-in-android/
  5. http://stackoverflow.com/questions/8696146/can-you-use-a-loadermanager-from-a-service
  6. 用法介绍及源码分析:http://blog.csdn.net/yanbober/article/details/48861457
    扩展阅读:
    http://codetheory.in/asynchronous-background-execution-and-data-loading-with-loaders-framework-in-android/
  • 0
    点赞
  • 2
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值