Loader使用及异步支持

一、简介

很多时候,我们可能想在Activity或Fragment中加载数据,例如使用ContentProvider获取数据库数据。显然,类似ContenProvider从数据库中读取数据多数是耗时行为,不能在主线程中完成加载因为这会阻塞主线程。Android 3.0后,提供了Loader加载器,可以轻松的在Activity或Fragment中异步加载数据。Loader具有以下特性:

  • 可用于每个 ActivityFragment

  • 支持异步加载数据。

  • 监控其数据源并在内容变化时传递新结果。

  • 在某一配置更改后重建加载器时,会自动重新连接上一个加载器的游标。 因此,它们无需重新查询其数据。

此外,Loader和Activity或Fragment的生命周期绑定,Activity或Fragment管理LoaderManager,而LoaderManager又管理着Loader。在它们生命周期结束时(onDestroy),它们会把Loader也Destroy。并且Loader不能作为内部类的对象初始化,因为内部类会持有外部Activity/Fragment的引用,造成内存泄漏。

二、使用

常用的Loader API有:

Loader API 摘要

在应用中使用加载器时,可能会涉及到多个类和接口。 下表汇总了这些类和接口:

LoaderManager

|

一种与 ActivityFragment 相关联的的抽象类,用于管理一个或多个 Loader 实例。 这有助于应用管理与ActivityFragment 生命周期相关联的、运行时间较长的操作。它最常见的用法是与 CursorLoader 一起使用,但应用可自由写入其自己的加载器,用于加载其他类型的数据。

每个 Activity 或片段中只有一个 LoaderManager。但一个 LoaderManager 可以有多个加载器。

|
|

LoaderManager.LoaderCallbacks

|

一种回调接口,用于客户端与 LoaderManager 进行交互。例如,您可使用 onCreateLoader() 回调方法创建新的加载器。

|
|

Loader

|

一种执行异步数据加载的抽象类。这是加载器的基类。 您通常会使用 CursorLoader,但您也可以实现自己的子类。加载器处于活动状态时,应监控其数据源并在内容变化时传递新结果。

|
|

AsyncTaskLoader

|

提供 AsyncTask 来执行工作的抽象加载器。

|
|

CursorLoader

|

AsyncTaskLoader 的子类,它将查询 ContentResolver 并返回一个 Cursor。此类采用标准方式为查询游标实现 Loader 协议。它是以 AsyncTaskLoader 为基础而构建,在后台线程中执行游标查询,以免阻塞应用的 UI。使用此加载器是从 ContentProvider 异步加载数据的最佳方式,而不用通过片段或 Activity 的 API 来执行托管查询。

|

LoaderManager.LoaderCallbacks中通常由用户实现,onCreateLoader方法返回想要使用的Loader实例,onLoadFinished为Loader加载数据完成后的回调函数,onLoaderReset为Loader重置时的初始化函数。

以下是一个使用Loader异步读取手机中截图图片路径的例子:

回调类PictureLoaderCallback.java:

package com.meituan.huangdanyang.practise;

import android.content.Context;
import android.database.Cursor;
import android.os.Bundle;
import android.provider.MediaStore;
import android.support.annotation.NonNull;
import android.support.v4.app.LoaderManager;
import android.support.v4.content.CursorLoader;
import android.support.v4.content.Loader;
import android.util.Log;

import java.util.ArrayList;
import java.util.List;

public class PictureLoaderCallback implements LoaderManager.LoaderCallbacks<Cursor> {
    private Context context;
    private OnLoadFinishListener onLoadFinishListener;
    private List<String> res;
    private Long last;
    public PictureLoaderCallback(Context context) {
        this.context = context;
    }

    public PictureLoaderCallback(Context context, OnLoadFinishListener onLoadFinishListener) {
        this.context = context;
        this.onLoadFinishListener = onLoadFinishListener;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        Log.i("开始时间",System.currentTimeMillis() + "");
        last = System.currentTimeMillis();
        return new CursorLoader(context, MediaStore.Images.Media.EXTERNAL_CONTENT_URI,null,
                MediaStore.Images.Media.MIME_TYPE + "=? or "
                        + MediaStore.Images.Media.MIME_TYPE + "=?",
                new String[] {"image/jpeg", "image/png"},
                MediaStore.Images.Media.DATE_MODIFIED
        );
    }

