Loaders(装载器)
1.Loader API 概述
从Android 3.0开始,Android引入loaders功能, loaders提供 了在activity和fragment中异步载入数据以及监视数据源的变化的能力。Loaders的特性如下:
- 支持Activity和Fragment
- 异步下载
- 当数据源改变时能及时通知客户端
- 发生configuration change时自动重连接
在应用程序中使用Loaders可能会用到–些类和接口:
(1) LoaderManager :
一个抽像类,关联到一个Activity或Fragment,管理一个或多个装载器的实例。这帮助一个应用管理那些与Activity或Fragment的生命周期相关的长时间运行的的操作。最常见的方式是与一个CursorLoader一起使用,然而应用是可以随便写它们自己的装载器以加载其它类型的数据。每个activity或fragment只有一个LoaderManager.但是-个LoaderManager 可以拥有多个装载器。
(2) LoaderManager.LoaderCallbacks :
一个用于客户端与LoaderManager交互的回调接口。例如,你使用回调方法onCreateLoader()来创建一 个新的装载器。
(3) Loader :
一个执行异步数据加载的抽象类。它是加载器的基类。你可以使用典型的CursorLoader,但是你也可以实现你自己的子类。一旦装载器被激活,它们将监视它们的数据源并且在数据改变时发送新的结果。
(4) AsyntTaskLoader :
提供一个AsyncTask来执行异步加载工作的抽象类。
(5) CursorLoader :
AsyncTaskLoader的子类,它查询ContentResolver然后返回一个Cursor。这个类为查询cursor以标准的方式实现了装载器的协议,它的游标查询是通过AsyncTaskLoader在后台线程中执行,从而不会阻塞界面。使用这个装载器是从一个 ContentProvider异步加载数据的最好方式。相比之下,通过fragment或activity的API来执行一个被管理的查询就不行了。
2.启动一个装载器
LoaderManager管理一个Activity或Fragment中的一个或多个装载器.但每个activity或fragment只拥有一个LoaderManager。你通常要在activity的onCreate()方法中或fragment的onActivityCreated()方法中初始化一个装载器,你可以如下创建:
//准备装载器 可以重连一个已经存在的也可以启动一个新的。getLoaderManager().initLoader(0,null, this);
initLoader()方法有以下参数:
- 一个唯一ID来标志装载器,
- 可选的装载器初始化参数
- .一个LoaderManager LoaderCallbacks的实现、被LoaderManager调用以报告装 载器的事件,在这个例子中,类本实现了这个接口,所以是this
initLoader()保证一个装载器被初始化并激活、它具有两种可能的结果:
- 如果ID所指的装载器已经存在,那么这个装载器将被重用
- .如果装载器不存在,initLoader()就触发LoaderManager.LoaderCallbacks的方法onCreateLoader()。这是你实例化并返回一个新装载器的地方。
注意
initLoader()返回所创建的装载器,但是你不需保存-个对它的引用,LoaderManager自动管理装载器的生命。LoaderManager会在需要时开始和停止装载动作,并且维护装载器的状态和它所关联的内容,这意味着,你很少与装载器直接交互,你通常都是使用LoaderManager LoaderCallbacks的方法们在某个事件发生时介入到数据加载的过程中
3.重启装载器
当你使用initloader()时,如果指定ID的装载器已经存在,则它使用这个装载器。如果不存在呢,它将创建一个新的。但是有时你却是想丟弃旧的然后开始新的数据。
要想丟弃旧数据,你应使用restartLoader()。例如,下面这个SearchView. OnQueryTextListener的实现在用户查询发生改变时重启了装载器,装载器于是需重启从而能使用新的搜索过虑来进行-次新的查询:public boolean onQueryTextChanged(String newText) {
//当动作栏的搜索字串发生改时被调用. ,
//更新搜索过虑,然后重新启动装载利用这个新过虑进行新的查询。
mCurFilter = !TextUtils,isEmpty(newText) ? newText : null;
getLoaderManager).restartLoader(O, noll, this);
return true;
}
4.使用LoaderManager的回调
LoaderManager.LoaderCallbacks是一个回调接口,它使得客户端可以与LoaderManager进行交互。
装载器一般指的是CursorLoader,我们希望在它停止后依然保持数据,这使得应用可以在activity或fragment的onStop() 和onStart()之间保持数据,所以当用户回到一个应用时,它们不需等待数据加载。你使用LoaderManager.LoaderCalbacks的方法,在需要时创建新的装载器,并且告诉应用什么时候要停止使用装载器的数据。
LoaderManager.LoaderCallbacks包含以下方法:
- onCreateLoader() 根据传入的ID,初始化并返回一个新的装载器
- onLoadFinished() 当一个装载器完成了它的装载过程后被调用
- onLoaderReset() 当一个装载器被重置而什其数据无效时被调用
onLoaderReset
当一个已创建的装载器被重置从而使其数据无效时,此方法被调用.此回调使你能发现什么时候数据将被釋放于是你可以釋放对它的引用.
下面这个实现调用参数为null的swapCursor() :
//这个Adapter被用于显示列表的数据.
SimpleCursorAdapter mAdapter;
public void onLoaderReset(Loader < Cursor> loader) {
//此处是用于上面的onloadFinished()的游标将被关闭时执行,我们需确保我们不再使用它。
mAdapter. swapCursor(nll);
5.CursorLoader
在下面的例子中,回调方法onCreateLoader[) 创建一个CursorLoader,你必须使用构造方法来建立CursorLoader,构造方法需要向ContentProvider执行一次查询的完整信息作为参数,它需要以下参数:
- context 上下文对象
- uri一要获取的内容的URI
- projection 要返回的列组成的列被.传入null 将会返回所有的列,但这是低效的
- selection 一个过滤器, 表明哪些行将被返回.格式化成类USQLWHERE语句的样子(除了没有WHERE).传入null将返回所有的行
- selectionArgs 你可以在selection中包含-
-些’?’,它将被本参数的值替换掉,这些值出现的顺序与’?"在selection中出现的顺序一至,值将作为字符串 - sortOrder 如何为行们排序,格式化成类似于SQLORDER BY语句的样字(除了没有OREDERBY),传入pull将使用默认顺序,默认顺序可能是无顺序
最后在onCreateLoader方法中返回CursorLoader对象
具体示例代码如下
PersonMetadata
package com.example.loaders;
import android.provider.BaseColumns;
//数据库常量类
public class PersonMetadata {
public static abstract class Person implements BaseColumns {
public static final String TABLE_NAME="person";
public static final String NAME="name";
public static final String AGE="age";
}
}
Person
package com.example.loaders;
//实体类
public class Person {
private int id;
private String name;
private int age;
public Person(int id, String name, int age) {
this.id = id;
this.name = name;
this.age = age;
}
public Person( String name, int age) {
this.name = name;
this.age = age;
}
public Person() {
}
public int getId() {
return id;
}
public void setId(int id) {
this.id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public int getAge() {
return age;
}
public void setAge(int age) {
this.age = age;
}
@Override
public String toString() {
return "Person{"+
"id="+id+
",name='"+name+"\'"+
",age='"+age+"\'"+
"}";
}
}
DatabaseAdapter
package com.example.loaders;
import android.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
import java.util.ArrayList;
//创建数据库类
public class DatabaseAdapter {
private DatabaseHelper databaseHelper;
public DatabaseAdapter(Context context){
databaseHelper=new DatabaseHelper(context);
}
public void save(Person person){
SQLiteDatabase db=databaseHelper.getWritableDatabase();
ContentValues values=new ContentValues();
values.put(PersonMetadata.Person.NAME,person.getName());
values.put(PersonMetadata.Person.AGE,person.getAge());
db.insert(PersonMetadata.Person.TABLE_NAME,null,values);
db.close();
}
public void delete(int id){
SQLiteDatabase database=databaseHelper.getWritableDatabase();
String sql="delete from person where _id=?";
Object[] args={id};
database.execSQL(sql,args);
database.close();
}
public void update(Person person){
SQLiteDatabase database=databaseHelper.getWritableDatabase();
String sql="update person set name=?,age=? where _id=?";
Object[] args={person.getName(),person.getAge(),person.getId()};
database.execSQL(sql,args);
database.close();
}
public ArrayList<Person> findAll(){
SQLiteDatabase database=databaseHelper.getReadableDatabase();
String sql="select _id,name,age from person";
Cursor c=database.rawQuery(sql,null);
ArrayList<Person> people=new ArrayList<>();
Person person=null;
while (c.moveToNext()){
person=new Person();
person.setId(c.getInt(c.getColumnIndexOrThrow(PersonMetadata.Person._ID)));
person.setName(c.getString(c.getColumnIndexOrThrow(PersonMetadata.Person.NAME)));
person.setAge(c.getInt(c.getColumnIndexOrThrow(PersonMetadata.Person.AGE)));
people.add(person);
}
c.close();
database.close();
return people;
}
public Person findById(int id){
SQLiteDatabase database=databaseHelper.getReadableDatabase();
String sql="select _id,name,age from person where _id=?";
Cursor c=database.rawQuery(sql,new String[]{String.valueOf(id)});
Person person=null;
if (c.moveToNext()){
person=new Person();
person.setId(c.getInt(c.getColumnIndexOrThrow(PersonMetadata.Person._ID)));
person.setName(c.getString(c.getColumnIndexOrThrow(PersonMetadata.Person.NAME)));
person.setAge(c.getInt(c.getColumnIndexOrThrow(PersonMetadata.Person.AGE)));
}
c.close();
database.close();
return person;
}
public Cursor list(){
SQLiteDatabase database=databaseHelper.getReadableDatabase();
Cursor c=database.query(PersonMetadata.Person.TABLE_NAME,null, null,null,null,null,null);
return c;
}
public static class DatabaseHelper extends SQLiteOpenHelper{
private static final String DB_NAME="cp.db";
private static final int VERSION=1;
private static final String create_table="create table person(_id integer primary key autoincrement,"+
"name text,age integer)";
private static final String drop_tabel="drop table if exists person";
public DatabaseHelper(Context context) {
super(context, DB_NAME, null, VERSION);
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(create_table);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
db.execSQL(drop_tabel);
db.execSQL(create_table);
}
}
}
PersonContentProvider
package com.example.loaders;
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;
//自定义ContentProvider
public class PersonContentProvider extends ContentProvider {
private static final String authority="com.example.loaders.personcontentprovider";
//创建一个Uri的匹配器
private static UriMatcher uriMatcher=new UriMatcher(UriMatcher.NO_MATCH);
//定义匹配码
private static final int single_code=2;//返回单个记录的匹配码
private static final int multiple_code=1;//返回多个记录的匹配码
//定义两种不同的类型 text/plain image/jpg
private static final String single_type="vnd.android.cursor.item/person";
private static final String multiple_type="vnd.android.cursor.dir/person";
static {
uriMatcher.addURI(authority,"person",1);
uriMatcher.addURI(authority,"person/#",2);
}
private DatabaseAdapter.DatabaseHelper databaseHelper;
//创建数据存储后端,如数据库,文件,网络接口等,这里主要进行初始化工作
@Override
public boolean onCreate() {
databaseHelper=new DatabaseAdapter.DatabaseHelper(getContext());
return false;
}
//查询符合指定条件的记录
@Override
public Cursor query(Uri uri,String[] projection,String selection,String[] selectionArgs,String sortOrder) {
switch (uriMatcher.match(uri)){
case single_code:
SQLiteDatabase database=databaseHelper.getReadableDatabase();
long id=ContentUris.parseId(uri);
selection= PersonMetadata.Person._ID+"=?";
selectionArgs= new String[]{String.valueOf(id)};
return database.query(PersonMetadata.Person.TABLE_NAME,projection,selection,selectionArgs,null,null,sortOrder);
case multiple_code:
database=databaseHelper.getReadableDatabase();
return database.query(PersonMetadata.Person.TABLE_NAME,projection,selection,selectionArgs,null,null,sortOrder);
}
return null;
}
//基于给定URI,返回URI表示的MIME类型
@Override
public String getType(Uri uri) {
switch (uriMatcher.match(uri)){
case single_code:
return single_type;
case multiple_code:
return multiple_type;
}
return null;
}
//插入一个新的记录
@Override
public Uri insert(Uri uri,ContentValues values) {
switch (uriMatcher.match(uri)){
case multiple_code:
SQLiteDatabase database=databaseHelper.getWritableDatabase();
long id=database.insert(PersonMetadata.Person.TABLE_NAME,null,values);
uri=ContentUris.withAppendedId(uri,id);
database.close();
break;
}
return uri;
}
//删除符合指定条件的记录
@Override
public int delete(Uri uri,String selection,String[] selectionArgs) {
switch (uriMatcher.match(uri)){
case single_code:
SQLiteDatabase database=databaseHelper.getWritableDatabase();
long id=ContentUris.parseId(uri);
selection= PersonMetadata.Person._ID+"=?";
selectionArgs= new String[]{String.valueOf(id)};
int row=database.delete(PersonMetadata.Person.TABLE_NAME,selection,selectionArgs);
database.close();
return row;
case multiple_code:
database=databaseHelper.getWritableDatabase();
row=database.delete(PersonMetadata.Person.TABLE_NAME,selection,selectionArgs);
database.close();
return row;
}
return 0;
}
//更新指定条件的记录
@Override
public int update(Uri uri,ContentValues values,String selection,String[] selectionArgs) {
switch (uriMatcher.match(uri)){
case single_code:
SQLiteDatabase database=databaseHelper.getWritableDatabase();
long id=ContentUris.parseId(uri);
selection= PersonMetadata.Person._ID+"=?";
selectionArgs= new String[]{String.valueOf(id)};
int row=database.update(PersonMetadata.Person.TABLE_NAME,values,selection,selectionArgs);
database.close();
return row;
case multiple_code:
database=databaseHelper.getWritableDatabase();
row=database.update(PersonMetadata.Person.TABLE_NAME,values,selection,selectionArgs);
database.close();
return row;
}
return 0;
}
}
MainActivity
package com.example.loaders;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.app.LoaderManager;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.view.View;
import android.widget.ListView;
import android.widget.SimpleCursorAdapter;
public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private DatabaseAdapter databaseAdapter;
SimpleCursorAdapter dataAdapter;
CursorLoader loader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
databaseAdapter=new DatabaseAdapter(this);
ListView lv=findViewById(R.id.listView);
dataAdapter=new SimpleCursorAdapter(this,R.layout.list_item,
databaseAdapter.list(),
new String[]{PersonMetadata.Person._ID,
PersonMetadata.Person.NAME,
PersonMetadata.Person.AGE},
new int[]{R.id.textView_id, R.id.textView_name,R.id.textView_age},
SimpleCursorAdapter.FLAG_REGISTER_CONTENT_OBSERVER);//内容观察者
lv.setAdapter(dataAdapter);
//初始化一个loader(id,Bundler参数,回调接口)
getLoaderManager().initLoader(0,null,this);
}
public void addClick(View view){
databaseAdapter.save(new Person("路飞",19));
//重启加载器
// getLoaderManager().restartLoader(0,null,this);
// loader.commitContentChanged();//api 18可用
loader.onContentChanged();//内容发生了变化,通知加载器
}
@NonNull
@Override
public Loader<Cursor> onCreateLoader(int i, @Nullable Bundle bundle) {
System.out.println("onCreateLoader");
Uri uri=Uri.parse("content://com.example.loaders.personcontentprovider/person");
//创建一个游标加载器(上下文,CP的URI,查询的列的数组,查询条件,查询条件的值,是否排序,排序条件)
loader=new CursorLoader(this,uri,null,null,null,null);
return loader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
System.out.println("onLoadFinished");
dataAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
System.out.println("onLoaderReset");
dataAdapter.swapCursor(null);
}
}
相应布局文件
activity_mian
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context=".MainActivity">
<Button
android:id="@+id/button_add"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="add"
android:onClick="addClick"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<ListView
android:id="@+id/listView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintTop_toBottomOf="@id/button_add"
tools:ignore="MissingConstraints" />
</android.support.constraint.ConstraintLayout>
list_item
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android" android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="new Text"
android:textSize="24sp"
android:id="@+id/textView_id"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="new Text"
android:textSize="24sp"
android:id="@+id/textView_name"/>
<TextView
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="new Text"
android:textSize="24sp"
android:id="@+id/textView_age"/>
</LinearLayout>
6.AsyncTaskLoader
提供了一个基于AsyncTask工作机制的Loader,查看源码可以看到其继承于loader,并且子类LoaderTask继承于AsyncTask<Void,Void,D>,并且实现了Runnable接口,功能十分强大。
package com.example.loaders;
import android.content.AsyncTaskLoader;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.app.LoaderManager;
import android.content.Loader;
import android.support.v7.app.AppCompatActivity;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ListView;
import android.widget.TextView;
import java.util.ArrayList;
public class Main2Activity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<ArrayList<Person>> {
private MyAdapter myAdapter;
private DatabaseAdapter databaseAdapter;
private DataAsyncTaskLoader loader;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main2);
ListView lv=findViewById(R.id.listView2);
databaseAdapter=new DatabaseAdapter(this);
myAdapter=new MyAdapter(this,databaseAdapter.findAll());
lv.setAdapter(myAdapter);
getLoaderManager().initLoader(0,null,this);
}
public void addClick(View view){
databaseAdapter.save(new Person("路飞",19));
loader.onContentChanged();
}
@NonNull
@Override
public Loader<ArrayList<Person>> onCreateLoader(int i, @Nullable Bundle bundle) {
loader=new DataAsyncTaskLoader(this,databaseAdapter);
return loader;
}
@Override
public void onLoadFinished(@NonNull Loader<ArrayList<Person>> loader, ArrayList<Person> people) {
myAdapter.setPeople(people);
myAdapter.notifyDataSetChanged();
}
@Override
public void onLoaderReset(@NonNull Loader<ArrayList<Person>> loader) {
myAdapter.setPeople(null);
}
//自定义Loader
private static class DataAsyncTaskLoader extends AsyncTaskLoader<ArrayList<Person>>{
private DatabaseAdapter databaseAdapter;
private ArrayList<Person> data;
public DataAsyncTaskLoader(Context context,DatabaseAdapter databaseAdapter) {
super(context);
this.databaseAdapter=databaseAdapter;
}
//该方法在后台线程中执行,用来加载数据
@Override
public ArrayList<Person> loadInBackground() {
System.out.println("loadInBackground");
data=databaseAdapter.findAll();
return data;
}
//用于发送结果
@Override
public void deliverResult(ArrayList<Person> data) {
if (isReset()){
return;
}
if (isStarted()){
super.deliverResult(data);
}
super.deliverResult(data);
}
@Override
protected void onStartLoading() {
if (data!=null){
deliverResult(data);
}
if (takeContentChanged()){
forceLoad();//强制加载数据
}
super.onStartLoading();
}
}
//自定义适配器
private static class MyAdapter extends BaseAdapter{
private ArrayList<Person> people;
private Context context;
public void setPeople(ArrayList<Person> people){
this.people=people;
}
public MyAdapter(Context context,ArrayList<Person> people){
this.people=people;
this.context=context;
}
@Override
public int getCount() {
return people.size();
}
@Override
public Object getItem(int position) {
return people.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder vh=null;
if (convertView==null){
convertView=LayoutInflater.from(context).inflate(R.layout.list_item,null);
vh=new ViewHolder();
vh.tv_id=convertView.findViewById(R.id.textView_id);
vh.tv_name=convertView.findViewById(R.id.textView_name);
vh.tv_age=convertView.findViewById(R.id.textView_age);
convertView.setTag(vh);
}else {
vh= (ViewHolder) convertView.getTag();
}
Person p=people.get(position);
vh.tv_id.setText(String.valueOf(p.getId()));
vh.tv_name.setText(p.getName());
vh.tv_age.setText(String.valueOf(p.getAge()));
return convertView;
}
private static class ViewHolder{
TextView tv_id;
TextView tv_name;
TextView tv_age;
}
}
}