详细讲解Android中使用LoaderManager加载数据的方法

Android的设计之中,任何耗时的操作都不能放在UI主线程之中。所以类似于网络操作等等耗时的操作都需要使用异步的实现。而在ContentProvider之中,也有可能存在耗时的操作(当查询的数据量很大的时候),这个时候我们也需要使用异步的调用来完成数据的查询。

当使用异步的query的时候,我们就需要使用LoaderManager了。使用LoaderManager就可以在不阻塞UI主线程的情况下完成数据的加载。

(1)获取loaderManger:activity.getLoaderManager()

(2)loaderManager的事件回调接口, LoaderManager.LoaderCallbacks<D>

下面是一个demo,从contentprovider中query数据添加到listview中,是异步执行的。

MySQLiteOpenHeleper.java:

package com.app.loadermanager;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;

public class MySQLiteOpenHelper extends SQLiteOpenHelper {

  public static final String db_name = "test.db3";
  public static final int version = 1;

  public MySQLiteOpenHelper(Context context) {
    super(context, db_name, null, version);
  }

  @Override
  public void onCreate(SQLiteDatabase db) {

    String create_sql = "create table tb_student(_id integer primary key autoincrement,name varchar(20),age integer)";
    db.execSQL(create_sql);
  }

  @Override
  public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {

  }

}

MyContentProvider.java

package com.app.loadermanager;

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 MyContentProvider extends ContentProvider {

  private MySQLiteOpenHelper helper = null;
  private static final UriMatcher matcher = new UriMatcher(
      UriMatcher.NO_MATCH);
  private static final int students = 1;
  static {
    matcher.addURI("com.app.contentprovider", "tb_student", students);
  }

  @Override
  public int delete(Uri arg0, String arg1, String[] arg2) {
    return 0;
  }

  @Override
  public String getType(Uri arg0) {
    return null;
  }

  @Override
  public Uri insert(Uri uri, ContentValues values) {
    SQLiteDatabase db = helper.getWritableDatabase();
    int flag = matcher.match(uri);
    switch (flag) {
    case students:
      long id = db.insert("tb_student", null, values);
      return ContentUris.withAppendedId(uri, id);
    }
    return null;
  }

  @Override
  public boolean onCreate() {
    helper = new MySQLiteOpenHelper(this.getContext());
    return true;
  }

  @Override
  public Cursor query(Uri uri, String[] projection, String selection,
      String[] selectionArgs, String sortOrder) {
    SQLiteDatabase db = helper.getWritableDatabase();
    Cursor cursor=db.query("tb_student", projection, selection, selectionArgs, null, null, null);
    return cursor;
  }

  @Override
  public int update(Uri uri, ContentValues values, String selection,
      String[] selectionArgs) {
    return 0;
  }

}

MainActivity.java:

package com.app.loadermanager;

import java.util.ArrayList;

import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;

@SuppressLint("NewApi")
public class MainActivity extends Activity implements
    LoaderManager.LoaderCallbacks<Cursor> {

  LoaderManager manager = null;
  ListView listView = null;

  @SuppressLint("NewApi")
  @Override
  protected void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.activity_main);
    listView = (ListView) this.findViewById(R.id.listview);
    manager = this.getLoaderManager();
    manager.initLoader(1000, null, this);
  }

  @Override
  public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
    CursorLoader loader = new CursorLoader(this,
        Uri.parse("content://com.app.contentprovider"), null, null,
        null, null);
    return loader;
  }

  @Override
  public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
    ArrayList<String> al = new ArrayList<String>();
    while (cursor.moveToNext()) {
      String name = cursor.getString(cursor.getColumnIndex("name"));
      al.add(name);
    }
    ArrayAdapter adapter=new ArrayAdapter(this,android.R.layout.simple_list_item_1,al);
    listView.setAdapter(adapter);
    adapter.notifyDataSetChanged();

  }

  @Override
  public void onLoaderReset(Loader<Cursor> loader) {

  }
}

