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里加载数据。
1 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 2 // TODO Auto-generated method stub 3 /*CursorLoader loader=new CursorLoader(MainActivity.this); 4 Uri uri = Uri 5 .parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 6 loader.setUri(uri);*/ 7 Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 8 CursorLoader loader=new CursorLoader(MainActivity.this, uri, null, null, null, null); 9 return loader; 10 }
onLoadFinished方法完成提取 放到适配器(自定义)中 更新UI
1 public class myAdapter extends BaseAdapter{ 2 3 private Context context; 4 private List<String> list; 5 private myAdapter(){ 6 7 } 8 private myAdapter(Context context){ 9 this.context=context; 10 } 11 12 private void setData(List<String> list){ 13 this.list=list; 14 } 15 @Override 16 public int getCount() { 17 // TODO Auto-generated method stub 18 return list.size(); 19 } 20 21 @Override 22 public Object getItem(int position) { 23 // TODO Auto-generated method stub 24 return list.get(position); 25 } 26 27 @Override 28 public long getItemId(int position) { 29 // TODO Auto-generated method stub 30 return position; 31 } 32 33 @Override 34 public View getView(int position, View convertView, ViewGroup parent) { 35 // TODO Auto-generated method stub 36 TextView view=null; 37 if(convertView==null){ 38 view=new TextView(context); 39 } 40 else 41 view=(TextView)convertView; 42 view.setText(list.get(position)); 43 return view; 44 } 45 46 } 47
1 @Override 2 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 3 // TODO Auto-generated method stub 4 List<String> list=new ArrayList<String>(); 5 while(cursor.moveToNext()){ 6 String name=cursor.getString(cursor.getColumnIndex("name")); 7 list.add(name); 8 } 9 myAdapter adapter=new myAdapter(MainActivity.this); 10 adapter.setData(list); 11 listView.setAdapter(adapter); 12 adapter.notifyDataSetChanged(); 13 }
这样就可以异步加载数据库中的数据了,接下来需要借助上下文菜单ContextMenu给listview中的选项添加两个操作:添加和删除
首先在activity的oncreate方法中为listView注册一个上下文菜单:registerForContextMenu(listView);
第二步:重写onCreateContextMenu方法处理上下文菜单,需要定义一个菜单,也可编辑使用menu菜单下已有的菜单main.xml,加载菜单布局
第三步:重写onMenuItemSelected方法,其中需要自定义对话框(还要自定义一个布局)处理用户的选择
最终源码:
项目目录:
MainActivity.java
1 package com.example.android_07loader_manager; 2 3 import java.util.ArrayList; 4 import java.util.List; 5 6 import android.app.Activity; 7 import android.app.Dialog; 8 import android.app.LoaderManager; 9 import android.app.LoaderManager.LoaderCallbacks; 10 import android.content.ContentResolver; 11 import android.content.ContentValues; 12 import android.content.Context; 13 import android.content.CursorLoader; 14 import android.content.Loader; 15 import android.database.Cursor; 16 import android.net.Uri; 17 import android.os.Bundle; 18 import android.view.ContextMenu; 19 import android.view.ContextMenu.ContextMenuInfo; 20 import android.view.Menu; 21 import android.view.MenuItem; 22 import android.view.View; 23 import android.view.ViewGroup; 24 import android.widget.BaseAdapter; 25 import android.widget.Button; 26 import android.widget.EditText; 27 import android.widget.ListView; 28 import android.widget.TextView; 29 import android.widget.Toast; 30 import android.widget.AdapterView.AdapterContextMenuInfo; 31 32 public class MainActivity extends Activity { 33 34 private LoaderManager manager; 35 private ListView listView; 36 myAdapter adapter=null; 37 @Override 38 protected void onCreate(Bundle savedInstanceState) { 39 super.onCreate(savedInstanceState); 40 setContentView(R.layout.activity_main); 41 listView=(ListView)this.findViewById(R.id.listView1); 42 adapter=new myAdapter(MainActivity.this); 43 manager=getLoaderManager();//LoaderManager已经集成在activity中,用来完成异步加载 44 manager.initLoader(1000, null, callback);//启动loader 45 registerForContextMenu(listView); 46 } 47 48 private LoaderManager.LoaderCallbacks<Cursor> callback=new LoaderCallbacks<Cursor>() { 49 50 @Override 51 public Loader<Cursor> onCreateLoader(int id, Bundle args) { 52 // TODO Auto-generated method stub 53 /*CursorLoader loader=new CursorLoader(MainActivity.this); 54 Uri uri = Uri 55 .parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 56 loader.setUri(uri);*/ 57 Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 58 CursorLoader loader=new CursorLoader(MainActivity.this, uri, null, null, null, null); 59 return loader; 60 } 61 62 //完成提取 放到适配器中 更新UI 63 @Override 64 public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) { 65 // TODO Auto-generated method stub 66 List<String> list=new ArrayList<String>(); 67 while(cursor.moveToNext()){ 68 String name=cursor.getString(cursor.getColumnIndex("name")); 69 list.add(name); 70 } 71 72 adapter.setData(list); 73 listView.setAdapter(adapter); 74 adapter.notifyDataSetChanged(); 75 } 76 77 @Override 78 public void onLoaderReset(Loader<Cursor> loader) { 79 // TODO Auto-generated method stub 80 81 } 82 }; 83 public class myAdapter extends BaseAdapter{ 84 85 private Context context; 86 private List<String> list; 87 private myAdapter(){ 88 89 } 90 private myAdapter(Context context){ 91 this.context=context; 92 } 93 94 private void setData(List<String> list){ 95 this.list=list; 96 } 97 @Override 98 public int getCount() { 99 // TODO Auto-generated method stub 100 return list.size(); 101 } 102 103 @Override 104 public Object getItem(int position) { 105 // TODO Auto-generated method stub 106 return list.get(position); 107 } 108 109 @Override 110 public long getItemId(int position) { 111 // TODO Auto-generated method stub 112 return position; 113 } 114 115 @Override 116 public View getView(int position, View convertView, ViewGroup parent) { 117 // TODO Auto-generated method stub 118 TextView view=null; 119 if(convertView==null){ 120 view=new TextView(context); 121 } 122 else 123 view=(TextView)convertView; 124 view.setText(list.get(position)); 125 return view; 126 } 127 128 } 129 @Override 130 public boolean onCreateOptionsMenu(Menu menu) { 131 // Inflate the menu; this adds items to the action bar if it is present. 132 getMenuInflater().inflate(R.menu.main, menu); 133 return true; 134 } 135 136 @Override 137 public void onCreateContextMenu(ContextMenu menu, View v,ContextMenuInfo menuInfo) { 138 // TODO Auto-generated method stub 139 super.onCreateContextMenu(menu, v, menuInfo); 140 getMenuInflater().inflate(R.menu.main, menu); 141 } 142 @Override 143 public boolean onMenuItemSelected(int featureId, MenuItem item) { 144 // TODO Auto-generated method stub 145 switch (item.getItemId()) { 146 case R.id.add: 147 //Toast.makeText(MainActivity.this, "add", 1).show(); 148 final Dialog dialog=createAddDialog(MainActivity.this); 149 Button button1=(Button)dialog.findViewById(R.id.button1); 150 button1.setOnClickListener(new View.OnClickListener() { 151 152 @Override 153 public void onClick(View v) { 154 // TODO Auto-generated method stub 155 final EditText editText=(EditText)dialog.findViewById(R.id.editText1); 156 String name=editText.toString(); 157 ContentResolver resolver=getContentResolver(); 158 Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 159 ContentValues values = new ContentValues(); 160 values.put("name", name); 161 Uri res_uri=resolver.insert(uri, values); 162 if(res_uri!=null)//添加成功的时候 刷新数据 163 { 164 manager.restartLoader(1000, null, callback);//重新启动查询器 165 dialog.dismiss(); 166 } 167 } 168 }); 169 dialog.show(); 170 break; 171 172 case R.id.del: 173 //AdapterContextMenuInfo对象包含了对话框选项的所有信息 AdapterContextMenuInfo into=item.getMenuInfo(); 174 AdapterContextMenuInfo info=(AdapterContextMenuInfo) item.getMenuInfo(); 175 ContentResolver resolver = getContentResolver(); 176 Uri uri = Uri.parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 177 int position=info.position; 178 String name=adapter.getItem(position).toString(); 179 int count = resolver.delete(uri, "name=?", new String[]{name}); 180 //Toast.makeText(MainActivity.this, "del", 1).show(); 181 if(count>0){ 182 manager.restartLoader(1000, null, callback);//重新启动查询器 183 } 184 185 break; 186 } 187 return super.onMenuItemSelected(featureId, item); 188 } 189 190 public Dialog createAddDialog(Context context){ 191 Dialog dialog=new Dialog(context); 192 dialog.setContentView(R.layout.add); 193 return dialog; 194 195 } 196 /* @Override 197 public boolean onOptionsItemSelected(MenuItem item) { 198 199 int id = item.getItemId(); 200 if (id == R.id.action_settings) { 201 return true; 202 } 203 204 205 return super.onOptionsItemSelected(item); 206 }*/ 207 }
MyTest.java
1 package com.example.android_07loader_manager; 2 3 import android.content.ContentResolver; 4 import android.content.ContentUris; 5 import android.content.ContentValues; 6 import android.database.Cursor; 7 import android.net.Uri; 8 import android.test.AndroidTestCase; 9 10 public class MyTest extends AndroidTestCase { 11 12 public MyTest() { 13 // TODO Auto-generated constructor stub 14 } 15 16 public void add() { 17 ContentResolver contentResolver = getContext().getContentResolver(); 18 ContentValues values = new ContentValues(); 19 values.put("name", "mlj"); 20 Uri url = Uri 21 .parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 22 contentResolver.insert(url, values); 23 } 24 25 public void delete() { 26 ContentResolver contentResolver = getContext().getContentResolver(); 27 28 Uri url = Uri 29 .parse("content://com.example.android_07loader_manager.StudentContentProvider/student/1"); 30 int count = contentResolver.delete(url, null, null); 31 System.out.println("---count-->>" + count); 32 } 33 34 public void query() { 35 ContentResolver contentResolver = getContext().getContentResolver(); 36 Uri url = Uri 37 .parse("content://com.example.android_07loader_manager.StudentContentProvider/student"); 38 Uri uri = ContentUris.withAppendedId(url, 2); 39 40 Cursor cursor = contentResolver.query(uri, null, null, null, null); 41 while (cursor.moveToNext()) { 42 System.out.println("--->>" 43 + cursor.getString(cursor.getColumnIndex("name"))); 44 } 45 } 46 }
StudentContentProvider.java
1 package com.example.android_07loader_manager; 2 3 import com.example.android_07loader_manager.dbhelp.DBHelper; 4 5 import android.R.integer; 6 import android.content.ContentProvider; 7 import android.content.ContentUris; 8 import android.content.ContentValues; 9 import android.content.UriMatcher; 10 import android.database.Cursor; 11 import android.database.sqlite.SQLiteDatabase; 12 import android.net.Uri; 13 14 public class StudentContentProvider extends ContentProvider { 15 16 private final static UriMatcher URI_MATCHER = new UriMatcher(UriMatcher.NO_MATCH); 17 private final static int STUDENT = 1; 18 private final static int STUDENTS = 2; 19 private DBHelper helper; 20 static { 21 URI_MATCHER.addURI( 22 "com.example.android_07loader_manager.StudentContentProvider","student", STUDENTS); 23 URI_MATCHER.addURI( 24 "com.example.android_07loader_manager.StudentContentProvider","student/#", STUDENT); 25 } 26 27 public StudentContentProvider() { 28 // TODO Auto-generated constructor stub 29 } 30 31 @Override 32 public int delete(Uri uri, String selection, String[] selectionArgs) { 33 // TODO Auto-generated method stub 34 int count = 0;// 影响数据库的行数 35 int flag = URI_MATCHER.match(uri); 36 SQLiteDatabase database = helper.getWritableDatabase(); 37 38 switch (flag) { 39 case STUDENT: 40 long stuid = ContentUris.parseId(uri); 41 String where_value = " stuid = " + stuid; 42 if (selection != null && !selection.equals("")) { 43 where_value += selection; 44 } 45 count = database.delete("student", where_value, selectionArgs); 46 break; 47 48 case STUDENTS: 49 count = database.delete("student", selection, selectionArgs); 50 break; 51 } 52 return count; 53 } 54 55 @Override 56 public String getType(Uri uri) { 57 // TODO Auto-generated method stub 58 int flag = URI_MATCHER.match(uri); 59 switch (flag) { 60 case STUDENT: 61 return "vnd.android.cursor.item/student"; 62 case STUDENTS: 63 return "vnd.android.cursor.dir/studens"; 64 } 65 return null; 66 } 67 68 @Override 69 public Uri insert(Uri uri, ContentValues contentValues) { 70 // TODO Auto-generated method stub 71 int flag = URI_MATCHER.match(uri); 72 SQLiteDatabase database = helper.getWritableDatabase(); 73 Uri uri2 = null; 74 switch (flag) { 75 case STUDENTS: 76 long id = database.insert("student", null, contentValues); 77 uri2 = ContentUris.withAppendedId(uri, id); 78 break; 79 } 80 System.out.println("-->>" + uri2.toString()); 81 return uri2; 82 } 83 84 @Override 85 public boolean onCreate() { 86 // TODO Auto-generated method stub 87 helper = new DBHelper(getContext()); 88 return false; 89 } 90 91 @Override 92 public Cursor query(Uri uri, String[] projection, String selection, 93 String[] selectionArgs, String sortOrder) { 94 // TODO Auto-generated method stub 95 Cursor cursor = null; 96 int flag = URI_MATCHER.match(uri); 97 SQLiteDatabase database = helper.getReadableDatabase(); 98 switch (flag) { 99 case STUDENT: 100 long stuid = ContentUris.parseId(uri); 101 String where_value = " stuid = " + stuid; 102 if (selection != null && !"".equals(selection)) { 103 where_value += selection; 104 } 105 cursor = database.query("student", projection, where_value, 106 selectionArgs, null, null, null); 107 108 break; 109 110 case STUDENTS: 111 cursor = database.query("student", projection, selection, 112 selectionArgs, null, null, null); 113 break; 114 } 115 return cursor; 116 } 117 118 @Override 119 public int update(Uri uri, ContentValues values, String selection, 120 String[] selectionArgs) { 121 // TODO Auto-generated method stub 122 int count = 0; 123 int flag = URI_MATCHER.match(uri); 124 SQLiteDatabase database = helper.getWritableDatabase(); 125 switch (flag) { 126 case STUDENT: 127 long stuid = ContentUris.parseId(uri); 128 String where_value = " stuid = " + stuid; 129 if (selection != null && !selection.equals("")) { 130 where_value += selection; 131 } 132 count = database.update("student", values, where_value, 133 selectionArgs); 134 break; 135 136 case STUDENTS: 137 count = database 138 .update("student", values, selection, selectionArgs); 139 break; 140 } 141 return count; 142 } 143 144 }
DBHelper.java
1 package com.example.android_07loader_manager.dbhelp; 2 3 import android.content.Context; 4 import android.database.DatabaseErrorHandler; 5 import android.database.sqlite.SQLiteDatabase; 6 import android.database.sqlite.SQLiteDatabase.CursorFactory; 7 import android.database.sqlite.SQLiteOpenHelper; 8 9 public class DBHelper extends SQLiteOpenHelper{ 10 11 private static String name = "mydb.db"; 12 private static int version = 1; 13 14 public DBHelper(Context context) { 15 super(context, name, null, version); 16 // TODO Auto-generated constructor stub 17 } 18 19 public DBHelper(Context context, String name, CursorFactory factory, 20 int version, DatabaseErrorHandler errorHandler) { 21 super(context, name, factory, version, errorHandler); 22 // TODO Auto-generated constructor stub 23 } 24 25 26 @Override 27 public void onCreate(SQLiteDatabase db) { 28 // TODO Auto-generated method stub 29 String sql = "create table student (stuid integer primary key autoincrement,name varchar(64))"; 30 db.execSQL(sql); 31 } 32 33 @Override 34 public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) { 35 // TODO Auto-generated method stub 36 37 } 38 39 }
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;
1 @Override 2 public boolean onCreate() { 3 // TODO Auto-generated method stub 4 helper = new DBHelper(getContext()); 5 return false; 6 }
匹配规则(静态代码块)
1 static { 2 URI_MATCHER.addURI( 3 "com.example.android_07loader_manager.StudentContentProvider","student", STUDENTS); 4 URI_MATCHER.addURI( 5 "com.example.android_07loader_manager.StudentContentProvider","student/#", STUDENT); 6 }
重写getType方法
1 @Override 2 public String getType(Uri uri) { 3 // TODO Auto-generated method stub 4 int flag = URI_MATCHER.match(uri); 5 switch (flag) { 6 case STUDENT: 7 return "vnd.android.cursor.item/student"; 8 case STUDENTS: 9 return "vnd.android.cursor.dir/studens"; 10 } 11 return null; 12 }
.在清单文件中注册内容提供者: <provider android:name=".StudentContentProvider" android:authorities="com.example.android_07loader_manager.StudentContentProvider"></provider>
2.2 完成增删改查
2.3建测试类
第三步: