Android进阶——自定义Loader以一种更优美的方式异步加载不同类型的数据(二)

引言

前一篇文章中Android进阶——借助Loader机制以一种更优美的方式异步加载数据(一)概述了Loader我们借助了系统提供的CursorLoader实现了把通讯录的联系人名字加载到列表中,显而易见这种形式只是针对加载经过ContentProvider封装的数据类型,而实际的应用中我们肯定会遇到各种各样的数据类型,所以仅仅使用系统提供的远远不足以完成需求,实际开发中我们也是更多的使用自定义的Loader实现各种各样数据的异步加载。

一、Loader和AsyncTaskLoader

通常我们自定义Loader的话只需要直接继承AsyncTaskLoader几乎就可以实现各种类型数据的加载了,当然如果你想继承Loader的话也可以,工作会繁杂些,而Loader作为Loader机制中所有Loader的基类,AsyncTaskLoader也是直接继承自Loader的,所以有必要再分解一遍各自的逻辑。

1、Loader

public class Loader<D> {
    OnLoadCompleteListener<D> mListener;
    OnLoadCanceledListener<D> mOnLoadCanceledListener;
    boolean mStarted = false;
    boolean mAbandoned = false;
    boolean mReset = true;
    boolean mContentChanged = false;
    boolean mProcessingChange = false;
    
    /**当加载器完成数据加载时被创建Loader的线程调用,可以用于监听加载器何时完成数据加载。 Called on *the thread that created the Loader when the load is complete.
     *Parameters:loader the loader that completed the load data the result of the load
    */
    public interface More ...OnLoadCompleteListener<D> {
        public void More ...onLoadComplete(Loader<D> loader, D data);
    }

  public final class ForceLoadContentObserver extends ContentObserver {
        public ForceLoadContentObserver() {
            super(new Handler());
        }

        @Override
        public boolean deliverSelfNotifications() {
            return true;
        }

        @Override
        public void onChange(boolean selfChange) {
            onContentChanged();
        }
    }
    
    //传递数据至对应的监听器
    public void deliverResult(D data) {
        if (mListener != null) {
            mListener.onLoadComplete(this, data);
        }
    }

    public void deliverCancellation() {
        if (mOnLoadCanceledListener != null) {
            mOnLoadCanceledListener.onLoadCanceled(this);
        }
    }

    //在startLoading执行完,且没有调用stopLoading或reset。
    public boolean isStarted() {
        return mStarted;
    }

    //在该状态下,不应该上报新的数据,并且应该保持着最后一次上报的数据直到被reset。
    public boolean isAbandoned() {
        return mAbandoned;
    }

    //没有启动,或者调用了reset方法。
    public boolean isReset() {
        return mReset;
    }
    
    //开始任务。    
    public final void startLoading() {
        mStarted = true;
        mReset = false;
        mAbandoned = false;
        onStartLoading();
    }
    
    //开始任务后回调该方法,调用者重写该方法来加载数据。
    protected void onStartLoading() {}
 
   /**取消任务。返回 false 表示不能取消,有可能是已经完成,或者 startLoading 还没有被调用。
   *这不是一个立刻的过程,因为加载是在后台线程中运行的。
   */
   public boolean cancelLoad() {
        return onCancelLoad();
    }
    
    //调用者重写该方法来做取消后的操作。
    protected boolean onCancelLoad() {
        return false;
    }
 
    //强制刷新数据
    public void forceLoad() {
        onForceLoad();
    }

    protected void onForceLoad() {}

    //停止任务
    public void stopLoading() {
        mStarted = false;
        onStopLoading();
    }

    protected void onStopLoading() {}
  
    //废弃任务
    public void abandon() {
        mAbandoned = true;
        onAbandon();
    }

    protected void onAbandon() {}

    public void reset() {
        onReset();
        mReset = true;
        mStarted = false;
        mAbandoned = false;
        mContentChanged = false;
        mProcessingChange = false;
    }

    protected void onReset() {}
    
