自定义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;
编译运行,然后再输入“储”,看到的结果图如下:
- 自定义Loader的实现---ApiDemos中的LoaderCustom例子分析
- ApiDemos--FragmenAlertDialog的例子分析
- ApiDemos--FragmentArguments的例子分析
- ApiDemos--FragmentContextMenu的例子分析
- android 自定义 loader实现
- APIDemos例子3D Transition学习(自定义Animation)
- lua5.1.4中实现自定义require的loader函数
- lua5.1.4中实现自定义require的loader函数
- Android SDK Simple 例程分析之 ApiDemos 的 ApiDemos.java 分析
- Loader的简单实现
- 实现自定义ViewGroup的一个精简例子
- 自定义实现输入输出模版的一个例子
- 自定义HttpModule实现某些功能的例子
- Android-Universal-Image-Loader中的缓存分析
- ApiDemos – BouncingBalls分析
- ApiDemos—FragmentCustomAnimations分析
- ApiDemos—FragmentCustomAnimations分析
- android自带例子apidemos
- 跟我一起学extjs5(40--增加一个自定义模块[1建立表和bean文件])
- android 中如何获取IMEI号
- 3、SQL语句记录
- 最短路径—Dijkstra算法和Floyd算法
- Fast Excel import and export
- 自定义Loader的实现---ApiDemos中的LoaderCustom例子分析
- 比H.264更高效 新视频编解码H.265解读
- TRUNCATE TABLE HANG
- Android开发相关概念知识
- 稳定网站排名的秘密—百度优先级算法
- 设置禁止Document和Library目录下的文件同步到iCloud
- jQery事件绑定事件处理函数bind详解
- 什么都是新的
- linux之sed用法