LoaderManager与生命周期
Activity和Fragment都拥有getLoaderManager的方法,其实Fragment的getLoaderManager去获取的就是Activity所管理的众多LoaderManager之一。

1.Who标签
先来看一下Activity的getLoaderManager方法:

   LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
     if (mAllLoaderManagers == null) {
       mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
     }
     LoaderManagerImpl lm = mAllLoaderManagers.get(who);
     if (lm == null) {
       if (create) {
         lm = new LoaderManagerImpl(who, this, started);
         mAllLoaderManagers.put(who, lm);
       }
     } else {
       lm.updateActivity(this);
     }
     return lm;
   }

mAllLoaderManagers保存着一个Activity所拥有的所有LoaderManager,其key为String类型的who变量。若从Activity调用getLoaderManager,那么所得LoaderManager的who标签为(root):

   public LoaderManager getLoaderManager() {
     if (mLoaderManager != null) {
       return mLoaderManager;
     }
     mCheckedForLoaderManager = true;
     mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
     return mLoaderManager;
   }

若从Fragment中使用getLoaderManager,则所得LoaderManager的who标签会根据Fragment的层级不同而不同,在Activity中处于最顶级的Fragment的who标签为:

android:fragment:X
X为序号。

而属于Fragment的Fragment所活的的LoaderManager who标签就成为了:

android:fragment:X:Y
甚至更大的深度。

2.LoaderManager状态量mLoadersStarted
在开篇分析的时候分析过LoaderManager内部保存了一个mStarted状态,很多操作会根据这个状态做不同处理。通过上边的分析也能看出,Fragment中获取LoaderManager最终是通过Activity获取的,在LoaderManager构造时,就将一个状态量mLoadersStarted传递了进去,这个状态量交给LoaderManager自行管理。

而无论是Fragment还是Activity,都有mLoadersStarted这样一个状态量,在onStart生命周期后为true,onStop后为false。所以在onStart生命周期后做initLoader操作就会使Loader一经初始化就开始运行了。

3.Fragment和Activity的生命周期
Fragment和Activity能够对LoaderManager产生影响的生命周期是一样的。

onStart

系统在onStart阶段会获取LoaderManager,如果成功获取,则调用LoaderManager的doStart(),这里需要特别说明的是,虽然所有LoaderManager都是保存在Activity中,但是在Activity的onStart生命周期其也只是会获取属于自己的(root)标签LoaderManager,而并不是将所有保存在mAllLoaderManagers里的Manager全部遍历一遍。

onStop(performStop)

处于onStop生命周期,但是系统内部是通过performStop调用的。在这里,同样会获取属于自己的LoaderManager,如果Activity是因为配置改变出发的onStop(旋转屏幕),则调用LoaderManager的doRetain()接口,如果不是,就调用LoaderManager的doStop()接口。

onDestroy(performDestroy)

调用LoaderManager的doDestroy()接口销毁LoaderManager。

4.LoaderManager的生命周期
因为LoaderManager与Fragment/Activity的生命周期紧密相连,所以想要用好LoaderManager就必须了解其自身的生命周期,这样就能把握数据的完整变化规律了。

正常的从出生到销毁:

doStart() -> doReportStart() -> doStop() -> doDestroy()
Activity配置发生变化:

doStart() -> doRetain() -> finishRetain() -> doReportStart() -> doStart() -> doStop() -> doDestroy()
Fragment在onDestroyView()之后还会执行LoaderManager的doReportNextStart(), 即:

doStart() -> doRetain() -> doReportNextStart() -> finishRetain() -> doReportStart() -> doStart() -> doStop() -> doDestroy()
doStart()会将LoaderManager中保存的所有Loader都启动。最终是运行每一个Loader的onStartLoading()方法。只要是通过initLoader使用过的Loader都会记录在LoaderManager的mLoaders中,那么问题来了:

