自定义Loader的实现---ApiDemos中的LoaderCustom例子分析

来源:互联网 发布:cocos2d-x用js编写 编辑:程序博客网 时间:2024/05/16 04:08

 阅读前请下载ApiDemos工程代码。

 

LoaderCustom类中的自定义AppListLoader的写法其实和android类CursorLoader的写法基本一样,可见自定义Loader的实现可以说是有模板的,这也为轻松的实现自定义Loader提供了基础。

 

自定义Loader的实现步骤相对简单,只需要做如下几件事即可:

1)  自定义Loader类必须继承于AsyncTaskLoader类,AsyncTaskLoader类提供了大部分的Loader管理工作;当然了,如果你很牛X,你也可以不用这么做。

2)  需要实现如下基本的回调方法,以AppListLoader为例:


上面回调的细节描述后文会介绍。

3)  监听数据源的变化。

CursorLoader中使用cursor.registerContentObserver(mObserver)来监听数据源变化,当数据源发生变化的时候,Loader类的onContentChanged()方法会被调用。

 

而AppListLoader类的加载数据是从PackageManager中获取的所有已安装的应用程序信息,我们通过直接操作PackageManager来获取数据,因此对于数据源的变化,此处是通过一个BroadcastReceiver来监听应用程序包的安装情况,当收到onReceive()回调的时候,我们直接调用Loader类的onContentChanged()方法,这样就和CursorLoader的行为就保持一致了。

 

下面我们先看第一步AppListLoader的继承情况:

public static class AppListLoader extends AsyncTaskLoader<List<AppEntry>> 

 

首先继承自AsyncTaskLoader,数据类型为List<AppEntry>,AppEntry也是自定义的内部类,用来保存应用程序的信息。

 

接着依次看看这几个回调方法的细节:

1) loadInBackground

/**         * 此方法是Loader在后台加载大量数据的地方。这个方法工作在后台线程中,而且你要做的就是加载新的数据,并且返回给调用者.         */        @Override public List<AppEntry> loadInBackground() {            // 获得所有已安装的应用程序信息.            List<ApplicationInfo> apps = mPm.getInstalledApplications(                    PackageManager.GET_UNINSTALLED_PACKAGES |                    PackageManager.GET_DISABLED_COMPONENTS);            if (apps == null) {                apps = new ArrayList<ApplicationInfo>();            }            final Context context = getContext();            // Create corresponding array of entries and load their labels.            List<AppEntry> entries = new ArrayList<AppEntry>(apps.size());            for (int i=0; i<apps.size(); i++) {                AppEntry entry = new AppEntry(this, apps.get(i));                entry.loadLabel(context);                entries.add(entry);            }            // Sort the list.            Collections.sort(entries, ALPHA_COMPARATOR);            // Done!            return entries;        }

这个方法主要工作就是加载后台数据。因此,在这里你可以用自己的方法去加载想要获得的数据,但是切记,此方法工作于后台线程,因此不要和视图组件有任何交互。

2)deliverResult