    @Override
    public void onLoadFinished(@NonNull Loader<Cursor> loader, Cursor data) {
        if (data == null) {
            return;
        }
        Log.i("返回时间",(System.currentTimeMillis() - last) + "");
        last = System.currentTimeMillis();
        res = new ArrayList<>();
        data.moveToPrevious();
        while (data.moveToNext()) {
          /* for (int i = 0;i < data.getColumnCount();i++) {
               System.out.print(data.getString(i) + " ");
           }*/
          if (data.getString(1).matches(".*Screenshots.*")) {
              res.add(data.getString(1));
          }
          /* System.out.println();*/
        }
        Log.i("结束时间",System.currentTimeMillis() - last + "");
        if (onLoadFinishListener != null) {
            onLoadFinishListener.onComplete(res);
        }
    }

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

    }

}

在Fragment中初始化Loader及使用Loader加载完成后的数据:

public class HomeFragment extends BaseFragment implements OnLoadFinishListener<String>{

    @Override
    public void onActivityCreated(@Nullable Bundle savedInstanceState) {
        super.onActivityCreated(savedInstanceState);
        getLoaderManager().initLoader(0, null, new PictureLoaderCallback(getContext(),
                this));
    }

    @Override
    public void onComplete(List<String> data) {
        for (String item:data) {
           Log.i("ITEM",item);
        }
    }
}

这样在Loader加载完成时,调用onLoadFinished方法再调用Fragment传入的OnLoadFinishListener的onComplete方法,Fragment中就拿到了加载到的数据。

三、异步机制

Loader中是怎么支持异步加载的呢?

AsyncTaskLoader是支持异步加载的Loader的抽象类。上面代码中的CursorLoader就是AsyncTaskLoader的子类。

AsyncTaskLoader是怎么支持异步的?如它的名字一般,AsyncTaskLoader异步实现是依靠Android的AsyncTask。

AsyncTaskLoader有一个继承自AsyncTask的内部类:

final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable {
        private final CountDownLatch mDone = new CountDownLatch(1);

        // Set to true to indicate that the task has been posted to a handler for
        // execution at a later time.  Used to throttle updates.
        boolean waiting;

        /* Runs on a worker thread */
        @Override
        protected D doInBackground(Void... params) {
            if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
            try {
                D data = AsyncTaskLoader.this.onLoadInBackground();
                if (DEBUG) Log.v(TAG, this + "  <<< doInBackground");
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    // onLoadInBackground threw a canceled exception spuriously.
                    // This is problematic because it means that the LoaderManager did not
                    // cancel the Loader itself and still expects to receive a result.
                    // Additionally, the Loader's own state will not have been updated to
                    // reflect the fact that the task was being canceled.
                    // So we treat this case as an unhandled exception.
                    throw ex;
                }
                if (DEBUG) Log.v(TAG, this + "  <<< doInBackground (was canceled)", ex);
                return null;
            }
        }

        /* Runs on the UI thread */
        @Override
        protected void onPostExecute(D data) {
            if (DEBUG) Log.v(TAG, this + " onPostExecute");
            try {
                AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
            } finally {
                mDone.countDown();
            }
        }

        /* Runs on the UI thread */
        @Override
        protected void onCancelled(D data) {
            if (DEBUG) Log.v(TAG, this + " onCancelled");
            try {
                AsyncTaskLoader.this.dispatchOnCancelled(this, data);
            } finally {
                mDone.countDown();
            }
        }

        /* Runs on the UI thread, when the waiting task is posted to a handler.
         * This method is only executed when task execution was deferred (waiting was true). */
        @Override
        public void run() {
            waiting = false;
            AsyncTaskLoader.this.executePendingTask();
        }

        /* Used for testing purposes to wait for the task to complete. */
        public void waitForLoader() {
            try {
                mDone.await();
            } catch (InterruptedException e) {
                // Ignore
            }
        }
    }

doInBackgound中调用了AsyncTaskLoader.this.onLoadInBackground(),这里就是真正执行耗时操作的地方,由子类实现抽象类AsyncTaskLoader的接口。联系CursorLoader的源码:

 @Override
    public Cursor loadInBackground() {
        synchronized (this) {
            if (isLoadInBackgroundCanceled()) {
                throw new OperationCanceledException();
            }
            mCancellationSignal = new CancellationSignal();
        }
        try {
            Cursor cursor = ContentResolverCompat.query(getContext().getContentResolver(),
                    mUri, mProjection, mSelection, mSelectionArgs, mSortOrder,
                    mCancellationSignal);
            if (cursor != null) {
                try {
                    // Ensure the cursor window is filled.
                    cursor.getCount();
                    cursor.registerContentObserver(mObserver);
                } catch (RuntimeException ex) {
                    cursor.close();
                    throw ex;
                }
            }
            return cursor;
        } finally {
            synchronized (this) {
                mCancellationSignal = null;
            }
        }
    }

