Android 之异步加载LoaderManager
LoaderManager:
Loader出现的背景:
Activity是我们的前端页面展现,数据库是我们的数据持久化地址,那么正常的逻辑就是在展示页面的渲染页面的阶段进行数据库查询。拿到数据以后才展示页面。但是这个逻辑有一些缺点:
首先是查询数据的逻辑放在了UI生成的同个线程中,这个就意味着在查询数据的时候,UI页面生成的工作被阻塞住了。UI一旦被阻塞用户就会被感知出来了,因此就会出现各种无相应页面(Application Not Response),或者activity页面延迟的现象,这对用户体验来说是不可接受的。
其次是在渲染页面的时候需要固定需要进行一次数据查询,但是这个是很不节省资源的。假如一个Activity从一个停止状态回到前台,那么这个时候尽管数据并没有变化,但是也需要进行一次query操作。在浪费资源的同时也再次增加了页面渲染失败的风险。
还有就是当数据变化的时候如何通知页面进行修改呢?这个时候往往就又要创建一个monitor的角色,来当数据源变化的时候来让页面重新调用requery。
因此在Android的越来越提倡用户体验的今天,加载器和加载管理器(Loader,LoaderManager)就出现了。
在Android3.0中,Google引入了一种数据异步加载机制,该机制的核心,便是LoaderManager、Loader,顾名思义,LoaderManager是Loader的管理者,而Loader便是数据加载器,你可以根据自己的需要实现形形色色的数据加载器。
Google强烈建议在加载数据时,使用LoaderManager及其相关的机制。
每个Activity和Fragment中,都会有且只有一个LoaderManager,而LoaderManager中可以有多个Loader,也就是说,在一个Activity或者Fragment中,你可以同时异步加载N则不同的数据,具体加多少则,要看你那一亩三分地(Activity和Fragment就是你的地)有多大产。
Loaders机制在Android 3.0版本后引入。Loaders机制使一个Activity或者一个Fragment更加容易异步加载数据。Loaders有如下的特性:
Ø 它们适用于任何Activity和Fragment;
Ø 它们提供了异步加载数据的机制;
Ø 它们检测数据源,当数据源内容改变时它们能够传递新的结果;
Ø 当配置改变后需要重新创建时,它们会重新连接到最后一个loader的游标。这样,它们不需要重新查询它们的数据。
api
|
描述 | |
LoaderManager |
一个与Activity和Fragment有关联的抽象类,用于管理一个或多个Loader实例。这有助于app管理长运行操作。 使用它的最显著的例子是CursorLoader。每个Activity或Fragment只能有一个LoaderManager。 而一个LoaderManager可以有多个loaders。 |
|
LoaderManager.LoaderCallbacks |
提供给客户端的一个callback接口,用于和LoaderManager进行交互。 例如,你可以使用onCreateLoader() callback来创建一个新的loader。 |
|
AsyncTaskLoader | 一个抽象Loader,提供一个AsyncTask进行工作。 | |
CursorLoader |
AsyncTaskLoader的子类,用于向ContentResover请求,返回一个Cursor。 这个类以标准的游标查询方式实现了Loader协议,建立了AsyncTaskLoader,使用一个后台线程来进行游标查询,不会阻塞app的UI。 因此,使用这个loader是从ContentProvider加载异步数据的最好的方式。 |
如何在Activity中使用Loader?
1.声明一个LoaderManager的实例。并在oncreate方法中实例化
2. 在oncreate方法中启动一个loader
(initLoader(0,null, this);参数分别表示:
Ø 一个标志loader的唯一ID。
Ø 提供给loader构造函数的参数,可选。
Ø 一个LoaderManager.LoaderCallbacks的实现。该回调参数不能为空)
3.实现回调:一个LoaderManager.LoaderCallbacks的实现。在这你创建新的loader,和管理已经存在的loaders。
onCreateLoader方法中完成一个CursorLoader的创建,需要从一个ContentProvider里加载数据。
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// TODO Auto-generated method stub
/*CursorLoader loader=new CursorLoader(MainActivity.this);
Uri uri = Uri
.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
loader.setUri(uri);*/
Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
CursorLoader loader=new CursorLoader(MainActivity.this, uri, null, null, null, null);
return loader;
}
onLoadFinished方法完成提取 放到适配器(自定义)中 更新UI
public class myAdapter extends BaseAdapter{ private Context context;
private List<String> list;
private myAdapter(){ }
private myAdapter(Context context){
this.context=context;
} private void setData(List<String> list){
this.list=list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
} @Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
} @Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
TextView view=null;
if(convertView==null){
view=new TextView(context);
}
else
view=(TextView)convertView;
view.setText(list.get(position));
return view;
} }
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// TODO Auto-generated method stub
List<String> list=new ArrayList<String>();
while(cursor.moveToNext()){
String name=cursor.getString(cursor.getColumnIndex("name"));
list.add(name);
}
myAdapter adapter=new myAdapter(MainActivity.this);
adapter.setData(list);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
这样就可以异步加载数据库中的数据了,接下来需要借助上下文菜单ContextMenu给listview中的选项添加两个操作:添加和删除
首先在activity的oncreate方法中为listView注册一个上下文菜单:registerForContextMenu(listView);
第二步:重写onCreateContextMenu方法处理上下文菜单,需要定义一个菜单,也可编辑使用menu菜单下已有的菜单main.xml,加载菜单布局
第三步:重写onMenuItemSelected方法,其中需要自定义对话框(还要自定义一个布局)处理用户的选择
最终源码:
项目目录:
MainActivity.java
package com.example.android_07loader_manager; import java.util.ArrayList;
import java.util.List; import android.app.Activity;
import android.app.Dialog;
import android.app.LoaderManager;
import android.app.LoaderManager.LoaderCallbacks;
import android.content.ContentResolver;
import android.content.ContentValues;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.AdapterView.AdapterContextMenuInfo; public class MainActivity extends Activity { private LoaderManager manager;
private ListView listView;
myAdapter adapter=null;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=(ListView)this.findViewById(R.id.listView1);
adapter=new myAdapter(MainActivity.this);
manager=getLoaderManager();//LoaderManager已经集成在activity中,用来完成异步加载
manager.initLoader(, null, callback);//启动loader
registerForContextMenu(listView);
} private LoaderManager.LoaderCallbacks<Cursor> callback=new LoaderCallbacks<Cursor>() { @Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// TODO Auto-generated method stub
/*CursorLoader loader=new CursorLoader(MainActivity.this);
Uri uri = Uri
.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
loader.setUri(uri);*/
Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
CursorLoader loader=new CursorLoader(MainActivity.this, uri, null, null, null, null);
return loader;
} //完成提取 放到适配器中 更新UI
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
// TODO Auto-generated method stub
List<String> list=new ArrayList<String>();
while(cursor.moveToNext()){
String name=cursor.getString(cursor.getColumnIndex("name"));
list.add(name);
} adapter.setData(list);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
} @Override
public void onLoaderReset(Loader<Cursor> loader) {
// TODO Auto-generated method stub }
};
public class myAdapter extends BaseAdapter{ private Context context;
private List<String> list;
private myAdapter(){ }
private myAdapter(Context context){
this.context=context;
} private void setData(List<String> list){
this.list=list;
}
@Override
public int getCount() {
// TODO Auto-generated method stub
return list.size();
} @Override
public Object getItem(int position) {
// TODO Auto-generated method stub
return list.get(position);
} @Override
public long getItemId(int position) {
// TODO Auto-generated method stub
return position;
} @Override
public View getView(int position, View convertView, ViewGroup parent) {
// TODO Auto-generated method stub
TextView view=null;
if(convertView==null){
view=new TextView(context);
}
else
view=(TextView)convertView;
view.setText(list.get(position));
return view;
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
} @Override
public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) {
// TODO Auto-generated method stub
super.onCreateContextMenu(menu, v, menuInfo);
getMenuInflater().inflate(R.menu.main, menu);
}
@Override
public boolean onMenuItemSelected(int featureId, MenuItem item) {
// TODO Auto-generated method stub
switch (item.getItemId()) {
case R.id.add:
//Toast.makeText(MainActivity.this, "add", 1).show();
final Dialog dialog=createAddDialog(MainActivity.this);
Button button1=(Button)dialog.findViewById(R.id.button1);
button1.setOnClickListener(new View.OnClickListener() { @Override
public void onClick(View v) {
// TODO Auto-generated method stub
final EditText editText=(EditText)dialog.findViewById(R.id.editText1);
String name=editText.toString();
ContentResolver resolver=getContentResolver();
Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
ContentValues values = new ContentValues();
values.put("name", name);
Uri res_uri=resolver.insert(uri, values);
if(res_uri!=null)//添加成功的时候 刷新数据
{
manager.restartLoader(, null, callback);//重新启动查询器
dialog.dismiss();
}
}
});
dialog.show();
break; case R.id.del:
//AdapterContextMenuInfo对象包含了对话框选项的所有信息 AdapterContextMenuInfo into=item.getMenuInfo();
AdapterContextMenuInfo info=(AdapterContextMenuInfo) item.getMenuInfo();
ContentResolver resolver = getContentResolver();
Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
int position=info.position;
String name=adapter.getItem(position).toString();
int count = resolver.delete(uri, "name=?", new String[]{name});
//Toast.makeText(MainActivity.this, "del", 1).show();
if(count>){
manager.restartLoader(, null, callback);//重新启动查询器
} break;
}
return super.onMenuItemSelected(featureId, item);
} public Dialog createAddDialog(Context context){
Dialog dialog=new Dialog(context);
dialog.setContentView(R.layout.add);
return dialog; }
/* @Override
public boolean onOptionsItemSelected(MenuItem item) { int id = item.getItemId();
if (id == R.id.action_settings) {
return true;
} return super.onOptionsItemSelected(item);
}*/
}
MyTest.java
package com.example.android_07loader_manager; import android.content.ContentResolver;
import android.content.ContentUris;
import android.content.ContentValues;
import android.database.Cursor;
import android.net.Uri;
import android.test.AndroidTestCase; public class MyTest extends AndroidTestCase { public MyTest() {
// TODO Auto-generated constructor stub
} public void add() {
ContentResolver contentResolver = getContext().getContentResolver();
ContentValues values = new ContentValues();
values.put("name", "mlj");
Uri url = Uri
.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
contentResolver.insert(url, values);
} public void delete() {
ContentResolver contentResolver = getContext().getContentResolver(); Uri url = Uri
.parse("content://com.example.android_07loader_manager.StudentContentProvider/student/1");
int count = contentResolver.delete(url, null, null);
System.out.println("---count-->>" + count);
} public void query() {
ContentResolver contentResolver = getContext().getContentResolver();
Uri url = Uri
.parse("content://com.example.android_07loader_manager.StudentContentProvider/student");
Uri uri = ContentUris.withAppendedId(url, ); Cursor cursor = contentResolver.query(uri, null, null, null, null);
while (cursor.moveToNext()) {
System.out.println("--->>"
+ cursor.getString(cursor.getColumnIndex("name")));
}
}
}
StudentContentProvider.java
package com.example.android_07loader_manager; import com.example.android_07loader_manager.dbhelp.DBHelper; import android.R.integer;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri; public class StudentContentProvider extends ContentProvider { private final static UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
private final static int STUDENT = ;
private final static int STUDENTS = ;
private DBHelper helper;
static {
URI_MATCHER.addURI(
"com.example.android_07loader_manager.StudentContentProvider","student", STUDENTS);
URI_MATCHER.addURI(
"com.example.android_07loader_manager.StudentContentProvider","student/#", STUDENT);
} public StudentContentProvider() {
// TODO Auto-generated constructor stub
} @Override
public int delete(Uri uri, String selection, String[] selectionArgs) {
// TODO Auto-generated method stub
int count = ;// 影响数据库的行数
int flag = URI_MATCHER.match(uri);
SQLiteDatabase database = helper.getWritableDatabase(); switch (flag) {
case STUDENT:
long stuid = ContentUris.parseId(uri);
String where_value = " stuid = " + stuid;
if (selection != null && !selection.equals("")) {
where_value += selection;
}
count = database.delete("student", where_value, selectionArgs);
break; case STUDENTS:
count = database.delete("student", selection, selectionArgs);
break;
}
return count;
} @Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STUDENT:
return "vnd.android.cursor.item/student";
case STUDENTS:
return "vnd.android.cursor.dir/studens";
}
return null;
} @Override
public Uri insert(Uri uri, ContentValues contentValues) {
// TODO Auto-generated method stub
int flag = URI_MATCHER.match(uri);
SQLiteDatabase database = helper.getWritableDatabase();
Uri uri2 = null;
switch (flag) {
case STUDENTS:
long id = database.insert("student", null, contentValues);
uri2 = ContentUris.withAppendedId(uri, id);
break;
}
System.out.println("-->>" + uri2.toString());
return uri2;
} @Override
public boolean onCreate() {
// TODO Auto-generated method stub
helper = new DBHelper(getContext());
return false;
} @Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
// TODO Auto-generated method stub
Cursor cursor = null;
int flag = URI_MATCHER.match(uri);
SQLiteDatabase database = helper.getReadableDatabase();
switch (flag) {
case STUDENT:
long stuid = ContentUris.parseId(uri);
String where_value = " stuid = " + stuid;
if (selection != null && !"".equals(selection)) {
where_value += selection;
}
cursor = database.query("student", projection, where_value,
selectionArgs, null, null, null); break; case STUDENTS:
cursor = database.query("student", projection, selection,
selectionArgs, null, null, null);
break;
}
return cursor;
} @Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
// TODO Auto-generated method stub
int count = ;
int flag = URI_MATCHER.match(uri);
SQLiteDatabase database = helper.getWritableDatabase();
switch (flag) {
case STUDENT:
long stuid = ContentUris.parseId(uri);
String where_value = " stuid = " + stuid;
if (selection != null && !selection.equals("")) {
where_value += selection;
}
count = database.update("student", values, where_value,
selectionArgs);
break; case STUDENTS:
count = database
.update("student", values, selection, selectionArgs);
break;
}
return count;
} }
DBHelper.java
package com.example.android_07loader_manager.dbhelp; import android.content.Context;
import android.database.DatabaseErrorHandler;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper; public class DBHelper extends SQLiteOpenHelper{ private static String name = "mydb.db";
private static int version = ; public DBHelper(Context context) {
super(context, name, null, version);
// TODO Auto-generated constructor stub
} public DBHelper(Context context, String name, CursorFactory factory,
int version, DatabaseErrorHandler errorHandler) {
super(context, name, factory, version, errorHandler);
// TODO Auto-generated constructor stub
} @Override
public void onCreate(SQLiteDatabase db) {
// TODO Auto-generated method stub
String sql = "create table student (stuid integer primary key autoincrement,name varchar(64))";
db.execSQL(sql);
} @Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
// TODO Auto-generated method stub } }
Google倒是提供了一个标准的Loader,即CursorLoader,它是Loader的标准实现,如果你的数据能够用Cursor表示,比如来自SQLiteDatabase的数据就是标准的Cursor,那么这个类对你而言就够用了,具体如何使用CursorLoader,请参看如下例子:
首先添加单元测试授权和包
第二步建内容提供者类
1.DBHelper extends SQLiteOpenHelper
2.class StudentContentProvider extends ContentProvider
2.1 初始化操作:匹配器,标识位,DBHelper声明及实例化,匹配规则,getType ,注册内容提供者
匹配器:private final static UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH);
标识位private final static int STUDENT = 1; private final static int STUDENTS = 2;
初始化sqliteDatabase示例并在oncreate方法中初始化;
private DBHelper helper;
@Override
public boolean onCreate() {
// TODO Auto-generated method stub
helper = new DBHelper(getContext());
return false;
}
匹配规则(静态代码块)
static {
URI_MATCHER.addURI(
"com.example.android_07loader_manager.StudentContentProvider","student", STUDENTS);
URI_MATCHER.addURI(
"com.example.android_07loader_manager.StudentContentProvider","student/#", STUDENT);
}
重写getType方法
@Override
public String getType(Uri uri) {
// TODO Auto-generated method stub
int flag = URI_MATCHER.match(uri);
switch (flag) {
case STUDENT:
return "vnd.android.cursor.item/student";
case STUDENTS:
return "vnd.android.cursor.dir/studens";
}
return null;
}
.在清单文件中注册内容提供者: <provider android:name=".StudentContentProvider" android:authorities="com.example.android_07loader_manager.StudentContentProvider"></provider>
2.2 完成增删改查
2.3建测试类
第三步:
Android 之异步加载LoaderManager的更多相关文章
- [Android] Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.LoaderCallbacks)
Android 用于异步加载 ContentProvider 中的内容的机制 -- Loader 机制 (LoaderManager + CursorLoader + LoaderManager.Lo ...
- Android 图片异步加载的体会,SoftReference已经不再适用
在网络上搜索Android图片异步加载的相关文章,目前大部分提到的解决方案,都是采用Map<String, SoftReference<Drawable>> 这样软引用的 ...
- Android图片异步加载之Android-Universal-Image-Loader
将近一个月没有更新博客了,由于这段时间以来准备毕业论文等各种事务缠身,一直没有时间和精力沉下来继续学习和整理一些东西.最近刚刚恢复到正轨,正好这两天看了下Android上关于图片异步加载的开源项目,就 ...
- Android图片异步加载之Android-Universal-Image-Loader(转)
今天要介绍的是Github上一个使用非常广泛的图片异步加载库Android-Universal-Image-Loader,该项目的功能十分强大,可以说是我见过的目前功能最全.性能最优的图片异步加载解决 ...
- android 网络异步加载数据进度条
ProgressDialog progressDialog = null; public static final int MESSAGETYPE = 0; private void execute( ...
- Android开发--异步加载
因为移动端软件开发思维模式或者说是开发的架构其实是不分平台和编程语言的,就拿安卓和IOS来说,他们都是移动前端app开发展示数据和用户交互数据的数据终端,移动架构的几个大模块:UI界面展示.本地数据可 ...
- Android图片异步加载框架Android-Universal-Image-Loader
版权声明:本文为博主原创文章,未经博主允许不得转载. Android-Universal-Image-Loader是一个图片异步加载,缓存和显示的框架.这个框架已经被很多开发者所使用,是最常用的几个 ...
- Android 多线程 异步加载
Android 应用中需要显示网络图片时,图片的加载过程较为耗时,因此加载过程使用线程池进行管理, 同时使用本地缓存保存图片(当来回滚动ListView时,调用缓存的图片),这样加载和显示图片较为友好 ...
- android ListView异步加载图片(双缓存)
首先声明,参考博客地址:http://www.iteye.com/topic/685986 对于ListView,相信很多人都很熟悉,因为确实太常见了,所以,做的用户体验更好,就成了我们的追求... ...
随机推荐
- Apache Storm 1.1.0 中文文档 | ApacheCN
前言 Apache Storm 是一个免费的,开源的,分布式的实时计算系统. 官方文档: http://storm.apache.org 中文文档: http://storm.apachecn.org ...
- 用notepad如何在每行结尾处添加特殊字符
在处理关键词的时候,有时候需要将每一行的末尾添加某个特殊字符,较常用的一种方法就是用excel拼接起来.今天要分享是一种简单的方法,用notepad就可以很容易实现,主要用到notepad中的扩展匹配 ...
- Django配置mysql
我的环境为windows10+pyhton3.6+Django1.11.4 由于mysqldb不支持python3,所以django连接MySQL就不能再使用mysqldb了.故而选择了mysqlcl ...
- Html5笔记之第四天
属性 值 描述 accesskey character 规定访问元素的键盘快捷键 class classname 规定元素的类名(用于规定样式表中的类). contenteditable true f ...
- java IO输入输出流实现文本复制
- carry-检查数据接口返回数据合法性
问题背景: 在测试&部署监控过程中,我们常常会遇到外部接口返回数据不靠谱的时候.最常见的场合是从某个http获取如json和xml等结构化的结果,进行解析并处理,在这时候出现以下这几种常见类型 ...
- Maven项目下update maven后Eclipse报错:java.lang.ClassNotFoundException: ContextLoaderL
Maven项目下update maven后Eclipse报错:java.lang.ClassNotFoundException: ContextLoaderL 严重: Error config ...
- WPF DEV实现手风琴效果
说明 最近用WPF+DevExpress做项目时,需要做一个类似手风琴的效果,效果的界面如下.因为没有现成的控件,需要自定义模板,所以写了一个Demo和大家分享,项目中可以根据实际情况使用.如果你用不 ...
- 读取Execl表 导入数据库
不知不觉博客园园林都两年多了,我是今年毕业的应届生,最近公司项目需要改动,很多的数据需要导入,很多的实体类需要些.考虑到这些问题自己写了两个winform版的小工具,一个是读取Execl数据导入数据库 ...
- positio:absolute与position:relative的区别
absolute 能让元素 inline-block 化:例如一个div标签默认宽度是100%显示的,但是一旦被absolute属性缠上,则100%默认宽度就会变成自适应内部元素的宽度. float ...