zl程序教程

您现在的位置是:首页 >  移动开发

当前栏目

Android之数据存储----使用LoaderManager异步加载数据库

2023-09-14 08:57:57 时间

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:

复制代码
 1 package com.example.loadermanagertest;

 3 import android.annotation.SuppressLint;

 4 import android.app.Activity;

 5 import android.app.AlertDialog;

 6 import android.app.LoaderManager;

 7 import android.app.LoaderManager.LoaderCallbacks;

 8 import android.content.ContentResolver;

 9 import android.content.ContentValues;

 10 import android.content.CursorLoader;

 11 import android.content.Loader;

 12 import android.database.Cursor;

 13 import android.net.Uri;

 14 import android.os.Bundle;

 15 import android.util.Log;

 16 import android.view.ContextMenu;

 17 import android.view.ContextMenu.ContextMenuInfo;

 18 import android.view.LayoutInflater;

 19 import android.view.Menu;

 20 import android.view.MenuInflater;

 21 import android.view.MenuItem;

 22 import android.view.View;

 23 import android.widget.AdapterView.AdapterContextMenuInfo;

 24 import android.widget.Button;

 25 import android.widget.EditText;

 26 import android.widget.ListView;

 27 import android.widget.SimpleCursorAdapter;

 28 import android.widget.TextView;

 30 @SuppressLint("InflateParams") public class MainActivity extends Activity {

 31 private LoaderManager manager;

 32 private ListView listview;

 33 private AlertDialog alertDialog;

 34 private SimpleCursorAdapter mAdapter;

 35 private final String TAG="MainActivity";

 37 @Override

 38 protected void onCreate(Bundle savedInstanceState) {

 39 super.onCreate(savedInstanceState);

 40 setContentView(R.layout.activity_main);

 41 listview = (ListView) findViewById(R.id.listView1);

 42 //使用一个SimpleCursorAdapter,布局使用android自带的布局资源simple_list_item_1, android.R.id.text1 为simple_list_item_1中TextView的Id

 43 mAdapter = new SimpleCursorAdapter(MainActivity.this,

 44 android.R.layout.simple_list_item_1, null,

 45 new String[] { "name" }, new int[] { android.R.id.text1 },0);

 47 // 获取Loader管理器。

 48 manager = getLoaderManager();

 49 // 初始化并启动一个Loader,设定标识为1000,并制定一个回调函数。

 50 manager.initLoader(1000, null, callbacks);

 52 // 为ListView注册一个上下文菜单

 53 registerForContextMenu(listview);

 54 }

 56 @Override

 57 public void onCreateContextMenu(ContextMenu menu, View v,

 58 ContextMenuInfo menuInfo) {

 59 super.onCreateContextMenu(menu, v, menuInfo);

 60 // 声明一个上下文菜单,contentmenu中声明了两个菜单,添加和删除

 61 MenuInflater inflater = getMenuInflater();

 62 inflater.inflate(R.menu.contentmenu, menu);

 63 }

 65 //单击单个的item,弹出菜单选项,让你选择是添加还是删除

 66 @Override

 67 public boolean onContextItemSelected(MenuItem item) {

 69 switch (item.getItemId()) {

 70 //当用户点击菜单中的“添加”选项是,弹出对话框,在对话框里添加name的值

 71 case R.id.menu_add:

 72 // 添加一个对话框

 73 AlertDialog.Builder builder = new AlertDialog.Builder(

 74 MainActivity.this);

 75 // 加载一个自定义布局,add_name中有一个EditText和Button控件。

 76 final View view = LayoutInflater.from(MainActivity.this).inflate(

 77 R.layout.add_name, null);

 78 Button btnAdd = (Button) view.findViewById(R.id.btnAdd);

 79 btnAdd.setOnClickListener(new View.OnClickListener() {

 81 @Override

 82 public void onClick(View v) {

 83 EditText etAdd = (EditText) view

 84 .findViewById(R.id.username);

 85 String name = etAdd.getText().toString();

 86 // 使用ContentResolver进行删除操作,根据name字段。

 87 ContentResolver contentResolver = getContentResolver();

 88 ContentValues contentValues = new ContentValues();

 89 contentValues.put("name", name);

 90 Uri uri = Uri

 91 .parse("content://com.example.loadermanagertest.PersonContentProvider/person");

 92 Uri result = contentResolver.insert(uri, contentValues);

 93 if (result != null) {

 94 //result不为空证明添加成功,重新启动Loader,注意标识需要和之前init的标识一致。

 95 manager.restartLoader(1000, null, callbacks);

 96 }

 97 // 关闭对话框

 98 alertDialog.dismiss();

100 Log.i(TAG, "--- 添加数据成功,name="+name);

101 }

102 });

103 builder.setView(view);

104 alertDialog = builder.show();

105 return true;

106 case R.id.menu_delete:

107 // 获取菜单选项的信息

108 AdapterContextMenuInfo info = (AdapterContextMenuInfo) item

109 .getMenuInfo();

110 // 获取到选项的TextView控件,并得到选中项的内容

111 TextView tv = (TextView) info.targetView;

112 String name = tv.getText().toString();

113 // 使用ContentResolver进行删除操作

114 Uri url = Uri

115 .parse("content://com.example.loadermanagertest.PersonContentProvider/person");

116 ContentResolver contentResolver = getContentResolver();

117 String where = "name=?";

118 String[] selectionArgs = { name };

119 int count = contentResolver.delete(url, where, selectionArgs);

120 if (count == 1) {

121 //这个操作仅删除单条记录,如果删除行为1 ,则重新启动Loader

122 manager.restartLoader(1000, null, callbacks);

123 }

124 Log.i(TAG, "--- 删除数据成功,name="+name);

125 return true;

126 default:

127 return super.onContextItemSelected(item);

128 }

130 }

132 // Loader的回调接口,在这里异步加载数据库的内容,显示在ListView上,同时能够自动更新

133 private LoaderManager.LoaderCallbacks Cursor callbacks = new LoaderCallbacks Cursor () {

135 @Override

136 public Loader Cursor onCreateLoader(int id, Bundle bundle) {

137 // 在Loader创建的时候被调用,这里使用一个ContentProvider获取数据,所以使用CursorLoader返回数据

138 Uri uri = Uri

139 .parse("content://com.example.loadermanagertest.PersonContentProvider/person");

140 CursorLoader loader = new CursorLoader(MainActivity.this, uri,

141 null, null, null, null);

142 Log.i(TAG, "--- onCreateLoader被执行。");

143 return loader;

144 }

146 //完成对UI的数据提取,更新UI

147 @Override

148 public void onLoadFinished(Loader Cursor loader, Cursor cursor) {

149 //把数据提取出来,放到适配器中完成对UI的更新操作(刷新SimpleCursorAdapter的数据)

150 mAdapter.swapCursor(cursor);

151 // 为ListView绑定适配器

152 listview.setAdapter(mAdapter);

153 Log.i(TAG, "--- onLoadFinished被执行。");

154 }

156 @Override

157 public void onLoaderReset(Loader Cursor loader) {

158 // 当Loader被从LoaderManager中移除的时候,被执行,清空SimpleCursorAdapter适配器的Cursor

159 mAdapter.swapCursor(null);

160 Log.i(TAG, "--- onLoaderReset被执行。");

161 }

162 };

164 @Override

165 public boolean onCreateOptionsMenu(Menu menu) {

166 getMenuInflater().inflate(R.menu.main, menu);

167 return true;

168 }

170 }
复制代码

核心代码: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:定义添加内容的布局

复制代码
 1 ?xml version="1.0" encoding="utf-8"? 

 2 LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"

 3 android:layout_width="240dp"

 4 android:layout_height="wrap_content"

 5 android:orientation="vertical" 

 7 TextView

 8 android:id="@+id/textView1"

 9 android:layout_width="wrap_content"

10 android:layout_height="wrap_content"

11 android:text="姓名:" / 

13 EditText

14 android:id="@+id/username"

15 android:layout_width="match_parent"

16 android:layout_height="wrap_content"

17 android:layout_marginBottom="4dp"

18 android:layout_marginLeft="4dp"

19 android:layout_marginRight="4dp"

20 android:layout_marginTop="16dp"

21 android:hint="username"

22 android:inputType="textEmailAddress" / 

24 Button

25 android:id="@+id/btnAdd"

26 android:layout_width="match_parent"

27 android:layout_height="wrap_content" android:text="确定" 

28 /Button 

30 /LinearLayout 
复制代码

布局效果如下:

8ead0ac2-df0c-4efd-9403-5da9f016959e

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

 

图文分解如下:

初始界面为:

71bab64b-aafe-445d-903d-8bf9c2feae7d

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

0b7707d4-c959-44e6-8676-b4c45f25d342

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

831b74ca-9828-450f-9ce9-fb75b07e16f9

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

40974acd-1a9b-4ad6-a782-c23c477ec643

如果单击菜单中的“删除”,会直接删除。

注:本文采用的适配器是SimpleCursorAdapter,可以参考老罗的老版视频,采用的是自定义适配器BaseAdapter。


转载请标明出处:http://blog.csdn.net/zhaoyanjun6/article/details/70259914 本文出自【赵彦军的博客】
Android Loader 异步加载详解一:基础概念 Android Loader 异步加载详解二:探寻Loader内部机制 在上一篇文章中,讲解了 Loader 的基本概念。
Android异步加载全解析之IntentService Android异步加载全解析之IntentService 搞什么IntentService 前面我们说了那么多,异步处理都使用钦定的AsyncTask,再不济也使用的Thread,那么这个IntentService是个什么鬼。
Android异步加载全解析之引入二级缓存 Android异步加载全解析之引入二级缓存 为啥要二级缓存 前面我们有了一级缓存,为啥还要二级缓存呢?说白了,这就和电脑是一样的,我们电脑有内存和硬盘,内存读取速度快,所以CPU直接读取内存中的数据,但是,内存资源有限,所以我们可以把数据保存到硬盘上,这就是二级缓存,硬盘虽然读取速度慢,但是人家容量大。
Android异步加载全解析之引入一级缓存 Android异步加载全解析之引入缓存 为啥要缓存 通过对图像的缩放,我们做到了对大图的异步加载优化,但是现在的App不仅是高清大图,更是高清多图,动不动就是图文混排,以图代文,如果这些图片都加载到内存中,必定会OOM。
Android异步加载全解析之大图处理 Android异步加载全解析之大图处理 异步加载中非常重要的一部分就是对图像的处理,这也是我们前面用异步加载图像做演示例子的原因。一方面是因为图像处理不好的话会非常占内存,而且容易OOM,另一方面,图像也比文字要大,加载比较慢。
生命壹号 个人网站:smyhvae.com。博客园:cnblogs.com/smyhvae。微信公众号:生命团队 | vitateam