    //得到mContentChanged的值,并把mContentChanged设为false
    public boolean takeContentChanged() {
        boolean res = mContentChanged;
        mContentChanged = false;
        mProcessingChange |= res;
        return res;
    }
    
    //表明正在处理变化。
    public void commitContentChanged() {
        mProcessingChange = false;
    }
    
    //如果正在处理变化,那么停止它,并且把mContentChanged设为true。
    public void rollbackContentChanged() {
        if (mProcessingChange) {
            mContentChanged = true;
        }
    }

    //如果当前是start状态,那么收到变化的通知就立即重新加载,否则记录下这个标志mContentChanged。
    public void onContentChanged() {
        if (mStarted) {
            forceLoad();
        } else {
            mContentChanged = true;
        }
    }
    ...
}
接口/类名 说明
class Loader.ForceLoadContentObserver 一个内容监视器的实现,负责将其连接到加载器,让加载器在观察者被告知已经改变时重新加载它的数据。
interface Loader.OnLoadCanceledListener< D > Loader完成加载数据之前,当Loader被取消之时,被创建Loader的线程主动调用,可监听Loader在加载数据完成之前何时被取消。
interface Loader.OnLoadCompleteListener< D > 在Loader完成加载数据之时被创建Loader的线程主动调用,可监听Loader在何时完成加载数据完

首先从源码上我们看到有三个接口和一个作为观察者角色监听数据源的内部类

接口/类名 说明
class Loader.ForceLoadContentObserver 一个内容监视器的实现,负责将其连接到加载器,让加载器在观察者被告知已经改变时重新加载它的数据。
interface Loader.OnLoadCanceledListener< D > Loader完成加载数据之前,当Loader被取消之时,被创建Loader的线程主动调用,可监听Loader在加载数据完成之前何时被取消。
interface Loader.OnLoadCompleteListener< D > 在Loader完成加载数据之时被创建Loader的线程主动调用,可监听Loader在何时完成加载数据完
重要的方法名 说明
void abandon() LoaderManager在重启Loader时会自动调用这个函数
boolean cancelLoad() 尝试取消Load,必须在主线程中调用,需要注意的是取消不是即时操作,因为加载是在后台线程执行的。 如果目前有负载正在进行,那么请求被取消;不过一旦后台线程完成其工作后尝试取消,那么剩余状态将被清除,如果此时有另一个加载请求进入,它将一直挂起直到前一个取消Load完成。返回false,则说明load不能被取消,通常原因有二:1)加载已完成 2)未调用startLoading方法;返回true,则会触发OnLoadCanceledListener接口
boolean onCancelLoad() 子类必须实现的方法,真正的尝试取消Loader,必须在主线程中调用,其他和cancelLoad方法类似
void commitContentChanged() 调用这个方法来承认已经处理了内容的改变,还有一种就是协助rollbackContentChanged()一起处理Load被取消的情况
void rollbackContentChanged() 告知已放弃takeContentChanged()返回的改变,用于处理在数据在回传至Loader前因为内容改变导致Load被取消
void deliverResult(D data) 回传加载的数据至相应的监听者
void forceLoad() 强制重载数据
final void startLoading() 当相关的Fragment/Activity正在启动时,loadermanager会自动调用这个函数,启动异步数据加载。当结果准备就绪时,回调将在主线程上调用。但当之前的加载已经完成并且依然有效时候,结果会立即传给回调

另外从源码还可以直观地发现在Loader< D >中的一个通用逻辑——Loader< D >的大部分方法仅仅是提供了一个与LoaderManager交互的接口,实际上它并没有真正地执行操作仅仅是改变一些状态变量的值,真正有意义的工作都是传递到对应的回调中(即交给子类负责在这些回调中去执行对应具体的工作)比如说在startLoading等方法之后,提供了onStartLoading,其他的方法也是如此等等。

