Android 之异步加载LoaderManager

时间:2022-10-09 09:05:03

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

Class/Interface

描述
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方法,其中需要自定义对话框(还要自定义一个布局)处理用户的选择

最终源码:

项目目录:

Android 之异步加载LoaderManager

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建测试类 

第三步: