Android 3.0引入的异步加载机制Loader

Loader是谷歌在Android 3.0引入的异步加载机制,能够对数据异步加载并显示到Activity或Fragment上,使用者不需要对数据的生命周期进行管理,而是交给Loader机制来管理。

使用Loader的优点

假如我们需要从网络上获取数据,通常的做法是使用子线程Thread+Handler或者是使用AsyncTask来处理。

Thread+Handler方法实现起来简单直观,不过会麻烦点,需要自己实现Handler子类,创建线程,还要管理Handler的生命周期。

AsyncTask实现起来会简单些,无需自己管理线程和Handler。但是要管理AsyncTask的生命周期,要对Activity退出时的情况进行处理。否则可能会出现异常或内存泄露。

使用Loader无需关心线程和Handler的创建和销毁,也无需自己管理数据整个的生命周期,Loader机制会自动帮我们处理好。我们唯一要处理的就是数据本身。

Loader使用的步骤:

创建FragmentActivity或Fragment 持有LoaderManager的实例实现Loader,用来加载数据源返回的数据实现LoaderManager.LoaderCallbacks接口实现数据的展示提供数据的数据源,如ContentProvider,服务器下发的数据等 几个相关的类 LoaderManager

管理Loader实例,并使之和FragmentActiivty或Fragment关联上

一个Activity或Fragment有一个唯一的LoaderManager实例

一个LoaderManager实例可以管理多个Loader实例

可以在FragmentActivity或Fragmeng中使用getSupportLoaderManager()获取到LoaderManager实例

可以使用 initLoader() 或 restartLoader() 方法开始进行数据的加载

//0,为唯一的ID,可以为任意整数,为Loader的唯一标识
//null,为Bundle类型,可以向Loader传递构造参数
//LoaderCallbacks,LoaderManager对Loader各事件的调用,参考下面讲到的 LoaderManager.LoaderCallbacks
getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<D>());

LoaderManager.LoaderCallbacks

LoaderManager对Loader各种情况的回调接口,包含三个回调方法

onCreateLoader(int,Bundle)
在这里需要自己创建Loader对象,int 为Loader的唯一标识,Bundle为Loader的构造参数,可为空

...
new LoaderManager.LoaderCallbacks<String>() {
      @Override
      public Loader<String> onCreateLoader(int id, Bundle args) {
        return new MyLoader();
      }
      ...
}

onLoadFinished(Loader<D>,D)
当LoaderManager加载完数据时回调此方法,在这里用UI展示数据给用户。D为泛型,根据实际情况设置为所需的数据类型。和initLoader()LoaderCallbacks<D>参数中的的泛型为同一类型

new LoaderManager.LoaderCallbacks<String>() {
      ...
      @Override
      public void onLoadFinished(Loader<String> loader, String data) {
          show(data);
      }
      ...
}

onLoaderReset(Loader<D>)
当之前创建的Loader实例被重置的时候会回调此方法,此时需要对相关的数据进行清除处理

new LoaderManager.LoaderCallbacks<String>() {
      ...
      @Override
      public void onLoaderReset(Loader<String> loader) {
          show(null);
      }
      ...
}

Loader

从数据源获取数据,并对数据进行加载,为抽象类,需要自己实现子类

或使用官方已经实现的两个子类

AsyncTaskLoader(继承此类的时候会遇到一个坑,见下面的分析)
处理异步获取数据 CursorLoader
处理ContentProvider返回的数据 实现AsyncTaskLoader遇到的一个坑

首先自定义一个 MyAsyncTaskLoader,继承AsyncTaskLoader,会发现需要实现参数为Context的构造方法和实现 loadInBackground() 抽象方法

//继承AsyncTaskLoader类,里面的泛型为返回的数据的类型,这里设为String
public class MyAsyncTaskLoader extends AsyncTaskLoader<String>{

  public MyAsyncTaskLoader(Context context) {
    super(context);
  }

  @Override
   public String loadInBackground() {
    //模拟加载
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //返回获取到的数据
    return new String("MyAsyncTaskLoader Test Result");
  }
}

创建FragmentActivity