重要的方法名 说明
void abandon() LoaderManager在重启Loader时会自动调用这个函数
boolean cancelLoad() 尝试取消Load,必须在主线程中调用,需要注意的是取消不是即时操作,因为加载是在后台线程执行的。 如果目前有负载正在进行,那么请求被取消;不过一旦后台线程完成其工作后尝试取消,那么剩余状态将被清除,如果此时有另一个加载请求进入,它将一直挂起直到前一个取消Load完成。返回false,则说明load不能被取消,通常原因有二:1)加载已完成 2)未调用startLoading方法;返回true,则会触发OnLoadCanceledListener接口
boolean onCancelLoad() 子类必须实现的方法,真正的尝试取消Loader,必须在主线程中调用,其他和cancelLoad方法类似
void commitContentChanged() 调用这个方法来承认已经处理了内容的改变,还有一种就是协助rollbackContentChanged()一起处理Load被取消的情况
void rollbackContentChanged() 告知已放弃takeContentChanged()返回的改变,用于处理在数据在回传至Loader前因为内容改变导致Load被取消
void deliverResult(D data) 回传加载的数据至相应的监听者
void forceLoad() 强制重载数据
final void startLoading() 当相关的Fragment/Activity正在启动时,loadermanager会自动调用这个函数,启动异步数据加载。当结果准备就绪时,回调将在主线程上调用。但当之前的加载已经完成并且依然有效时候,结果会立即传给回调

2、AsyncTaskLoader

一般对于异步加载数据的情况来说,Loader它更加希望自己只用处理业务的逻辑,而不用再去关心如何把耗时的任务放到异步线程中。基于此系统为我们提供了一个Loader的实现类AsyncTaskLoader< D >,里面封装了一个AsyncTask用来执行耗时操作。但是它也是一个抽象类,我们并不能直接使用它,而是让子类取实现它的loadInBackground方法去处理自己的业务逻辑

public abstract class AsyncTaskLoader<D> extends Loader<D> {
    static final String TAG = "AsyncTaskLoader";
    static final boolean DEBUG = false;

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

        /** 设置为true表示该task已发布到Handler以供稍后执行,也表明该task 被延迟执行了会触发run()方法
        *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) {
            try {
                D data = AsyncTaskLoader.this.onLoadInBackground();
                return data;
            } catch (OperationCanceledException ex) {
                if (!isCancelled()) {
                    /**
                    *onLoadInBackground抛出取消的异常不合逻辑,这是有问题的,因为这意味着LoaderManager没有取消加载程序本身,仍然期望收到结果。
                    *另外,Loader自己的状态不会被更新到反映任务被取消的事实。所以我们将这种情况视为未处理的异常。
                    */
                    throw ex;
                }
                return null;
            }
        }

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

        /* Runs on the UI thread */
        @Override
        protected void onCancelled(D data) {
            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
            }
        }
    }

    private final Executor mExecutor;

    volatile LoadTask mTask;
    volatile LoadTask mCancellingTask;

    long mUpdateThrottle;
    long mLastLoadCompleteTime = -10000;
    Handler mHandler;

    public AsyncTaskLoader(Context context) {
        this(context, AsyncTask.THREAD_POOL_EXECUTOR);
    }

    /** {@hide} */
    public AsyncTaskLoader(Context context, Executor executor) {
        super(context);
        mExecutor = executor;
    }

    /**
     *设置倒计时更新的时间,最小时间是前一个load在loadInBackground开始执行到新的load 开始执行的间隔
     * @param delayMS Amount of delay, in milliseconds.
     */
    public void setUpdateThrottle(long delayMS) {
        mUpdateThrottle = delayMS;
        if (delayMS != 0) {
            mHandler = new Handler();
        }
    }

    @Override
    protected void onForceLoad() {
        super.onForceLoad();
        cancelLoad();//取消当前load
        mTask = new LoadTask();//重建一个新的Task
        executePendingTask();//执行新的Task
    }

    @Override
    protected boolean onCancelLoad() {

        if (mTask != null) {
            if (mCancellingTask != null) {
                /*There was a pending task already waiting for a previous one being canceled; just drop it.
                *若有正在等待一个被取消的任务执行完毕,那么先取消后面的那个任务。
                */
                if (mTask.waiting) {
                    mTask.waiting = false;
                    mHandler.removeCallbacks(mTask);
                }
                mTask = null;
                return false;
            } else if (mTask.waiting) {
                // 有一个任务,但它正在等待它应该执行的时间。 我们可以把它扔掉。There is a task, but it is waiting for the time it should execute.  We can just toss it.
                mTask.waiting = false;
                mHandler.removeCallbacks(mTask);
                mTask = null;
                return false;
            } else {
                boolean cancelled = mTask.cancel(false);
                if (cancelled) {
                    mCancellingTask = mTask;
                    cancelLoadInBackground();
                }
                mTask = null;
                return cancelled;
            }
        }
        return false;
    }

    /**
     * Called if the task was canceled before it was completed.  Gives the class a chance
     * to clean up post-cancellation and to properly dispose of the result.
     * @param data The value that was returned by {@link #loadInBackground}, or null if the task threw {@link OperationCanceledException}.
     */
    public void onCanceled(D data) {
    }

    void executePendingTask() {
        if (mCancellingTask == null && mTask != null) {
            //如果mTask正在等待被执行。
            if (mTask.waiting) {
                mTask.waiting = false; //那么把它从队列中移除。
                mHandler.removeCallbacks(mTask);
            }
            if (mUpdateThrottle > 0) {
                long now = SystemClock.uptimeMillis();
                if (now < (mLastLoadCompleteTime+mUpdateThrottle)) {
                    //放入Handler当中。
                    mTask.waiting = true;
                    mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                    return;
                }
            }
            //执行这个任务。
            mTask.executeOnExecutor(mExecutor, (Void[]) null);
        }
    }

