前言

  Loaders,装载机,适用于Android3.0以及更高的版本,它提供了一套在UI的主线程中异步加载数据的框架。使用Loaders可以非常简单的在Activity或者Fragment中异步加载数据,一般适用于大量的数据查询,或者需要经常修改并及时展示的数据显示到UI上,这样可以避免查询数据的时候,造成UI主线程的卡顿。

  Loaders有以下特点:

  • 可以适用于Activity和Fragment。
  • 可以提供异步的方式加载数据。
  • 监听数据源,当数据改变的时候,将新的数据发布到UI上。
  • Loaders使用Cursor加载数据,在更改Cursor的时候,会自动重新连接到最后配置的Cursor中读取数据,因此不需要重新查询数据。

  在Android中使用Loaders机制,需要多个类和接口的配合,以下是它们大致的关系图,之后的内容会对这几个类或接口进行详细讲解:

LoaderManager

  LoaderManager,装载机管理器。用于在Activity或者Fragment中管理一个或多个Loader实例。在Activity或者Fragment中,可以通过getLoaderManager()方法获取LoaderManager对象,它是一个单例模式。

  介绍几个LoaderManager提供的方法,用于管理Loader:

  • Loader<D> initLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):初始化一个Loader,并注册回调事件。
  • Loader<D> restartLoader(int id,Bundle bundle,LoaderCallbacks<D> callback):重新启动或创建一个Loader,并注册回调事件。
  • Loader<D> getLoader(int id):返回给定Id的Loader,如果没有找到则返回Null。
  • void destroyLoader(int id):根据指定Id,停止和删除Loader。

  通过上面几个方法的参数可以看到,都有一个id参数,这个Id是Loader的标识,因为LoaderManager可以管理一个或多个Loader,所以必须通过这个Id参数来唯一确定一个Loader。而InitLoader()、restartLoader()中的bundle参数,传递一个Bundle对象给LoaderCallbacks中的onCreateLoader()去获取,下面介绍LoaderCallbacks。

LoaderManager.LoaderCallbacks

  LoaderCallbacks是LoaderManager和Loader之间的回调接口。它是一个回调接口,所以我们需要实现其定义的三个方法:

  • Loader<D> onCreateLoader(int id,Bundle bundle):根据指定Id,初始化一个新的Loader。
  • void onLoadFinished(Loader<D> loader,D data):当Loader被加载完毕后被调用,在其中处理Loader获取的Cursor数据。
  • void onLoaderReset(Loader<D> loader):当Loader被销毁的时候被调用,在其中可以使Loader的数据不可用。

  从LoaderCallbacks的声明的几个方法中可以看到,它是一个泛型的接口,需要指定Loader数据的类型。如果是数据源是从一个ContentProvider中获取的,一般直接使用它的子类CursorLoader,下面介绍CursorLoader。

Loader

  Loader,一个抽象的类,用于执行异步加载数据,这个Loader对象可以监视数据源的改变和在内容改变后,以新数据的内容改变UI的展示。它是一个Loader的抽象接口,所有需要实现的Loader功能的类都需要实现这个接口,但是如果需要自己开发一个装载机的话,一般并不推荐继承Loader接口,而是继承它的子类AsyncTaskLoader,这是一个以AsyncTask框架执行的异步加载。

  Android中还提供了一个CursorLoader类,它是AsyncTaskLoader的子类,一个异步的加载数据的类,通过ContentResolver的标准查询并返回一个Cursor。这个类实现了Loader的协议,以一种标准的方式查询Cursor。

  CursorLoader类有两个构造函数,推荐使用第二个,因为使用第一个构造函数,需要还需要通过CursorLoader提供的一些了getXxx()方法设置对应的属性:

  • CursorLoader(Context context)
  • CursorLoader(Context context,Uri uri,String[] projection,String selection ,String[] selectionArgs,String sortOrder)

SimpleCursorAdapter

  在Android中,数据的展示都需要使用一个Adapter适配器,而Loader一般返回的就是一个Cursor的数据,可以使用BaseAdapter的一个子类SimpleCursorAdapter,它可以使用XML资源文件自定义一个布局在展示数据。它有两个构造函数,但是有一个构造函数在API Level11之后就不推荐使用。下面是构造函数的签名:

  SimpleCursorAdapter(Context context,int layout,Cursor c,String[] from,int[] to,int flags).

  最后一个参数flags是一个标识,标识当数据改变调用onContentChanged()的时候,是否通知ContentProvider数据的改变,如果无需监听ContentProvider的改变,则可以传0。对于SimpleCursorAdapter适配器的Cursor的改变,可以使用SimpleCursorAdapter.swapCursor(Cursor)方法,它会与旧的Cursor互换,并且返回旧的Cursor。

