前言

  Loaders,装载机,适用于Android3.0以及更高的版本,它提供了一套在UI的主线程中异步加载数据的框架。使用Loaders可以非常简单的在Activity或者Fragment中异步加载数据,一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,这样可以避免查询数据的时候,造成UI主线程的卡顿。

  Loaders有以下特点:

  • 可以适用于Activity和Fragment。
  • 可以提供异步的方式加载数据。
  • 监听数据源,当数据改变的时候,将新的数据发布到UI上。
  • Loaders使用Cursor加载数据,在更改Cursor的时候,会自动重新连接到最后配置的Cursor中读取数据,因此不需要重新查询数据。

  在Android中使用Loaders机制,需要多个类和接口的配合,以下是它们大致的关系图,之后的内容会对这几个类或接口进行详细讲解:

LoaderManager

  LoaderManager,装载机管理器。用于在Activity或者Fragment中管理一个或多个Loader实例。在Activity或者Fragment中,可以通过getLoaderManager()方法获取LoaderManager对象,它是一个单例模式。

  介绍几个LoaderManager提供的方法,用于管理Loader:

  • Loader<D> initLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):初始化一个Loader,并注册回调事件。
  • Loader<D> restartLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):重新启动或创建一个Loader,并注册回调事件。
  • Loader<D> getLoader(int id):返回给定Id的Loader,如果没有找到则返回Null。
  • void destroyLoader(int id):根据指定Id,停止和删除Loader。

  通过上面几个方法的参数可以看到,都有一个id参数,这个Id是Loader的标识,因为LoaderManager可以管理一个或多个Loader,所以必须通过这个Id参数来唯一确定一个Loader。而InitLoader()、restartLoader()中的bundle参数,传递一个Bundle对象给LoaderCallbacks中的onCreateLoader()去获取,下面介绍LoaderCallbacks。

LoaderManager.LoaderCallbacks

  LoaderCallbacks是LoaderManager和Loader之间的回调接口。它是一个回调接口,所以我们需要实现其定义的三个方法:

  • Loader<D> onCreateLoader(int id,Bundle bundle):根据指定Id,初始化一个新的Loader。
  • void onLoadFinished(Loader<D> loader,D data):当Loader被加载完毕后被调用,在其中处理Loader获取的Cursor数据。
  • void onLoaderReset(Loader<D> loader):当Loader被销毁的时候被调用,在其中可以使Loader的数据不可用。

  从LoaderCallbacks的声明的几个方法中可以看到,它是一个泛型的接口,需要指定Loader数据的类型。如果是数据源是从一个ContentProvider中获取的,一般直接使用它的子类CursorLoader,下面介绍CursorLoader。

Loader

  Loader,一个抽象的类,用于执行异步加载数据,这个Loader对象可以监视数据源的改变和在内容改变后,以新数据的内容改变UI的展示。它是一个Loader的抽象接口,所有需要实现的Loader功能的类都需要实现这个接口,但是如果需要自己开发一个装载机的话,一般并不推荐继承Loader接口,而是继承它的子类AsyncTaskLoader,这是一个以AsyncTask框架执行的异步加载。

  Android中还提供了一个CursorLoader类,它是AsyncTaskLoader的子类,一个异步的加载数据的类,通过ContentResolver的标准查询并返回一个Cursor。这个类实现了Loader的协议,以一种标准的方式查询Cursor。

  CursorLoader类有两个构造函数,推荐使用第二个,因为使用第一个构造函数,需要还需要通过CursorLoader提供的一些了getXxx()方法设置对应的属性:

  • CursorLoader(Context context)
  • CursorLoader(Context context,Uri uri,String[] projection,String selection ,String[] selectionArgs,String sortOrder)

SimpleCursorAdapter

  在Android中,数据的展示都需要使用一个Adapter适配器,而Loader一般返回的就是一个Cursor的数据,可以使用BaseAdapter的一个子类SimpleCursorAdapter,它可以使用XML资源文件自定义一个布局在展示数据。它有两个构造函数,但是有一个构造函数在API Level11之后就不推荐使用。下面是构造函数的签名:

  SimpleCursorAdapter(Context context,int layout,Cursor c,String[] from,int[] to,int flags).

  最后一个参数flags是一个标识,标识当数据改变调用onContentChanged()的时候,是否通知ContentProvider数据的改变,如果无需监听ContentProvider的改变,则可以传0。对于SimpleCursorAdapter适配器的Cursor的改变,可以使用SimpleCursorAdapter.swapCursor(Cursor)方法,它会与旧的Cursor互换,并且返回旧的Cursor。

Demo

  下面通过一个Demo来讲解一下Loaders的使用。在这个Demo中,数据使用SQLite数据库保存,而使用ContentProvider进行数据的请求与访问。在SQLite数据库中,存在一个Student表,它近有两个字段:_id,name。在Demo中,使用一个ListView展示数据,使用LoaderManager管理一个Loader,并通过这个Loader的回调接口进行刷新ListView的数据显示。进行对SQLite数据库中的数据进行增加与删除。下面不提供SQLiteOpenHelper和ContentProvider相关实现类的代码,如有需要可以下载源码查看,对于SQLite和ContentProvider的内容,不清楚的朋友可以参见博客:数据持久化之SQLiteContentProvider

  实现代码:

 package com.example.loadermanagerdemo;

 import android.net.Uri;
import android.os.Bundle;
import android.app.Activity;
import android.app.AlertDialog;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.util.Log;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ContextMenu.ContextMenuInfo;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
import android.widget.TextView; public class MainActivity extends Activity {
private LoaderManager manager;
private ListView listview;
private AlertDialog alertDialog;
private SimpleCursorAdapter mAdapter;
private final String TAG="main"; @Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listview = (ListView) findViewById(R.id.listView1);
//使用一个SimpleCursorAdapter,布局使用android自带的布局资源simple_list_item_1, android.R.id.text1 为simple_list_item_1中TextView的Id
mAdapter = new SimpleCursorAdapter(MainActivity.this,
android.R.layout.simple_list_item_1, null,
new String[] { "name" }, new int[] { android.R.id.text1 },0); // 获取Loader管理器。
manager = getLoaderManager();
// 初始化并启动一个Loader,设定标识为1000,并制定一个回调函数。
manager.initLoader(1000, null, callbacks); // 为ListView注册一个上下文菜单
registerForContextMenu(listview);
} @Override
public void onCreateContextMenu(ContextMenu menu, View v,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, v, menuInfo);
// 声明一个上下文菜单,contentmenu中声明了两个菜单,添加和删除
MenuInflater inflater = getMenuInflater();
inflater.inflate(R.menu.contentmenu, menu);
} @Override
public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) {
case R.id.menu_add:
// 声明一个对话框
AlertDialog.Builder builder = new AlertDialog.Builder(
MainActivity.this);
// 加载一个自定义布局,add_name中有一个EditText和Button控件。
final View view = LayoutInflater.from(MainActivity.this).inflate(
R.layout.add_name, null);
Button btnAdd = (Button) view.findViewById(R.id.btnAdd);
btnAdd.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
EditText etAdd = (EditText) view
.findViewById(R.id.username);
String name = etAdd.getText().toString();
// 使用ContentResolver进行删除操作,根据name字段。
ContentResolver contentResolver = getContentResolver();
ContentValues contentValues = new ContentValues();
contentValues.put("name", name);
Uri uri = Uri
.parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
Uri result = contentResolver.insert(uri, contentValues);
if (result != null) {
//result不为空证明删除成功,重新启动Loader,注意标识需要和之前init的标识一致。
manager.restartLoader(1000, null, callbacks);
}
// 关闭对话框
alertDialog.dismiss(); Log.i(TAG, "添加数据成功,name="+name);
}
});
builder.setView(view);
alertDialog = builder.show();
return true;
case R.id.menu_delete:
// 获取菜单选项的信息
AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
.getMenuInfo();
// 获取到选项的TextView控件,并得到选中项的那么
TextView tv = (TextView) info.targetView;
String name = tv.getText().toString();
// 使用ContentResolver进行删除操作
Uri url = Uri
.parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
ContentResolver contentResolver = getContentResolver();
String where = "name=?";
String[] selectionArgs = { name };
int count = contentResolver.delete(url, where, selectionArgs);
if (count == 1) {
//这个操作仅删除单挑记录,如果删除行为1 ,则重新启动Loader
manager.restartLoader(1000, null, callbacks);
}
Log.i(TAG, "删除数据成功,name="+name);
return true;
default:
return super.onContextItemSelected(item);
} } // Loader的回调接口
private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderCallbacks<Cursor>() { @Override
public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
// 在Loader创建的时候被调用,这里使用一个ContentProvider获取数据,所以使用CursorLoader返回数据
Uri uri = Uri
.parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
CursorLoader loader = new CursorLoader(MainActivity.this, uri,
null, null, null, null);
Log.i(TAG, "onCreateLoader被执行。");
return loader;
} @Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
//刷新SimpleCursorAdapter的数据
mAdapter.swapCursor(cursor);
// 重新设定适配器
listview.setAdapter(mAdapter);
Log.i(TAG, "onLoadFinished被执行。");
} @Override
public void onLoaderReset(Loader<Cursor> loader) {
// 当Loader被从LoaderManager中移除的时候,被执行,清空SimpleCursorAdapter适配器的Cursor
mAdapter.swapCursor(null);
Log.i(TAG, "onLoaderReset被执行。");
}
}; @Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.main, menu);
return true;
} }

  效果展示:

  源码下载

Android--Loaders的更多相关文章

  1. Android Loader使用详解

    1.CursorLoader使用Demo public class MainActivity extends Activity implements  LoaderManager.LoaderCall ...

  2. 【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57e3a3bc42eb88da6d4be143 作者:王赛 1. 前言 Andr ...

  3. Android Weekly Notes Issue #229

    Android Weekly Issue #229 October 30th, 2016 Android Weekly Issue #229 Android Weekly笔记, 本期内容包括: 性能库 ...

  4. Android MVP模式 谷歌官方代码解读

    Google官方MVP Sample代码解读 关于Android程序的构架, 当前(2016.10)最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法. Repo ...

  5. [Android Pro] Android异步任务处理之AsyncTaskLoader的使用

    reference to : http://blog.csdn.net/happy_horse/article/details/51518280 最近项目中涉及到加载本地的地名.db文件,数据量大,自 ...

  6. [Android Pro] AsyncTaskLoader vs AsyncTask

    reference to : http://blog.csdn.net/a910626/article/details/45599133 我看了一下asyncTask是从LV3开始,AsyncTask ...

  7. Android中SQLite数据库小计

    2016-03-16 Android数据库支持 本文节选并翻译<Enterprise Android - Programing Android Database Applications for ...

  8. Android内存优化-内存泄漏的几个场景以及解决方式

    转自:http://blog.csdn.net/a910626/article/details/50849760 一.什么是内存泄漏 在Java程序中,如果一个对象没有利用价值了,正常情况下gc是会对 ...

  9. android.app.Activity阅读摘要,有时候会不会需要保持一些现场数据呢? 想让系统帮你退出到后台或者挂掉前做些前置保持工作吗,重点参考吧:

    * * @param savedInstanceState If the activity is being re-initialized after * previously being shut ...

  10. Google Developing for Android 三 - Performance最佳实践

    Google Developing for Android 三 - Performance最佳实践 发表于 2015-06-07   |   分类于 Android最佳实践 原文 Developing ...

随机推荐

  1. KVM 虚拟机的热迁移

    热迁移:顾名思义在虚拟机不关机的情况下将KVM虚拟机进行迁移 准备工作:两台KVM虚拟机,一台nfs虚拟机,centos7.4系统 主机 IP地址 主机名 KVM01 10.00.11 kvm01 K ...

  2. java中的抽象类的作用

    抽象函数的作用有很多,我就举一个例子,(mars老师的例子): 打印机: 父类---Printer 子类---HBPrinter 子类----CanonPrinter 测试类----Test //定义 ...

  3. Sudoku(POJ2676/3074)

    Sudoku is one of the metaphysical techniques. If you understand the essence of it, you will have the ...

  4. MyBatis配置C3P0连接池

    一.导包 c3p0包     mybatis包 数据库的连接包 二.继承UnpooledDataSourceFactory的类 Mybatis 没有帮开发者实现 c3p0 数据库连接池,故需要使用者自 ...

  5. javascript中的replace方法

    1.replace 调用方法str.replace(regexp|substr, newSubStr|function) regexp,正则表达式 substr,需要被替换的字符串 newSubStr ...

  6. Openvswitch手册(9): Flow

    这一节我们将flow table flow table主要由ovs-ofctl命令操作 ovs-ofctl可以走和openflow controller一样的协议: ssl:ip[:port]: Th ...

  7. MySQL--Skip GTID CAP

    import os script_file = "./skip_file.sql" def write_script(script_content): file_handle = ...

  8. 像纸质笔记本一样给div,textarea添加行的分割线

    想要给textarea添加一个背景图来实现 但是背景图有几个问题, 1.每个div或者textarea的line-height不一样,对于每个不同的line-height都需要一个不同的背景图 2.当 ...

  9. jenkins在windows服务器上执行含git push命令的脚本权限不足的解决方法

    错误摘要 默认情况下执行脚本是没问题的,但是脚本中含有git push命令就无法执行了 用jenkins部署hexo博客时候遇到的,执行hexo d -g一直阻塞至Build was aborted, ...

  10. C++内存读写例子

    /************************************************************* // 声明头文件 #ifndef LyShark__h #define L ...