void dispatchOnCancelled(LoadTask task, D data) {
        onCanceled(data); //回调onCanceled.
        if (mCancellingTask == task) { //如果被取消的task执行完了。
            rollbackContentChanged(); 
            mLastLoadCompleteTime = SystemClock.uptimeMillis();
            mCancellingTask = null;
            deliverCancellation(); //通过被取消的task执行完了。
            executePendingTask(); //执行当前的Task。
        }
    }

    void dispatchOnLoadComplete(LoadTask task, D data) {
        if (mTask != task) { //如果执行完的task不是最新的。
            dispatchOnCancelled(task, data);
        } else {
            if (isAbandoned()) { //如果被abandon了。
                onCanceled(data);
            } else {
                commitContentChanged();
                mLastLoadCompleteTime = SystemClock.uptimeMillis();
                mTask = null;
                deliverResult(data);
            }
        }
    }

    /**
     * Called on a worker thread to perform the actual load and to return
     * the result of the load operation.
     *
     * Implementations should not deliver the result directly, but should return them
     * from this method, which will eventually end up calling {@link #deliverResult} on
     * the UI thread.  If implementations need to process the results on the UI thread
     * they may override {@link #deliverResult} and do so there.
     *
     * To support cancellation, this method should periodically check the value of
     * {@link #isLoadInBackgroundCanceled} and terminate when it returns true.
     * Subclasses may also override {@link #cancelLoadInBackground} to interrupt the load
     * directly instead of polling {@link #isLoadInBackgroundCanceled}.
     *
     * When the load is canceled, this method may either return normally or throw
     * {@link OperationCanceledException}.  In either case, the {@link Loader} will
     * call {@link #onCanceled} to perform post-cancellation cleanup and to dispose of the
     * result object, if any.
     *
     * @return The result of the load operation.
     *
     * @throws OperationCanceledException if the load is canceled during execution.
     */
    public abstract D loadInBackground();

    protected D onLoadInBackground() {
        return loadInBackground();
    }

    public void cancelLoadInBackground() {
    }

    public boolean isLoadInBackgroundCanceled() {
        return mCancellingTask != null;
    }
    public void waitForLoader() {
        LoadTask task = mTask;
        if (task != null) {
            task.waitForLoader();
        }
    }
    ...
}

二、自定义Loader

  • 继承自AsyncTaskLoader重写对应的构造方法

  • 重写loadInBackground(D d)onStartLoading()onStopLoading()onCanceled(D d)onReset()方法、

  • 根据需求实现释放资源的方法doReleaseResources和传递数据的deliverResult(D d)方法。

package com.crazymo.customloader;

/**
 * Auther: Crazy.Mo
 * DateTime: 2018/1/3 17:14
 * Summary:
 */

import android.content.AsyncTaskLoader;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageManager;

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

/**
 * A custom Loader that loads all of the installed applications.
 */
public class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
    final CustomLoaderHelper.InterestingConfigChanges lastConfig = new CustomLoaderHelper.InterestingConfigChanges();
    final PackageManager packageMgr;

    List<AppEntry> mApps;
    CustomLoaderHelper.PackageIntentReceiver mPackageObserver;

    public AppListLoader(Context context) {
        super(context);
        //Retrieve the package manager for later use; note we don't use 'context' directly but instead the save global application context returned by getContext().
        packageMgr = getContext().getPackageManager();
    }

    /**
     * 必须实现,此方法是Loader在后台加载大量数据的地方。这个方法工作在后台线程中,而且你要做的就是获取要加载新的数据,并且返回给调用者
     * This is where the bulk of our work is done.  This function is called in a background thread and should generate a new set of data to be published by the loader.
     */
    @Override
    public List<AppEntry> loadInBackground() {
        // 开始获取要加载的所有任何数据,相当于是此处可以执行耗时的后台操作获取数据
        List<ApplicationInfo> apps = packageMgr.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
        if (apps == null) {
            apps = new ArrayList<>();
        }
        final Context context = getContext();
        // Create corresponding array of entries and load their labels.
        List<AppEntry> entries = new ArrayList<>(apps.size());
        for (int i = 0; i < apps.size(); i++) {
            AppEntry entry = new AppEntry(this, apps.get(i));
            entry.loadLabel(context);
            entries.add(entry);
        }
        Collections.sort(entries, CustomLoaderHelper.ALPHA_COMPARATOR);//简单做个排序
        // Done!
        return entries;
    }

    /**
     * 建议实现,当需要把新的数据传递给用户的时候调用. 父类会处理具体传递细节,实现这个回调主要是为了添加额外的逻辑处理
     * Called when there is new data to deliver to the client.The super class will take care of delivering it;
     * the implementation here just adds a little more logic.
     */
    @Override
    public void deliverResult(List<AppEntry> apps) {
        if (isReset()) {
            // An async query came in while the loader is stopped.  We don't need the result.
            if (apps != null) {
                doReleaseResources(apps);
            }
            return;
        }
        List<AppEntry> oldApps = mApps;
        mApps = apps;
        if (isStarted()) {
            // If the Loader is currently started, we can immediately deliver its results.
            super.deliverResult(apps);
        }
        // At this point we can release the resources associated with 'oldApps' if needed; now that the new result is delivered we know that it is no longer in use.
        if (oldApps != null) {
            doReleaseResources(oldApps);
        }
    }

    /**
     * Handles a request to start the Loader.
     */
    @Override
    protected void onStartLoading() {
        if (mApps != null) {
            // If we currently have a result available, deliver it immediately.
            deliverResult(mApps);
        }
        // Start watching for changes in the app data.开始监控数据源的变化,这里使用的是注册一个BroadcastReceiver,当然也可以使用其他机制。
        if (mPackageObserver == null) {
            mPackageObserver = new CustomLoaderHelper.PackageIntentReceiver(this);
        }
        // Has something interesting in the configuration changed since we last built the app list?
        boolean configChange = lastConfig.applyNewConfig(getContext().getResources());
        if (takeContentChanged() || mApps == null || configChange) {
            /**
             * If the data has changed since the last time it was loaded  or is not currently available, start a load.
             * 如果源数据从上次加载以来已经发生了变化,那么就强制重新加载一次。这里有一个方法takeContentChanged(),需要注意的是Loader的onContentChanged()方法
             * 那个方法可能会设置相关标记,这样这个条件就为真了。
             */
            forceLoad();//强制重新加载一次
        }
    }

    /**
     * Handles a request to stop the Loader.
     */
    @Override
    protected void onStopLoading() {
        // Attempt to cancel the current load task if possible.
        cancelLoad();
    }

    /**
     * Handles a request to cancel a load.
     */
    @Override
    public void onCanceled(List<AppEntry> apps) {
        super.onCanceled(apps);
        // At this point we can release the resources associated with 'apps' if needed.
        doReleaseResources(apps);
    }

    /**
     * Handles a request to completely reset the Loader.
     */
    @Override
    protected void onReset() {
        super.onReset();
        // Ensure the loader is stopped
        onStopLoading();
        // 如果有数据应该进行清空处理At this point we can release the resources associated with 'apps' if needed.
        if (mApps != null) {
            doReleaseResources(mApps);
            mApps = null;
        }
        // 停止对数据源的监听Stop monitoring for changes.
        if (mPackageObserver != null) {
            getContext().unregisterReceiver(mPackageObserver);
            mPackageObserver = null;
        }
    }

    /**
     * Helper function to take care of releasing resources associated with an actively loaded data set.
     */
    protected void doReleaseResources(List<AppEntry> apps) {
        // For a simple List<> there is nothing to do.  For something like a Cursor, we would close it here.
    }
}

四、实现自定义的Loader的一般步骤

  • 实现LoaderManager.LoaderCallbacks< D >接口

  • 通过getLoaderManager()获取并初始化LoaderManager实例

  • 调用LoaderManager中的initLoader方法初始化Loader实例

  • LoaderManager.LoaderCallbacks< D >的onCreateLoader()方法中创建自定义Loader,在onLoadFinished处理回传的数据并显示到对应的控件,在onLoaderReset方法中完成数据重置清空

/**
 * Auther: Crazy.Mo
 * DateTime: 2018/1/4 16:17
 * Summary:自己后面封装的一个BaseLoader,使用的时候需要实现子类realLoadData方法
 */
public abstract class BaseLoader<T > extends AsyncTaskLoader<T> {

    private T data ;
    Bundle bundles;
    CancellationSignal cancellationSignal;
    protected abstract T realLoadData(Bundle bundle);
    protected void doReleaseResources(T result) {}//而不要回收资源的就不需要重写了

    public BaseLoader(Context context,Bundle bundle) {
        super (context);
        this.bundles=bundle;
    }

    public T loadInBackground(){
        synchronized (this) {
            if (isLoadInBackgroundCanceled()) {
                throw new OperationCanceledException();
            }
            cancellationSignal = new CancellationSignal();
        }
        try {
            return realLoadData(bundles);
        } finally {
            synchronized (this) {
                cancellationSignal = null;
            }
        }
    }

    @Override
    public void cancelLoadInBackground() {
        super.cancelLoadInBackground();
        synchronized (this) {
            if (cancellationSignal != null) {
                cancellationSignal.cancel();
            }
        }
    }
    public void deliverResult(T data) {
        if (isReset()) {
            if (data != null) {
                doReleaseResources(data);
            }
            return;
        }
        T oldData = data;
        if (isStarted()) {
            super.deliverResult(data);
        }
        if (oldData != null) {
            doReleaseResources(oldData);
        }
    }

    protected void onStartLoading() {
        if (data != null) {
            deliverResult( data);
        }
        if (takeContentChanged() || data == null) {
            forceLoad();
        }
    }

    protected void onStopLoading() {
        cancelLoad();
    }

    public void onCanceled(T apps) {
        super .onCanceled(apps);
        doReleaseResources(apps);
    }

    protected void onReset() {
        super .onReset();
        onStopLoading();
        if (data != null) {
            doReleaseResources(data);
            data = null;
        }
    }
}

只需要实现realLoadData方法即可

public class AppListLoader2 extends BaseLoader<List<AppEntry>>  {
    final CustomLoaderHelper.InterestingConfigChanges lastConfig = new CustomLoaderHelper.InterestingConfigChanges();
    final PackageManager packageMgr;

    List<AppEntry> mApps;
    CustomLoaderHelper.PackageIntentReceiver mPackageObserver;