怎样在Fragment/Activity不销毁的前提下从LoaderManager中移除某个使用过的Loader呢?
答案就是使用LoaderManager的接口去除指定ID的Loader:

public void destroyLoader(int id)
这样就能在mLoaders中移除掉了,下次onStart的时候就没有这个Loader什么事了。

doReportStart()。如果Fragment上一次在销毁并重做,而且数据有效的话会在这里主动上报数据,最终走到callback的onLoadFinished中。
doStop()会停止mLoaders保存的所有Loader。最终是运行每一个Loader的onStopLoading()方法。
doDestroy()会清空所有有效和无效Loader,LoaderManager中不再存在任何Loader。
doRetain()会将LoaderManager的mRetaining状态置位true,并且保存retain时LoaderInfo的mStarted状态
finishRetain()如果之前所保存的mStarted与现在的不一样而且新的状态是停止的话,就停止掉这个Loader。否则若有数据并且不是要下次再上报(没有call doReportNextStart)的话就上报给callback的onLoadFinished。
doReportNextStart(),根据第6条,已经能够理解了。当Fragment执行到onDestroyView生命周期时,对自己的LoaderManager发出请求:即使现在有数据也不要进行上报,等我重做再到onStart生命周期时再给我。

(0)

相关推荐

  • Android开发中类加载器DexClassLoader的简单使用讲解

    简介 "类装载器"(ClassLoader),顾名思义,就是用来动态装载class文件的.标准的Java SDK中有个ClassLoader类,借助此类可以装载需要的class文件,前提是ClassLoader类初始化必须制定class文件的路径. import关键字引用的类文件和ClassLoader动态加载类的区别: import引用类的两个特点: 1.必须存在于本地,当程序运行该类时,内部类装载器会自动装载该类. 2.编译时必须在现场,否则编译过程会因找不到引用文件而不能正常编译

  • Android开发之ImageLoader使用详解

    先给大家展示效果图,看看是大家想要的效果吗,如果还满意,请参考以下代码: 前言 UniversalImageLoader是用于加载图片的一个开源项目,在其项目介绍中是这么写的, •支持多线程图片加载 •提供丰富的细节配置,比如线程池大小,HTPP请求项,内存和磁盘缓存,图片显示时的参数配置等等: •提供双缓存 •支持加载过程的监听: •提供图片的个性化显示配置接口: •Widget支持(这个,个人觉得没必要写进来,不过尊重原文) 其他类似的项目也有很多,但这个作为github上著名的开源项目被广

  • Android Universal ImageLoader 缓存图片

    项目介绍: Android上最让人头疼的莫过于从网络获取图片.显示.回收,任何一个环节有问题都可能直接OOM,这个项目或许能帮到你.Universal Image Loader for Android的目的是为了实现异步的网络图片加载.缓存及显示,支持多线程异步加载.它最初来源于Fedor Vlasov的项目,且自此之后,经过大规模的重构和改进. 特性列举: 多线程下载图片,图片可以来源于网络,文件系统,项目文件夹assets中以及drawable中等 支持随意的配置ImageLoader,例如

  • 从源代码分析Android Universal ImageLoader的缓存处理机制

    通过本文带大家一起看过UIL这个国内外大牛都追捧的图片缓存类库的缓存处理机制.看了UIL中的缓存实现,才发现其实这个东西不难,没有太多的进程调度,没有各种内存读取控制机制.没有各种异常处理.反正UIL中不单代码写的简单,连处理都简单.但是这个类库这么好用,又有这么多人用,那么非常有必要看看他是怎么实现的.先了解UIL中缓存流程的原理图. 原理示意图 主体有三个,分别是UI,缓存模块和数据源(网络).它们之间的关系如下: ① UI:请求数据,使用唯一的Key值索引Memory Cache中的Bit

  • 全面解析Android的开源图片框架Universal-Image-Loader

    相信大家平时做Android应用的时候,多少会接触到异步加载图片,或者加载大量图片的问题,而加载图片我们常常会遇到许多的问题,比如说图片的错乱,OOM等问题,对于新手来说,这些问题解决起来会比较吃力,所以就有很多的开源图片加载框架应运而生,比较著名的就是Universal-Image-Loader,相信很多朋友都听过或者使用过这个强大的图片加载框架,今天这篇文章就是对这个框架的基本介绍以及使用,主要是帮助那些没有使用过这个框架的朋友们.该项目存在于Github上面https://github.c

  • Android ImageLoader第三方框架解析

    本文实例为大家分享了Android ImageLoader框架的使用方法,供大家参考,具体内容如下 1.准备工作 1)导入universal-image-loader-1.9.5.jar到项目中 2)创建MyApplication继承Application,在oncreate()中初始化ImageLoader public class MyApplication extends Application { @Override public void onCreate() { super.onCr

  • Android开发之ImageLoader本地缓存

    ImageLoader是一个图片缓存的开源库,提供了强大的图片缓存机制,很多开发者都在使用,今天给大家介绍Android开发之ImageLoader本地缓存,具体内容如下所示: 本地缓存在缓存文件时对文件名称的修改提供了两种方式,每一种方式对应了一个Java类 1) HashCodeFileNameGenerator ,该类负责获取文件名称的hashcode然后转换成字符串. 2) Md5FileNameGenerator ,该类把源文件的名称同过md5加密后保存. 两个类都继承了FileNam

  • android CursorLoader用法介绍

    工作内容集中到Contact模块,这个应用查询数据的地方很多,其使用了CursorLoader这个工具大大简化了代码复杂度.android自3.0提供了Loader机制,当时google的API只是简单的介绍了一下没有给出用法,大家很少有关注.后来因为重度模型下的性能优化,R&D的朋友发现这个东西非常给力,这才开始注意到这个强大的工具.CursorLoader是Loader的子类,可以说是Loader的升级版.这篇小结以loader为基础说明,弄懂原理之后也就明白了CursorLoader.先说

  • Android 开发 使用WebUploader解决安卓微信浏览器上传图片中遇到的bug

    先给大家分析下微信浏览器上传图片bug的原因 微信在新版本中采用的是自己的X5内核浏览器,而在较老的版本中还有可能是安卓的原生浏览器.具体的环境我也不太了解,但是经过实际多台安卓机型的测试,我采取的方案可以基本确保在安卓机中微信浏览器的成功上传.苹果机型没问题,因为微信的ios客户端使用的是Safari的内核,没有各种坑,且效果最好. 这里给出一个 WebUploader 官方关于移动端适配的 issues 链接.里面提供的方法确实有效,但就是解决的方案并没有很清楚的展示出来,从该issues中

  • Android利用CursorLoader实现短信验证码自动填写

    概述 Android上实现短信验证码自动填写,常用的有两种方式.一种是利用BroadCastReceiver,还有一种是监听手机上短信数据库的变化.利用BroadCastReceiver来实现会在一些情况下无效,最常见的就是手机上安装了具有垃圾短信拦截功能的软件的情况下,短信验证码自动填写无效.所以,现在一般会选用监听数据库内容变化的方式来实现短信验证码自动填写. 网上对于利用监听数据库内容变化来实现短信验证码自动填写的文章也很多,主要分为一下步骤: 1. 继承ContentObserver实现

  • Android Loader详细介绍及实例代码

    一,Android装载器基本方法 装载器从android3.0开始引进.它使得在activity或fragment中异步加载数据变得简单.装载器具有如下特性: 它们对每个Activity和Fragment都有效. 他们提供了异步加载数据的能力. 它们监视数据源的一将一动并在内容改变时传送新的结果. 当由于配置改变而被重新创建后,它们自动重连到上一个加载器的游标,所以不必重新查询数据. 装载器API概述 在使用装载器时,会涉及很多类和接口们,我们在下表中对它们总结一下: Class/Interfa

随机推荐