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

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里加载数据。

 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         }
View Code

                          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     
View Code
 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         }
View Code

这样就可以异步加载数据库中的数据了,接下来需要借助上下文菜单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 }
View Code

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 }
View Code

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 }
View Code

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 }
View Code

 

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     }
View Code

  匹配规则(静态代码块)

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     }
View Code

   重写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     }
View Code

.在清单文件中注册内容提供者: <provider android:name=".StudentContentProvider" android:authorities="com.example.android_07loader_manager.StudentContentProvider"></provider>

2.2 完成增删改查

2.3建测试类 

第三步:

posted @ 2015-11-14 17:27  孟想阳光  阅读(493)  评论(0编辑  收藏  举报