/**         * 当需要把新的数据传递给用户的时候调用. 父类会处理具体传递细节,*我们实现这个回调主要是为了添加额外的逻辑处理。         */        @Override public void deliverResult(List<AppEntry>apps) {                         if (isReset()) {                // 能够进到这个判断条件里,说明一个异步加载正在进行的时候,用户已经                // 停止了这个Loader的加载,所以返回的新数据不需要再传递给用户了,                //那么直接清理了,onReleaseResources()是自定义清理数据的方法,                //是个空方法,此处只是个示例。                if (apps != null) {                    onReleaseResources(apps);                }                            //原工程代码中没有return语句,但是从理论上以及参考CursorLoader                //类,应该写上return。因为毕竟新数据是无用的,那么当然要直接返回了。                return;            }            List<AppEntry> oldApps = mApps;            mApps = apps;             if (isStarted()) {                // 如果用户已经调用了startLoading,但是没有调用stopLoading                // 说明应该把数据传递给用户。                super.deliverResult(apps);            }             //到这里我们已经传递了新数据,那么旧数据自然无用了,所以直接清理掉.            if (oldApps != null) {                onReleaseResources(oldApps);            }        }

3)      onStartLoading

/**         *这个回调通知我们Loader准备开始加载了。此方法必须在UI线程中调用。         */        @Override protected void onStartLoading() {            if (mApps != null) {                // 如果当前已经有可用的数据,那么直接传递这些数据.                deliverResult(mApps);            }            // 开始监控数据源的变化,主要是注册一个BroadcastReceiver。            if (mPackageObserver == null) {                mPackageObserver = new PackageIntentReceiver(this);            }            // Has something interesting in the configuration changed since we            // last built the app list?            boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());            if (takeContentChanged() || mApps == null || configChange) {                // 如果源数据从上次加载以来已经发生了变化,那么就强制重新加载一次。                // 这里有一个方法takeContentChanged(),值得一说,还记得前面   // 说得Loader的nContentChanged()方法吗,那个方法可能会设置相关            //标记,这样这个条件就为真了。                forceLoad();            }        }

4)      onStopLoading

/**         * 处理停止加载的请求.         */        @Override protected void onStopLoading() {            // 只需简单地调用下面方法即可,表示退出加载.            cancelLoad();        }

5)      onCanceled

/**         *在加载完成前,处理一个退出加载的请求。         */        @Override public void onCanceled(List<AppEntry> apps) {            super.onCanceled(apps);            // 这个时候需要清理从LoadInBackground返回的数据            onReleaseResources(apps);        }

6)  onReset

/**         *处理重置Loader的请求         */        @Override protected void onReset() {            super.onReset();            // 首先确保停止加载            onStopLoading();            //如果当前有数据,那么清理掉.            if (mApps != null) {                onReleaseResources(mApps);                mApps = null;            }            // 关闭对数据源的监听.            if (mPackageObserver != null) {                getContext().unregisterReceiver(mPackageObserver);                mPackageObserver = null;            }        }

接着再看一下对数据源的监听以及当发生变化时如何通知Loader。看下面的代码:

/**     * 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);            // 1) 在此处注册,实现监听            mLoader.getContext().registerReceiver(this, sdFilter);        }        @Override public void onReceive(Context context, Intent intent) {            // 2)此处通知Loader,数据源有变化,这样在适当情况下,Loader会重新加载数据            mLoader.onContentChanged();        }    }

以上就是实现一个自定义Loader的基本步骤。和CursorLoader的实现过程基本一样。运行本例子,效果图如下:


 

点击右上角的搜索图标,并输入“帮”字,效果图如下:


 

但是如果输入“储”,结果图如下:



 

我第一次看到,觉得很奇怪,按理来说,如果输入“储”,应该显示“安全存储”,“拨号器存储”等结果啊。后来一查找到了原因,首先查看一下搜索图标的回调事件处理:

@Override public boolean onQueryTextChange(String newText) {            // Called when the action bar search text has changed.  Since this            // is a simple array adapter, we can just have it do the filtering.            mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;            mAdapter.getFilter().filter(mCurFilter);            return true;        }

 

原来调用了mAdatper的filter方法,mAdapter是一个自定义的Adapter:

public static class AppListAdapter extends ArrayAdapter<AppEntry> {

本质上讲mAdapter就是一个ArrayAdatper<AppEntry>,当调用filter()方法时,实际上调用了ArrayAdapter里的私有内部类ArrayFilter的performFiltering方法,这个方法就是把数据元素(此处是AppEntry)的toString()返回值与参数进行匹配,如果这个参数是那个toString()的前缀,那么就显示这个结果。

 

在本例中,AppEntry类的toString()方法如下:

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

返回是mLable值,也就是应用的名字。当输入“储”的时候,并没有任何一个名字是以“储”为前缀的,所以自然没有结果显示了。而实际上,我觉得只要匹配名字中任何一个字符就应该显示出来,大部分用户应该都会希望是这样的,所以我把ArrayAdapter拿来修改了一番就实现了搜索的模糊查找:

1)        拷贝adt-bundle-windows-x86-20140321\sdk\sources\android-19\android\widget\ArrayAdapter.java到目录\adt-bundle-windows-x86-20140321\sdk\samples\android-19\legacy\ApiDemos\src\com\example\android\apis\widget下;

2)        修改ArrayAdapter.java的内容:、

将两处valueText.startWith更改valueText.contains;

 

3)修改LoaderCustom.java的包导入路径:

import com.example.android.apis.widget.ArrayAdapter;//import android.widget.ArrayAdapter;

编译运行,然后再输入“储”,看到的结果图如下:


0 0
原创粉丝点击
热门问题 老师的惩罚 人脸识别 我在镇武司摸鱼那些年 重生之率土为王 我在大康的咸鱼生活 盘龙之生命进化 天生仙种 凡人之先天五行 春回大明朝 姑娘不必设防,我是瞎子 开发商把网签房卖给我该怎么办 cad中命令行没了怎么办 平板玩游戏很卡怎么办 卵巢包块20厘米怎么办 耳机的橡胶破了怎么办 脚被树枝扎破了怎么办 脚被木头扎肿了怎么办 手表字面脚断了怎么办 捡到苹果电脑怎么办才能自己用 耳机泡在水里了怎么办 蓝牙耳机泡水了怎么办 拖欠农民工工资劳动局不管怎么办 怎么办假的残疾军人证 外地人到北京就业怎么办五险 巴基斯坦留学生护照丢了怎么办 不敢上梯子太丢人怎么办 家人信了全能神怎么办 头皮上长淋巴炎怎么办? 鱼缸里的鱼相互咬怎么办? 火车凌晨4点到站怎么办 金立手机拍不了照怎么办 手术拆线后切口不平怎么办 乳牙透明冠掉了怎么办 配眼镜度数低了怎么办 眼睛高度近视怎么办才能恢复 猎人荒野的呼唤打中一枪后怎么办 凯恩帝数控超程报警怎么办 凯恩帝数控车床x向超程怎么办 别人问你借账号怎么办 微信成夜间模式怎么办 网络电视突然黑屏了怎么办 1adac连接线坏了怎么办 4g的标志没有了怎么办 苹果手机亏电了怎么办 苹果手机亏电了充不进电怎么办 比值审敛法 ρ=1怎么办 比值审敛法中p=1怎么办 电脑被老友重装系统搞坏了怎么办 vivo手机听筒声音小怎么办 小孩调皮幼儿园不收怎么办 小孩听力残疾幼儿园不收怎么办