美文网首页
Loader使用及异步支持

Loader使用及异步支持

作者: hdychi | 来源:发表于2018-08-01 16:30 被阅读0次

    一、简介

    很多时候,我们可能想在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中的回调函数。

    相关文章

      网友评论

          本文标题:Loader使用及异步支持

          本文链接:https://www.haomeiwen.com/subject/lcervftx.html