Demo

  下面通过一个Demo来讲解一下Loaders的使用。在这个Demo中,数据使用SQLite数据库保存,而使用ContentProvider进行数据的请求与访问。在SQLite数据库中,存在一个Student表,它近有两个字段:_id,name。在Demo中,使用一个ListView展示数据,使用LoaderManager管理一个Loader,并通过这个Loader的回调接口进行刷新ListView的数据显示。进行对SQLite数据库中的数据进行增加与删除。下面不提供SQLiteOpenHelper和ContentProvider相关实现类的代码,如有需要可以下载源码查看,对于SQLite和ContentProvider的内容,不清楚的朋友可以参见博客:数据持久化之SQLiteContentProvider

  实现代码:

  1. package com.example.loadermanagerdemo;
  2.  
  3. import android.net.Uri;
  4. import android.os.Bundle;
  5. import android.app.Activity;
  6. import android.app.AlertDialog;
  7. import android.app.LoaderManager;
  8. import android.app.LoaderManager.LoaderCallbacks;
  9. import android.content.ContentResolver;
  10. import android.content.ContentValues;
  11. import android.content.CursorLoader;
  12. import android.content.Loader;
  13. import android.database.Cursor;
  14. import android.util.Log;
  15. import android.view.ContextMenu;
  16. import android.view.LayoutInflater;
  17. import android.view.Menu;
  18. import android.view.MenuInflater;
  19. import android.view.MenuItem;
  20. import android.view.View;
  21. import android.view.ContextMenu.ContextMenuInfo;
  22. import android.widget.AdapterView.AdapterContextMenuInfo;
  23. import android.widget.Button;
  24. import android.widget.EditText;
  25. import android.widget.ListView;
  26. import android.widget.SimpleCursorAdapter;
  27. import android.widget.TextView;
  28.  
  29. public class MainActivity extends Activity {
  30. private LoaderManager manager;
  31. private ListView listview;
  32. private AlertDialog alertDialog;
  33. private SimpleCursorAdapter mAdapter;
  34. private final String TAG="main";
  35.  
  36. @Override
  37. protected void onCreate(Bundle savedInstanceState) {
  38. super.onCreate(savedInstanceState);
  39. setContentView(R.layout.activity_main);
  40. listview = (ListView) findViewById(R.id.listView1);
  41. //使用一个SimpleCursorAdapter,布局使用android自带的布局资源simple_list_item_1, android.R.id.text1 为simple_list_item_1中TextView的Id
  42. mAdapter = new SimpleCursorAdapter(MainActivity.this,
  43. android.R.layout.simple_list_item_1, null,
  44. new String[] { "name" }, new int[] { android.R.id.text1 },0);
  45.  
  46. // 获取Loader管理器。
  47. manager = getLoaderManager();
  48. // 初始化并启动一个Loader,设定标识为1000,并制定一个回调函数。
  49. manager.initLoader(1000, null, callbacks);
  50.  
  51. // 为ListView注册一个上下文菜单
  52. registerForContextMenu(listview);
  53. }
  54.  
  55. @Override
  56. public void onCreateContextMenu(ContextMenu menu, View v,
  57. ContextMenuInfo menuInfo) {
  58. super.onCreateContextMenu(menu, v, menuInfo);
  59. // 声明一个上下文菜单,contentmenu中声明了两个菜单,添加和删除
  60. MenuInflater inflater = getMenuInflater();
  61. inflater.inflate(R.menu.contentmenu, menu);
  62. }
  63.  
  64. @Override
  65. public boolean onContextItemSelected(MenuItem item) {
  66.  
  67. switch (item.getItemId()) {
  68. case R.id.menu_add:
  69. // 声明一个对话框
  70. AlertDialog.Builder builder = new AlertDialog.Builder(
  71. MainActivity.this);
  72. // 加载一个自定义布局,add_name中有一个EditText和Button控件。
  73. final View view = LayoutInflater.from(MainActivity.this).inflate(
  74. R.layout.add_name, null);
  75. Button btnAdd = (Button) view.findViewById(R.id.btnAdd);
  76. btnAdd.setOnClickListener(new View.OnClickListener() {
  77.  
  78. @Override
  79. public void onClick(View v) {
  80. EditText etAdd = (EditText) view
  81. .findViewById(R.id.username);
  82. String name = etAdd.getText().toString();
  83. // 使用ContentResolver进行删除操作,根据name字段。
  84. ContentResolver contentResolver = getContentResolver();
  85. ContentValues contentValues = new ContentValues();
  86. contentValues.put("name", name);
  87. Uri uri = Uri
  88. .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
  89. Uri result = contentResolver.insert(uri, contentValues);
  90. if (result != null) {
  91. //result不为空证明删除成功,重新启动Loader,注意标识需要和之前init的标识一致。
  92. manager.restartLoader(1000, null, callbacks);
  93. }
  94. // 关闭对话框
  95. alertDialog.dismiss();
  96.  
  97. Log.i(TAG, "添加数据成功,name="+name);
  98. }
  99. });
  100. builder.setView(view);
  101. alertDialog = builder.show();
  102. return true;
  103. case R.id.menu_delete:
  104. // 获取菜单选项的信息
  105. AdapterContextMenuInfo info = (AdapterContextMenuInfo) item
  106. .getMenuInfo();
  107. // 获取到选项的TextView控件,并得到选中项的那么
  108. TextView tv = (TextView) info.targetView;
  109. String name = tv.getText().toString();
  110. // 使用ContentResolver进行删除操作
  111. Uri url = Uri
  112. .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
  113. ContentResolver contentResolver = getContentResolver();
  114. String where = "name=?";
  115. String[] selectionArgs = { name };
  116. int count = contentResolver.delete(url, where, selectionArgs);
  117. if (count == 1) {
  118. //这个操作仅删除单挑记录,如果删除行为1 ,则重新启动Loader
  119. manager.restartLoader(1000, null, callbacks);
  120. }
  121. Log.i(TAG, "删除数据成功,name="+name);
  122. return true;
  123. default:
  124. return super.onContextItemSelected(item);
  125. }
  126.  
  127. }
  128.  
  129. // Loader的回调接口
  130. private LoaderManager.LoaderCallbacks<Cursor> callbacks = new LoaderCallbacks<Cursor>() {
  131.  
  132. @Override
  133. public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
  134. // 在Loader创建的时候被调用,这里使用一个ContentProvider获取数据,所以使用CursorLoader返回数据
  135. Uri uri = Uri
  136. .parse("content://com.example.loadermanagerdemo.StudentContentProvider/student");
  137. CursorLoader loader = new CursorLoader(MainActivity.this, uri,
  138. null, null, null, null);
  139. Log.i(TAG, "onCreateLoader被执行。");
  140. return loader;
  141. }
  142.  
  143. @Override
  144. public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
  145. //刷新SimpleCursorAdapter的数据
  146. mAdapter.swapCursor(cursor);
  147. // 重新设定适配器
  148. listview.setAdapter(mAdapter);
  149. Log.i(TAG, "onLoadFinished被执行。");
  150. }
  151.  
  152. @Override
  153. public void onLoaderReset(Loader<Cursor> loader) {
  154. // 当Loader被从LoaderManager中移除的时候,被执行,清空SimpleCursorAdapter适配器的Cursor
  155. mAdapter.swapCursor(null);
  156. Log.i(TAG, "onLoaderReset被执行。");
  157. }
  158. };
  159.  
  160. @Override
  161. public boolean onCreateOptionsMenu(Menu menu) {
  162. getMenuInflater().inflate(R.menu.main, menu);
  163. return true;
  164. }
  165.  
  166. }

  效果展示:

  源码下载

