Android之数据存储----使用LoaderManager异步加载数据库
一、各种概念:
1、Loaders:
适用于Android3.0以及更高的版本,它提供了一套在UI的主线程中异步加载数据的框架。使用Loaders可以非常简单的在Activity或者Fragment中异步加载数据,一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,这样可以避免查询数据的时候,造成UI主线程的卡顿。
即使是查询SQLite数据库,用Loaders来操作会更加的简便。
Loaders有以下特点:
- 可以适用于Activity和Fragment。
 - 可以提供异步的方式加载数据。
 - 监听数据源,当数据改变的时候,将新的数据发布到UI上。
 - Loaders使用Cursor加载数据,在更改Cursor的时候,会自动重新连接到最后配置的Cursor中读取数据,因此不需要重新查询数据。
 
在Android中使用Loaders机制,需要多个类和接口的配合,以下是它们大致的关系图,之后的内容会对这几个类或接口进行详细讲解:
2、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。
3、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。
4、CursorLoader
我们知道,Loader一个抽象的类,用于执行异步加载数据,这个Loader对象可以监视数据源的改变和在内容改变后,以新数据的内容改变UI的展示。它是一个抽象接口,所有需要实现的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)
 
二、代码举例:
在这个例子中,数据使用SQLite数据库保存,然后用ContentProvider进行数据的请求与访问。在SQLite数据库中,已经存在一个Student表,它有两个字段:_id,name。在本例中,使用一个ListView展示数据,使用LoaderManager管理一个Loader,并通过这个Loader的回调接口进行加载ListView的数据显示并实时刷新,最终进行完成对SQLite数据库中的数据进行增加与删除。
整个工程文件的目录结构如下:

具体步骤如下:
步骤(1):新建类PersonDao,用于进行对SQLite的CRUD操作
步骤(2):新建类DBHelper,用于初始化SQLiate数据库
步骤(3):新建类PersonContentProvider,继承ContetProvider,记得声明权限。
步骤(4):添加单元测试类。我们在单元测试里向SQLite中添加一些记录。
注:上述步骤是ContentProvider中的知识,和上一篇博文:ContentProvider内容提供者中的步骤一模一样。所以这里就不列出代码了,如果不明白的话,可以回去回顾一下,或者在本文的最后下载源码也行。
我们在步骤(4)的单元测试里向数据库中添加一些数据之后,可以开始接下来最关键的步骤了:
步骤(5):
MainActivity.java:
package com.example.loadermanagertest; import android.annotation.SuppressLint;
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.net.Uri;
import android.os.Bundle;
import android.util.Log;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View;
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; @SuppressLint("InflateParams") public class MainActivity extends Activity {
private LoaderManager manager;
private ListView listview;
private AlertDialog alertDialog;
private SimpleCursorAdapter mAdapter;
private final String TAG="MainActivity"; @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);
} //单击单个的item,弹出菜单选项,让你选择是添加还是删除
@Override
public boolean onContextItemSelected(MenuItem item) { switch (item.getItemId()) {
//当用户点击菜单中的“添加”选项是,弹出对话框,在对话框里添加name的值
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.loadermanagertest.PersonContentProvider/person");
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.loadermanagertest.PersonContentProvider/person");
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的回调接口,在这里异步加载数据库的内容,显示在ListView上,同时能够自动更新
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.loadermanagertest.PersonContentProvider/person");
CursorLoader loader = new CursorLoader(MainActivity.this, uri,
null, null, null, null);
Log.i(TAG, "--->>onCreateLoader被执行。");
return loader;
} //完成对UI的数据提取,更新UI
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
//把数据提取出来,放到适配器中完成对UI的更新操作(刷新SimpleCursorAdapter的数据)
mAdapter.swapCursor(cursor);
// 为ListView绑定适配器
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;
} }
核心代码:132行至162行、95行和122行的实时刷新
注意为ListView绑定适配器的代码:listview.setAdapter(mAdapter)是在Loader的回调接口中(132行)进行的,也就是在这里更新UI,这样就能够实现自动刷新UI。
43行:新建一个SimpleCursorAdapter适配器
先通过getLoaderManager()方法获取LoaderManager对象(48行),然后通过manager.initLoader(1000, null, callbacks)初始化一个Loader(50行)。其方法的完整版是:
public abstract <D> Loader<D> initLoader(int id,Bundle args, LoaderManager.LoaderCallbacks<D> callback)
- 第一个参数id:一个Acticity中可以加载多个Loader,所以要给每个Loader制定一个唯一的标识符id。第二个参数可以置空。
 - 第三个参数callback:回调。
 
Loader的回调接口是在132行至162行定义的,也就是在这里异步加载数据库的内容,显示在ListView上,同时能够自动更新。
我们在第53行为ListView注册一个上下文菜单,上下文的菜单布局是在62行的R.menu.contentmenu.xml中定义的(稍后给出代码)。
当用户单击单个的item时,弹出菜单选项,让你选择是添加还是删除(67行定义的方法)。如果是选择添加内容,则弹出一个对话框(71行)(对话框的布局文件R.layout.add_name稍后给出),输入需要加入的内容,单击确定,就会更新到UI(95行的manager.restartLoader(1000, null, callbacks)方法);如果选择删除内容,则直接删除,并更新UI(122行的manager.restartLoader(1000, null, callbacks)方法)。
R.menu.contentmenu.xml:用于定义上下文菜单的布局
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android" > <item
android:id="@+id/menu_add"
android:orderInCategory="100"
android:showAsAction="never"
android:title="添加">
</item>
<item
android:id="@+id/menu_delete"
android:orderInCategory="100"
android:showAsAction="never"
android:title="删除">
</item>
</menu>
R.layout.add_name.xml:定义添加内容的布局
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="240dp"
android:layout_height="wrap_content"
android:orientation="vertical" > <TextView
android:id="@+id/textView1"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="姓名:" /> <EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:layout_marginTop="16dp"
android:hint="username"
android:inputType="textEmailAddress" /> <Button
android:id="@+id/btnAdd"
android:layout_width="match_parent"
android:layout_height="wrap_content" android:text="确定">
</Button> </LinearLayout>
布局效果如下:

运行程序,动态演示效果如下:

图文分解如下:
初始界面为:

单击长按第二个item,会弹出一个菜单:

如果我们选择上图菜单中的“添加”,会弹出一个对话框:

我们在上面的对话框中输入smyhvae,点击“确定”,就会将内容添加到ListView中,并自动更新UI:

如果单击菜单中的“删除”,会直接删除。
注:本文采用的适配器是SimpleCursorAdapter,可以参考老罗的老版视频,采用的是自定义适配器BaseAdapter。
参考链接:http://www.cnblogs.com/plokmju/p/android_Loaders.html
Android之数据存储----使用LoaderManager异步加载数据库的更多相关文章
- Android-LoaderManager异步加载数据库数据
		
LoaderManager异步加载数据库数据,是在(Activity/fragment/其他UI等) 加载大量的本地Database库表数据,由于数据大在加载过程中会导致UI线程阻塞,导致用户体验不好 ...
 - [置顶] Android异步加载数据库和多线程编程
		
Android是一种基于Linux的自由及开放源代码的操作系统,主要使用于移动设备,如智能手机和平板电脑,由Google公司和开放手机联盟领导及开发.尚未有统一中文名称,中国大陆地区较多人使用“安卓” ...
 - ANDROID_MARS学习笔记_S04_009_用java.lang.ref.SoftReference作缓存,android.os.Handler和new Thread异步加载略图片
		
一.简介 二.代码流程 1.private Map<String, SoftReference<Drawable>> imageCache = new HashMap<S ...
 - Android进阶:ListView性能优化异步加载图片 使滑动效果流畅
		
ListView 是一种可以显示一系列项目并能进行滚动显示的 View,每一行的Item可能包含复杂的结构,可能会从网络上获取icon等的一些图标信息,就现在的网络速度要想保持ListView运行的很 ...
 - Cocos2d-js 开发记录:图片数据资源等的异步加载
		