CursorLoader实现了AsynTaskLoader的loadInBackground方法,很清楚,在这个方法里调用ContentResolverCompat的query方法进行查询操作。所以,这个耗时的查询操作将在子线程中执行。

执行完成后,根据AsyncTask的机制,调用onPostExecute方法:从而 AsyncTaskLoader.this.dispatchOnLoadComplete(this, data):

 void dispatchOnLoadComplete(LoadTask task, D data) {
        if (mTask != task) {
            if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
            dispatchOnCancelled(task, data);
        } else {
            if (isAbandoned()) {
                // This cursor has been abandoned; just cancel the new data.
                onCanceled(data);
            } else {
                commitContentChanged();
                mLastLoadCompleteTime = SystemClock.uptimeMillis();
                mTask = null;
                if (DEBUG) Log.v(TAG, "Delivering result");
                deliverResult(data);
            }
        }
    }

没有异常的情况下,调用deliverResult(data);把结果分发下去。这是个Loader中就有的方法,看看CursorLoader重写的deliverResult(data)方法:

 @Override
    public void deliverResult(Cursor cursor) {
        if (isReset()) {
            // An async query came in while the loader is stopped
            if (cursor != null) {
                cursor.close();
            }
            return;
        }
        Cursor oldCursor = mCursor;
        mCursor = cursor;

        if (isStarted()) {
            super.deliverResult(cursor);
        }

        if (oldCursor != null && oldCursor != cursor && !oldCursor.isClosed()) {
            oldCursor.close();
        }
 }

其实还是调用了祖先Loader的方法。

  public void deliverResult(D data) {
        if (mListener != null) {
            mListener.onLoadComplete(this, data);
        }
    }

这是个观察者模式,也就是说,观察者观察被观察者数据的改变作出反应。

至于mListener从哪来的,这就由LoadManagerImpl管理了。

LoadManagerImpl的静态内部类给Loader的mListener赋值:

    public static class LoaderInfo<D> extends MutableLiveData<D>
            implements Loader.OnLoadCompleteListener<D> {

        private final int mId;
        private final @Nullable Bundle mArgs;
        private final @NonNull Loader<D> mLoader;
        private LifecycleOwner mLifecycleOwner;
        private LoaderObserver<D> mObserver;
        private Loader<D> mPriorLoader;

        LoaderInfo(int id, @Nullable Bundle args, @NonNull Loader<D> loader,
                @Nullable Loader<D> priorLoader) {
            mId = id;
            mArgs = args;
            mLoader = loader;
            mPriorLoader = priorLoader;
            mLoader.registerListener(id, this);
        }
        ...
           @Override
        public void onLoadComplete(@NonNull Loader<D> loader, @Nullable D data) {
            if (DEBUG) Log.v(TAG, "onLoadComplete: " + this);
            if (Looper.myLooper() == Looper.getMainLooper()) {
                setValue(data);
            } else {
                // The Loader#deliverResult method that calls this should
                // only be called on the main thread, so this should never
                // happen, but we don't want to lose the data
                if (DEBUG) {
                    Log.w(TAG, "onLoadComplete was incorrectly called on a "
                            + "background thread");
                }
                postValue(data);
            }
        }

        @Override
        public void setValue(D value) {
            super.setValue(value);
            // Now that the new data has arrived, we can reset any prior Loader
            if (mPriorLoader != null) {
                mPriorLoader.reset();
                mPriorLoader = null;
            }
        }
}

这个LoadInfo,继承自LiveData,而LiveData又是Android提供的一种观察者模式的数据存储,它也能与UI控件的生命周期绑定,从而不会产生内存泄漏。在数据改变时通知观察者。这里就不再说LiveData了

LiveData的官方文档:https://developer.android.com/topic/libraries/architecture/livedata

既然是观察者模式,那么观察者在哪呢,其实就在这:

 Loader<D> setCallback(@NonNull LifecycleOwner owner,
                @NonNull LoaderCallbacks<D> callback) {
            LoaderObserver<D> observer = new LoaderObserver<>(mLoader, callback);
            // Add the new observer
            observe(owner, observer);
            // Loaders only support one observer at a time, so remove the current observer, if any
            if (mObserver != null) {
                removeObserver(mObserver);
            }
            mLifecycleOwner = owner;
            mObserver = observer;
            return mLoader;
        }

也就是说,观察者对于数据改变产生的“反应”,其实主要就是LoaderCallbacks中的回调函数。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,634评论 6 497
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 91,951评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,427评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,770评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,835评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,799评论 1 294
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,768评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,544评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,979评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,271评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,427评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,121评论 5 340
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,756评论 3 324
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,375评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,579评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,410评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,315评论 2 352