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

 阅读前请下载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
    点赞
  • 1
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值