public class BaseActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks{
  @Override
  protected void onCreate(@Nullable Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);
    setContentView(R.layout.base_activity_layout);
//    addFragment();
    log("onCreate");
    loadData();
  }

  protected void loadData(){
    Log.e(getClassName(),"call");
    getSupportLoaderManager().initLoader(0, null, this);
  }

  protected String getClassName(){
    return getClass().getSimpleName();
  }

  @Override
  public Loader onCreateLoader(int id, Bundle args) {
    Log.e(getClassName(),"onCreateLoader");
    return new MyAsyncTaskLoader(BaseActivity.this);
  }

  @Override
  public void onLoadFinished(Loader loader, Object data) {
    Log.e(getClassName(),"data:"+data);
  }

  @Override
  public void onLoaderReset(Loader loader) {

  }
}

当运行的时候发现日志值打印了onCreate,call,onCreateLoader,而预期中的 MyAsyncTaskLoader Test Result 并没有输出,也就是说 onLoadFinished 并未被回调。调试发现 MyAsyncTaskLoader 中的 loadInBackground() 方法也未执行。

这个是怎么回事呢?

那么只好查看源码了,这里所使用的都是 support-v4 的包。

查看 AsyncTaskLoader 源码发现 loadInBackground() 方法的确为 abstract 类型,其被调用的地方是在一个叫做 LoadTask 的内部类中。

//可以把 ModernAsyncTask 看做 AsyncTask

final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable {
    ....
    @Override
    protected D doInBackground(Void... params) {
       ...
        D data = AsyncTaskLoader.this.onLoadInBackground();
       ...
    }
   .....
  }

并且作为AsyncTaskLoader的一个全局变量。

public abstract class AsyncTaskLoader<D> extends Loader<D> {
....
volatile LoadTask mTask;
....
}

mTask 实例化和被执行的地方在 onForceLoad() 方法里

...
  @Override
  protected void onForceLoad() {
    ...
    mTask = new LoadTask();
    ...
    executePendingTask();
  }
  ...
  void executePendingTask() {
    ...
      if (mUpdateThrottle > 0) {
        ...
          mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
          return;
        }
      }
      ...
      mTask.executeOnExecutor(mExecutor, (Void[]) null);
    }
  }

mHandler.postAtTime 或者是 mTask.executeOnExecutor 这两个地方就是执行 TaskLoader 的地方,并会调用到 doInBackground() 方法。

那么到这里我们可以猜测我们自定义的 MyAsyncLoader 的 loadInBackground() 未被执行,那么 onForceLoad() 也应该未被执行。

沿着这条线索查找看看这个 onForceLoad() 是在哪里被调用的。发现其是在AsyncLoader 的父类 Loader 中的 forceLoad() 中被调用

public class Loader{
...
  public void forceLoad() {
    onForceLoad();
  }
...
}

然后又看到注释发现,此方法只能在 loader 开始的时候调用,还是找不到什么头绪。

突然想到好像 CursorLoader 没有这个问题,那么看看它是不是有调用 forceLoad(),找了下,发现还果然有!是在 onStartLoading() 这个方法里,并且只有这里调用!

public class CursorLoader extends AsyncTaskLoader<Cursor> {
  ...
  @Override
  protected void onStartLoading() {
    if (mCursor != null) {
      deliverResult(mCursor);
    }
    if (takeContentChanged() || mCursor == null) {
      forceLoad();
    }
  }
  ...
}

那么我模仿下这个看看是不是真的能行,MyAsyncLoader 的代码修改如下:

//继承AsyncTaskLoader类,里面的泛型为返回的数据的类型,这里设为String
public class MyAsyncTaskLoader extends AsyncTaskLoader<String>{

  public MyAsyncTaskLoader(Context context) {
    super(context);
  }

  //添加了这段代码
  @Override
  protected void onStartLoading() {
    forceLoad();
  }

  @Override
   public String loadInBackground() {
    //模拟加载
    try {
      Thread.sleep(3000);
    } catch (InterruptedException e) {
      e.printStackTrace();
    }
    //返回获取到的数据
    return new String("MyAsyncTaskLoader Test Result");
  }
}

运行后发现真的能够输出了!看来问题是解决了。

最后一行为输出的结果

问题是解决了,但是还是有一个疑问,这个 onStartLoading()是在哪里被调用的呢?看来还是得看看源码。