这里说的是在需要的使用加载图片,比如游戏中的某个关卡的图片,不用在游戏一开始就加载(万一用户玩不到那关,岂不是很冤,流量费了那么多),否则载入速度也慢.这种方式加载资源要用到cc.loader官方文档 ...
 - Android 之异步加载LoaderManager
		
LoaderManager: Loader出现的背景: Activity是我们的前端页面展现,数据库是我们的数据持久化地址,那么正常的逻辑就是在展示页面的渲染页面的阶段进行数据库查询.拿到数据以后才展 ...
 - android的progressDialog 的使用。android数据异步加载 对话框提示
		
在调用的Activity中定义一个全局的 progressDialog 点击按钮的时候调用下面这句 progressDialog = ProgressDialog.show(SearchActivit ...
 - Android 异步加载解决方案
		
Android的Lazy Load主要体现在网络数据(图片)异步加载.数据库查询.复杂业务逻辑处理以及费时任务操作导致的异步处理等方面.在介绍Android开发过程中,异步处理这个常见的技术问题之前, ...
 - wemall app商城源码中基于JAVA的Android异步加载图片管理器代码
		
wemall doraemon是Android客户端程序,服务端采用wemall微信商城,不对原商城做任何修改,只需要在原商城目录下上传接口文件即可完成服务端的配置,客户端可随意定制修改.本文分享其中 ...
 
随机推荐
- 选择使用c语言编写的phalcon框架
			
使用这个框架,我总结了如下几点考虑 1.这个框架速度快.纯c语言编写的框架,速度都比php框架快,省去了中间环节.当然,使用它不仅仅是性能考虑.因为如果为了解决php性能问题,完全可以有很多种方式,不 ...
 - 类库LinqToExcel的介绍
			
LinqToExcel是一个.net framework平台下开源项目,它主要实现了LINQ的语法查询Excel电子表格.类型之前的LINQToXXX如果你是LINQ语法糖爱好者那最适 ...
 - Mysql进阶(二)
			
一.触发器 对某个表进行[增/删/改]操作的前后如果希望触发某个特定的行为时,可以使用触发器,触发器用于定制用户对表的行进行[增/删/改]前后的行为. 创建视图 # 插入前CREATE TRIGGER ...
 - Wechat4j之Hello world——使用wechat4j快速开发java版微信公众号
			
Wechat4j是一个开源的java微信开发框架,是目前最简单易用的java微信开发框架. 项目地址:https://github.com/sword-org/wechat4j Wechat4j.ja ...
 - JQuery插件validate的Remote使用
			
JQuery.validate.js 在表单验证中经常使用,初学,对于其中Remote的使用说明一下. 1. 基本解释 JQuery主要用于DOM树和CSS树的检索和后面的操作的一套方法,JQuery ...
 - javascript宿主对象之window.location
			
location属性是一个用来存储当前页面URL信息的对象. 下面我们通过循环来列出location对象的完整属性列表: for(var i in location){ if(typeof locat ...
 - mysql实时同步到mssql的解决方案
			
数据库在应用程序中是必不可少的部分,mysql是开源的,所以很多人它,mssql是微软的,用在windows平台上是非常方便的,所以也有很多人用它.现在问题来了,如何将这两个数据库同步,即数据内容保持 ...
 - Mybatis学习记录(五)----Mybatis的动态SQL
			
1. 什么是动态sql mybatis核心 对sql语句进行灵活操作,通过表达式进行判断,对sql进行灵活拼接.组装. 1.1 需求 用户信息综合查询列表和用户信息查询列表总数这两个statemen ...
 - 2015年第5本(英文第4本):Death on the Nile尼罗河上的惨案
			
书名:Death on the Nile 作者: Agatha Christie 单词数:7.9万(读完后发现网上还有一个版本,总共2.7万单词,孩子都能读懂,看来是简写版) 词汇量:6700 首万词 ...
 - android 浏览器开发实例
			
android app需要通过手机显示网页信息还是比较常用的,比如我最近业余开发的 抢商铺游戏,需要对游戏规则做说明,规则会比较多,而且要经常变动,就想到用网页来展示,更新起来方便,不像应用,一旦发布 ...