    @Override
    protected List<AppEntry> realLoadData(Bundle bundle) {
        List<ApplicationInfo> apps = packageMgr.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES);
        if (apps == null) {
            apps = new ArrayList<>();
        }
        final Context context = getContext();
        // Create corresponding array of entries and load their labels.
        List<AppEntry> entries = new ArrayList<>(apps.size());
        for (int i = 0; i < apps.size(); i++) {
            AppEntry entry = new AppEntry(this, apps.get(i));
            entry.loadLabel(context);
            entries.add(entry);
        }
        Collections.sort(entries, CustomLoaderHelper.ALPHA_COMPARATOR);//简单做个排序
        // Done!
        return entries;
    }

    public AppListLoader2(Context context, Bundle bundle) {
        super(context,bundle);
        packageMgr = getContext().getPackageManager();
    }
}

五、使用自定义Loader获取手机中所有安装了的Package信息列表

首先定义用于描述App信息的实体(如果要使用上面的继承自基类BaseLoader的话AppEntry得修改下,这里我就不演示了)

/**
 * Auther: Crazy.Mo
 * DateTime: 2018/1/3 17:03
 * This class holds the per-item data in our Loader.
 */
public class AppEntry {
    private final AppListLoader loader;
    private final ApplicationInfo applicationInfo;
    private final File apkFile;
    private String appLabel;
    private Drawable appIcon;
    private boolean mounted;

    public AppEntry(AppListLoader loader, ApplicationInfo info) {
        this.loader = loader;
        applicationInfo = info;
        apkFile = new File(info.sourceDir);
    }

    public ApplicationInfo getApplicationInfo() {
        return applicationInfo;
    }

    public String getLabel() {
        return appLabel;
    }

    public Drawable getIcon() {
        if (appIcon == null) {
            if (apkFile.exists()) {
                appIcon = applicationInfo.loadIcon(loader.packageMgr);
                return appIcon;
            } else {
                mounted = false;
            }
        } else if (!mounted) {
            // If the app wasn't mounted but is now mounted, reload its icon
            if (apkFile.exists()) {
                mounted = true;
                appIcon = applicationInfo.loadIcon(loader.packageMgr);
                return appIcon;
            }
        } else {
            return appIcon;
        }
        return loader.getContext().getResources().getDrawable(android.R.drawable.sym_def_app_icon);
    }

    @Override
    public String toString() {
        return appLabel;
    }

    void loadLabel(Context context) {
        if (appLabel == null || !mounted) {
            if (!apkFile.exists()) {
                mounted = false;
                appLabel = applicationInfo.packageName;
            } else {
                mounted = true;
                CharSequence label = applicationInfo.loadLabel(context.getPackageManager());
                appLabel = label != null ? label.toString() : applicationInfo.packageName;
            }
        }
    }
}

为了实现这个需求而需要的一些辅助类

public class AppListAdapter extends ArrayAdapter<AppEntry> {
    private final LayoutInflater inflater;

    public AppListAdapter(Context context) {
        super(context, android.R.layout.simple_list_item_2);
        inflater = (LayoutInflater)context.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
    }

    public void setData(List<AppEntry> data) {
        clear();
        if (data != null) {
            addAll(data);
        }
    }

    @Override
    public View getView(int position, View convertView, ViewGroup parent) {
        View view;
        if (convertView == null) {
            view = inflater.inflate(R.layout.item_app_list, parent, false);
        } else {
            view = convertView;
        }
        AppEntry item = getItem(position);
        ((ImageView)view.findViewById(R.id.imv_icon)).setImageDrawable(item.getIcon());
        ((TextView)view.findViewById(R.id.tv_name)).setText(item.getLabel());
        return view;
    }
}

public class CustomLoaderHelper {

    /**
     * 自定义的比较器用于排序
     */
    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {
        private final Collator sCollator = Collator.getInstance();
        @Override
        public int compare(AppEntry object1, AppEntry object2) {
            return sCollator.compare(object1.getLabel(), object2.getLabel());
        }
    };

    /**
     * Helper for determining if the configuration has changed in an interesting way so we need to rebuild the app list.
     */
    public static class InterestingConfigChanges {
        final Configuration mLastConfiguration = new Configuration();
        int mLastDensity;