从 getSupportLoaderManager().initLoader(0, null, this) 开始分析,发现最后是会调用到 onStartLoading()。

简记如下,可自己对照着源码查看:

LoaderManager的实现类为LoaderManagerImpl
init()方法里面创建 LoaderInfo info
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback); 进入 createAndInstallLoader 方法
mCallbacks.onLoadFinished(loader, data); 进入 onLoadFinished 方法
createLoader(id, args, callback) 进入 createLoader 方法
installLoader(info); 进入 installLoader 方法
info.start(); 进入 start 方法
mLoader.startLoading(); 进入 startLoading 方法
onStartLoading();
(0)

相关推荐

  • Android使用缓存机制实现文件下载及异步请求图片加三级缓存

    首先给大家介绍Android使用缓存机制实现文件下载 在下载文件或者在线浏览文件时,或者为了保证文件下载的正确性,需要使用缓存机制,常使用SoftReference来实现. SoftReference的特点是它的一个实例保存对一个Java对象的软引用,该软引用的存在不妨碍垃圾收集线程对该Java对象的回收.也就是说,一旦SoftReference保存了对一个Java对象的软引用后,在垃圾线程对这个Java对象回收前,SoftReference类所提供的get()方法返回Java对象的强引用.另外

  • 深入理解Android中的Handler异步通信机制

    一.问题:在Android启动后会在新进程里创建一个主线程,也叫UI线程(非线程安全)这个线程主要负责监听屏幕点击事件与界面绘制.当Application需要进行耗时操作如网络请求等,如直接在主线程进行容易发生ANR错误.所以会创建子线程来执行耗时任务,当子线程执行完毕需要通知UI线程并修改界面时,不可以直接在子线程修改UI,怎么办? 解决方法:Message Queue机制可以实现子线程与UI线程的通信. 该机制包括Handler.Message Queue.Looper.Handler可以把

  • Android异步消息机制详解

    Android中的异步消息机制分为四个部分:Message.Handler.MessageQueue和Looper. 其中,Message是线程之间传递的消息,其what.arg1.arg2字段可以携带整型数据,obj字段可以携带一个Object对象. Handler是处理者,主要用于发送消息和处理消息.发送消息的方法是sendMessage:处理消息的方法是handleMessage(),Message字段携带的信息在该方法中用作判别. MessageQueue是消息队列,存放所有Handle

  • Android 3.0引入的异步加载机制Loader

    Loader是谷歌在Android 3.0引入的异步加载机制,能够对数据异步加载并显示到Activity或Fragment上,使用者不需要对数据的生命周期进行管理,而是交给Loader机制来管理. 使用Loader的优点 假如我们需要从网络上获取数据,通常的做法是使用子线程Thread+Handler或者是使用AsyncTask来处理. Thread+Handler方法实现起来简单直观,不过会麻烦点,需要自己实现Handler子类,创建线程,还要管理Handler的生命周期. AsyncTask

  • Android实现图片缓存与异步加载

    ImageManager2这个类具有异步从网络下载图片,从sd读取本地图片,内存缓存,硬盘缓存,图片使用动画渐现等功能,已经将其应用在包含大量图片的应用中一年多,没有出现oom. Android程序常常会内存溢出,网上也有很多解决方案,如软引用,手动调用recycle等等.但经过我们实践发现这些方案,都没能起到很好的效果,我们的应用依然会出现很多oom,尤其我们的应用包含大量的图片.android3.0之后软引用基本已经失效,因为虚拟机只要碰到软引用就回收,所以带不来任何性能的提升. 我这里的解

  • Android AsyncTask 后监听异步加载完毕的动作详解

    Android 使用AsyncTask 后监听异步加载完毕的动作 AsyncTask 的使用方法网上有很多例子,使用起来也非常的方便.这里就不详细说具体的使用方法了,同学可以Google 一下,很多. 场景模拟 当我们在加载一个列表的时候,比如GridView ,这时候我们考虑到不阻塞UI的做法,一般会使用线程Thread .Timer 或者使用AsyncTask ,而这些操作都是在在后台另外开一个线程给我们找数据,具体得到的数据需要使用Handler 去更新UI,AsyncTask 也是一样使

  • Android App中实现图片异步加载的实例分享

    一.概述 一般大量图片的加载,比如GridView实现手机的相册功能,一般会用到LruCache,线程池,任务队列等:那么异步消息处理可以用哪呢? 1.用于UI线程当Bitmap加载完成后更新ImageView 2.在图片加载类初始化时,我们会在一个子线程中维护一个Loop实例,当然子线程中也就有了MessageQueue,Looper会一直在那loop停着等待消息的到达,当有消息到达时,从任务队列按照队列调度的方式(FIFO,LIFO等),取出一个任务放入线程池中进行处理. 简易的一个流程:当

  • Android中使用二级缓存、异步加载批量加载图片完整案例

    一.问题描述 Android应用中经常涉及从网络中加载大量图片,为提升加载速度和效率,减少网络流量都会采用二级缓存和异步加载机制,所谓二级缓存就是通过先从内存中获取.再从文件中获取,最后才会访问网络.内存缓存(一级)本质上是Map集合以key-value对的方式存储图片的url和Bitmap信息,由于内存缓存会造成堆内存泄露, 管理相对复杂一些,可采用第三方组件,对于有经验的可自己编写组件,而文件缓存比较简单通常自己封装一下即可.下面就通过案例看如何实现网络图片加载的优化. 二.案例介绍 案例新

  • 如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据(附源码下载)

    ECharts地图主要用于地理区域数据的可视化,展示不同区域的数据分布信息.ECharts官网提供了中国地图.世界地图等地图数据下载,通过js引入或异步加载json文件的形式调用地图. 效果演示      源码下载 本文将结合实例讲解如何使用PHP+jQuery+MySQL实现异步加载ECharts地图数据,我们以中国地图为例,展示去年(2015年)我国各省份GDP数据.通过异步请求php,读取mysql中的数据,然后展示在地图上,因此本文除了你掌握前端知识外,还需要你了解PHP以及MySQL方

  • Android 异步加载图片分析总结

    研究了android从网络上异步加载图像,现总结如下: (1)由于android UI更新支持单一线程原则,所以从网络上取数据并更新到界面上,为了不阻塞主线程首先可能会想到以下方法. 在主线程中new 一个Handler对象,加载图像方法如下所示 复制代码 代码如下: private void loadImage(final String url, final int id) { handler.post(new Runnable() { public void run() { Drawable

  • Android实现从缓存中读取图片与异步加载功能类

    本文实例讲述了Android实现从缓存中读取图片与异步加载功能类.分享给大家供大家参考,具体如下: 在新浪微博的微博列表中的图片,为了加速其显示也为了加快程序的响应,可以参考该图片异步加载类实现. public class AsyncImageLoader { //SoftReference是软引用,是为了更好的为了系统回收变量 private HashMap<String, SoftReference<Drawable>> imageCache; public AsyncImag

  • android异步加载图片并缓存到本地实现方法

    在android项目中访问网络图片是非常普遍性的事情,如果我们每次请求都要访问网络来获取图片,会非常耗费流量,而且图片占用内存空间也比较大,图片过多且不释放的话很容易造成内存溢出.针对上面遇到的两个问题,首先耗费流量我们可以将图片第一次加载上面缓存到本地,以后如果本地有就直接从本地加载.图片过多造成内存溢出,这个是最不容易解决的,要想一些好的缓存策略,比如大图片使用LRU缓存策略或懒加载缓存策略.今天首先介绍一下本地缓存图片. 首先看一下异步加载缓存本地代码: 复制代码 代码如下: public

  • 在Android的应用中实现网络图片异步加载的方法

    前言 其实很幸运,入职一周之后就能跟着两个师兄做android开发,师兄都是大神,身为小白的我只能多多学习,多多努力.最近一段时间都忙的没机会总结,今天刚完成了android客户端图片异步加载的类,这里记录一下(ps:其实我这里都是参考网上开源实现) 原理 在ListView或者GridView中加载图片的原理基本都是一样的: 先从内存缓存中获取,取到则返回,取不到进行下一步     从文件缓存中获取,取到则返回并更新到内存缓存,取不到则进行进行下一步     从网络上下载图片,并更新内存缓存和

随机推荐