Android--Loaders的更多相关文章

  1. Android Loader使用详解

    1.CursorLoader使用Demo public class MainActivity extends Activity implements  LoaderManager.LoaderCall ...

  2. 【腾讯Bugly干货分享】Android Linker 与 SO 加壳技术

    本文来自于腾讯bugly开发者社区,非经作者同意,请勿转载,原文地址:http://dev.qq.com/topic/57e3a3bc42eb88da6d4be143 作者:王赛 1. 前言 Andr ...

  3. Android Weekly Notes Issue #229

    Android Weekly Issue #229 October 30th, 2016 Android Weekly Issue #229 Android Weekly笔记, 本期内容包括: 性能库 ...

  4. Android MVP模式 谷歌官方代码解读

    Google官方MVP Sample代码解读 关于Android程序的构架, 当前(2016.10)最流行的模式即为MVP模式, Google官方提供了Sample代码来展示这种模式的用法. Repo ...

  5. [Android Pro] Android异步任务处理之AsyncTaskLoader的使用

    reference to : http://blog.csdn.net/happy_horse/article/details/51518280 最近项目中涉及到加载本地的地名.db文件,数据量大,自 ...

  6. [Android Pro] AsyncTaskLoader vs AsyncTask

    reference to : http://blog.csdn.net/a910626/article/details/45599133 我看了一下asyncTask是从LV3开始,AsyncTask ...

  7. Android中SQLite数据库小计

    2016-03-16 Android数据库支持 本文节选并翻译<Enterprise Android - Programing Android Database Applications for ...

  8. Android内存优化-内存泄漏的几个场景以及解决方式

    转自:http://blog.csdn.net/a910626/article/details/50849760 一.什么是内存泄漏 在Java程序中,如果一个对象没有利用价值了,正常情况下gc是会对 ...

  9. android.app.Activity阅读摘要,有时候会不会需要保持一些现场数据呢? 想让系统帮你退出到后台或者挂掉前做些前置保持工作吗,重点参考吧:

    * * @param savedInstanceState If the activity is being re-initialized after * previously being shut ...

  10. Google Developing for Android 三 - Performance最佳实践

    Google Developing for Android 三 - Performance最佳实践 发表于 2015-06-07   |   分类于 Android最佳实践 原文 Developing ...