        boolean applyNewConfig(Resources res) {
            int configChanges = mLastConfiguration.updateFrom(res.getConfiguration());
            boolean densityChanged = mLastDensity != res.getDisplayMetrics().densityDpi;
            if (densityChanged || (configChanges & (ActivityInfo.CONFIG_LOCALE | ActivityInfo.CONFIG_UI_MODE | ActivityInfo.CONFIG_SCREEN_LAYOUT)) != 0) {
                mLastDensity = res.getDisplayMetrics().densityDpi;
                return true;
            }
            return false;
        }
    }

    /**
     * Helper class to look for interesting changes to the installed apps so that the loader can be updated.
     */
    public static class PackageIntentReceiver extends BroadcastReceiver {
        final AppListLoader mLoader;

        public PackageIntentReceiver(AppListLoader loader) {
            mLoader = loader;
            IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
            filter.addAction(Intent.ACTION_PACKAGE_REMOVED);
            filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
            filter.addDataScheme("package");
            mLoader.getContext().registerReceiver(this, filter);
            // Register for events related to sdcard installation.
            IntentFilter sdFilter = new IntentFilter();
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_AVAILABLE);
            sdFilter.addAction(Intent.ACTION_EXTERNAL_APPLICATIONS_UNAVAILABLE);
            mLoader.getContext().registerReceiver(this, sdFilter);
        }

        @Override
        public void onReceive(Context context, Intent intent) {
            // Tell the loader about the change.
            mLoader.onContentChanged();
        }
    }
}

public  class MySearchView extends SearchView {
    public MySearchView(Context context, AttributeSet attrs) {
        super(context, attrs);
    }

    public MySearchView(Context context, AttributeSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
    }

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

    // The normal SearchView doesn't clear its search text when collapsed, so we will do this for it.
    @Override
    public void onActionViewCollapsed() {
        setQuery("", false);
        super.onActionViewCollapsed();
    }
}

MainActivity的实现(布局文件略)

package com.crazymo.customloader;

import android.app.LoaderManager;
import android.content.Loader;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.TextUtils;
import android.widget.ListView;
import java.util.List;

public class MainActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<List<AppEntry>>,
        MySearchView.OnQueryTextListener,MySearchView.OnCloseListener {

    public static final int APPLOADER_ID = 1001;
    private AppListAdapter mAdapter;
    private ListView listView;
    private MySearchView searchView;
    private String curFilter;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);
        init();
    }

    private void init() {
        listView= (ListView) findViewById(R.id.lv_applist);
        searchView= (MySearchView) findViewById(R.id.search_appname);
        searchView.setOnQueryTextListener(this);
        searchView.setOnCloseListener(this);
        mAdapter = new AppListAdapter(this);
        listView.setAdapter(mAdapter);
        getLoaderManager().initLoader(APPLOADER_ID, null, this);
    }

    @Override
    public Loader<List<AppEntry>> onCreateLoader(int i, Bundle bundle) {
        return new AppListLoader(this);//创建自定义Loader
    }

    @Override
    public void onLoadFinished(Loader<List<AppEntry>> loader, List<AppEntry> appEntries) {
        //接收到AppListLoader的数据,用于初始化话Apdater并显示
        mAdapter.setData(appEntries);
        mAdapter.notifyDataSetChanged();
        return;
    }

    @Override
    public void onLoaderReset(Loader<List<AppEntry>> loader) {
        mAdapter.setData(null);
    }

    @Override
    public boolean onClose() {
        if (!TextUtils.isEmpty(searchView.getQuery())) {
            searchView.setQuery(null, true);
        }
        return true;
    }

    @Override
    public boolean onQueryTextSubmit(String newText) {
        return true;
    }

    @Override
    public boolean onQueryTextChange(String newText) {
        curFilter = !TextUtils.isEmpty(newText) ? newText : null;
        mAdapter.getFilter().filter(curFilter);
        return true;
    }
}

这里写图片描述

源码传送门

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

推荐阅读更多精彩内容