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;
}
}
这里写图片描述
源码传送门