随机推荐

  1. Jquery源码学习日记(1)

    https://jquery.com/  最新源码下载链接:jquery3.0 135-231定义了一些jquery的通用方法 233-301行定义了一些继承的方法 302-477定义了一些工具类方法 ...

  2. 初识“FireBug”

    今天学习前端知识又一次提到“FireBug”这款插件,现在,把今天学到的一点东西简单记录下来. 什么是FireBug FireBug是一个用于网站前端开发的工具,它是FireFox浏览器的一个扩展插件 ...

  3. Web开发的小知识点

    ServletConfig:用于读取配置文件信息 ServletContext:这是一个容器,代表一个web应用程序,多个Servlet可以通过这个容器共享数据信息(注意:这样的数据共享有线程安全问题 ...

  4. SCUCTF2018web部分wp

    [签到] Web部分的签到题,打开连接后F12审查元素 可以看到有被隐藏起来的JSFuck密码,解码运行后可得flag [计算器] 打开后界面如上图,要求简单来说就是回答20道数学题目,每道题最多3s ...

  5. 计蒜客 2019 蓝桥杯省赛 B 组模拟赛(一)

    D题:马的管辖 二进制枚举方案.判断该方案是否全部能被覆盖,将最优方案存下来并进行剪枝. #include<iostream> #include<cstring> #inclu ...

  6. license

    http://139.199.89.239:1008/4571ab86-eb0d-4d2b-999e-37406bb8ba38 _~~~ rO0ABXNyAChjb20uemVyb3R1cm5hcm9 ...

  7. 一个jar包冲突引起的StackOverflowError

    项目运行中错误信息:java.lang.IllegalStateException: Unable to complete the scan for annotations for web appli ...

  8. MongoDB的数据备份与恢复

    一:数据备份操作 步骤: 1.以管理员身份打开cmd,然后打开到mongdb的bin文件夹 2.输入命令 mongodump -h dbhost -d dbname -o dbdirectory -h ...

  9. idea取消vim模式

    在安装idea时选择了vim编辑模式,但是用习惯了eclipse,总是要拷贝粘贴,在idea中一直按ctrl+c和ctrl+v不起总用.于是想把vim模式关闭掉.方法:菜单栏:tools->vi ...

  10. C# MVC验证Model

    .NET Core MVC3 数据模型验证的使用 这里我先粘贴一个已经加了数据验证的实体类PeopleModel,然后一一介绍. using System; using System.Collecti ...