Loader


1、Loaders具有以下特性:

1)它适用于任何Activity和Fragment

2)它使用异步加载的方式

3)当内容发生变化时,他监听数据源的变化并传递新的结果

4)当系统配置发生变化时,在数据重载时,他自动重连一次Loader的游标,所以无需重新查询数据


2、Loader的API概览

1)LoaderManager:这是一个抽象类,用于与某个Activity和Fragment绑定,以管理若干个Loader实例,这可以帮助一个应用程序轻松处理Actitiy或者Fragment生命周期的长时间操作,该抽象类最普遍的用法是和CursorLoader一起使用,当然应用程序也可以自由地为其他类型的数据写入相对应的Loaders。


2)LoaderManager.LoadCallbacks:一个与LoaderManger交互的回调接口,可以通过调用onCreateLoader()创建一个Loader实例。

3)Loader:一个异步加载数据的类,这是所有Loader的基类,CursorLoader就是一个比较常见的实现类,不过你也可以自己定制实现类。Loader是动态的,他能够监听数据源的改变

4)AsyncTaskLoader:这是一个用来提供AsyncTask类的抽象类

5)CursorLoader:这是一个AsyncTaskLoader的子类,它可以从ContentResolver中查询并返回Cursor类的引用。


3、在应用程序中使用Loader

在一个应用程序中,Loader的使用场景如下:

1)Activity或者Fragment中

2)LoaderManager的实例中

3)使用CursorLoader加载ContentProvider中提供的数据,也可以通过继承Loader或者AsyncTaskLoader来加载其他类型的数据

4)实现LoaderManager.LoaderCallBacks回调接口。在回调中,你也可以创建新的实例,也可以管理已存在的Loader实例

5)决定加载数据的显示方式,如SimpleCursorAdapter

6)加载数据源时,如CursorLoader


4、创建Loader对象

LoaderManager负责在Activity和Fragment中创建一个或多个Loader实例,每个Activity和Fragment仅能持有一个LoaderManager实例。

一般在Activity的onCreate()和Fragment的onActivityCreate()中创建Loader实例

getLoaderManager().initLoader(0, null, this);
initLoader()方法参数如下:
参数1(int):一个Loader实例的ID标识,示例中ID为0。
参数2(Bundle):一个用于构建Loader实例的可选参数,示例中为空。
参数3(LoaderManager.LoaderCallbacks):由LoaderManager调用,以便于通知Loader事件。

调用initLoader()方法可以确保一个Loader被创建或者激活,这会产生两种结果:
若为Loader指定的ID已经存在,则已经存在的Loader会被重用
若为Loader指定的ID不存在,则initLoader()方法为触发LoadManager.LoaderCallbacks中的回调方法onCreateLoader(),在该方法中,你需要实例化Loader对象,并返回该对象

无论发生上述那种情况,实现LoaderManager.LoaderCallbacks接口的对象将与 Loader相关联,并且当Loader的状态发生改变时,该回调接口中的方法将被回调,回调方法时,要求绑定的Loader已初始化并加载数据,接着,系统立即回调onFinish()方法(在执行initLoader()方法时回调)

尽管initLoader()方法返回Loader对象或其子类对象的引用,但程序无需持有这个引用,以为LoaderManager自动管理着所有已创建的Loader的生命周期,LoaderManager会在需要时启动或者加载,并可获得管辖的Loader的状态与Loader关联的数据。这意味着你无需跟Loades直接交互,相反,在多数情况下,当事件发生时,需使用LoaderManager.LoaderCallbacks中的回调方法管理加载过程


5、重启Loader

就像上面提到的那样,调用initLoader()时,若传入的ID值已经存在,则重用Loader,若不存在才会创建新的Loader,但在有些时候,我们希望Loader丢弃新数据并重启

为了丢弃旧数据,你可以调用restartLoader()方法,比如,当用户查询的内容发生改变时,Loader需要重启,进行新的查询

getLoaderManager().restartLoader(0,null,this)

6、使用LoaderManager回调

LoaderManager.LoaderCallbacks是一个回调接口,它可以使客户端与LoaderManager方便的进行交互


当处于stop状态时,Loader希望保持他们的数据不丢失。这需要通过Activity或者Fragment的onStart()和onStop()方法保持数据不丢失。所以当用户返回应用时,不用等待数据重新加载。你可以使用LoaderManager.LoaderCallbacks来监听新Loader被创建、或者Loader何时停止使用数据


回调接口LoaderManager.LoaderCallbacks包含以下方法

onCreateLoader():初始化一个新的Loader,并赋予一个ID

onLoaderFinish():当某个曾经创建的Loader停止加载时,该方法被回调

onLoaderReset():当某个曾经创建的Loader()重新启动时(这时数据不可获取),该方法被回调


下面详细介绍每个回调方法

onCreateLoader():当你需要获取Loader实例时(如:通过initLoader()获取),该方法会查询新创建的Loader的ID是否与之前创建过的Loader重复,若未重复,则onCreateLoader()会被回调,就是在这里创建新的Loader,这通常是CursorLoader,当然也可以是其他定制的Loader子类。创建CursorLoader需要调用构造方法,

其中参数需要使用ContentProvider的查询结果,而ContentProvider需要以下参数

1)Uri:检索内容的Uri地址

2)projection:需要查询的列,若传入null则表示查询所有列,这样效率比较低

3)selection:需要查询的行,若传入null则表示查询所有行

4)selectionArgs:需要筛选的参数

5)sortOrder:排序规则

例如:

public Loader<Cursor> onCreateLoader(int id, Bundle args) {
    Uri baseUri;
    if (mCurFilter!=null){
        baseUri=Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,Uri.encode(mCurFilter));
    }
    else {
        baseUri= ContactsContract.Contacts.CONTENT_URI;
    }
    String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
            + ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
            + ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
    return new CursorLoader(this, baseUri,
            CONTACTS_SUMMARY_PROJECTION, select, null,
            ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
    return null;
}

onLoadFinished():当之前创建的的Loader实例停止加载的时候,该方法被回调,该方法被系统回调的时机是:该Loader实例加载的最后一次数据释放掉之前。此时你应该清除
所有的旧数据,但不应该手动释放数据,因为Loader此时还持有数据
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
...

public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    // Swap the new cursor in.  (The framework will take care of closing the
    // old cursor once we return.)
    mAdapter.swapCursor(data);
}
onLoaderReset():当之前已经创建的某个Loader被重建时,该方法被回调。此时,数据不可用。当方法回调时,数据即将被释放,所以你可以移除对该数据的引用
7、ContentProvider情况下的CursorLoader自动刷新
1)对获取的Cursor数据设置需要监听的URI(即在ContentProvider的query()方法或者Loader的loadingBackground()方法中调用Cursor的setNotificationUri()方法)
2)在ContentProvider的insert()、update()、delete()等方法中调用ContentResolved的notifyChanged()方法

特别注意:如果在多次调用query()的情况下,应该把setNotificationUri()方法放在loadingBackground()中,防止消耗系统资源

8、不使用ContentProvider且自定义Loader的情况下自动刷新

public class NoProviderLoader extends AsyncTaskLoader {
    ForceLoadContentObserver mObserve=new ForceLoadContentObserver();

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

    @Override
    public Cursor loadInBackground() {
        SQLiteDatabase database = SQLiteOpenHelper.getReadableDatabase();
        Cursor cursor = database.query(table, columns, selection, selectionArgs, groupBy, having, orderBy);
        if (cursor!=null){
            cursor.registerContentObserver(mObserve);
            cursor.setNotificationUri(getContext().getContentResolver(),otificationUri);
        }
        return cursor;
    }
}

NoProvider中定义的ForceLoadContentObserve是Loader的内部类,当数据变化时会调用该类的onChange()方法,所以能够自动刷新

9、自定义Loader
public class AppEntry {
    private final AppListLoader mLoader;
    private final ApplicationInfo mInfo;
    private final File mApkFile;
    private String mLabel;
    private Drawable mIcon;
    private boolean mMounted;

    public AppEntry(AppListLoader loader, ApplicationInfo info) {
        mLoader = loader;
        mInfo = info;
        mApkFile = new File(info.sourceDir);//路径
    }

    public ApplicationInfo getApplicationInfo() {
        return mInfo;
    }

    public String getLabel() {
        return mLabel;
    }

    public Drawable getIcon() {
        if (mIcon == null) {
            if (mApkFile.exists()) {
                mIcon = mInfo.loadIcon(mLoader.mPm);
                return mIcon;
            } else {
                mMounted = false;
            }
        } else if (!mMounted) {
            //如果之前应用程序没有安装,重新加载图片
            if (mApkFile.exists()) {
                mMounted = true;
                mIcon = mInfo.loadIcon(mLoader.mPm);
                return mIcon;
            }
        } else {
            return mIcon;
        }
        return mLoader.getContext().getResources().getDrawable(android.R.drawable.sym_def_app_icon);
    }

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

    void loadLabel(Context context) {
        if (mLabel == null || !mMounted) {
            if (!mApkFile.exists()) {
                mMounted = false;
                mLabel = mInfo.packageName;
            } else {
                mMounted = true;
                CharSequence label = mInfo.loadLabel(context.getPackageManager());
                mLabel = label != null ? label.toString() : mInfo.packageName;
            }
        }
    }

    public static final Comparator<AppEntry> ALPHA_COMPARATOR = new Comparator<AppEntry>() {

        private final Collator sColltor = Collator.getInstance();

        @Override
        public int compare(AppEntry lhs, AppEntry rhs) {
            return sColltor.compare(lhs.getLabel(), rhs.getLabel());//对label进行排序
        }
    };


public 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) {//configChanges是否包含那几个flag
            mLastDensity = res.getDisplayMetrics().densityDpi;
            return true;
        }
        return false;
    }

}

public class PackageIntentReceiver extends BroadcastReceiver {
    //发现安装应用程序的变化来更新Loader
    final AppListLoader mLoader;

    public PackageIntentReceiver(AppListLoader mLoader) {
        this.mLoader = mLoader;
        IntentFilter filter = new IntentFilter(Intent.ACTION_PACKAGE_ADDED);
        filter.addAction(Intent.ACTION_PACKAGE_CHANGED);
        filter.addAction(Intent.ACTION_MEDIA_REMOVED);
        filter.addDataScheme("package");
        mLoader.getContext().registerReceiver(this, filter);
        //注册有关sd安装的事件
        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) {
    //告诉Loader改变
        mLoader.onContentChanged();
    }
}

public class AppListAdapter extends ArrayAdapter<AppEntry> {
    private final LayoutInflater mInflater;
    public AppListAdapter(Context context) {
        super(context, android.R.layout.simple_list_item_2);
        this.mInflater = (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=mInflater.inflate(R.layout.list_item_icon_text,parent,false);
        }
        else {
            view=convertView;
        }
        AppEntry item=getItem(position);
        ((ImageView)view.findViewById(R.id.icon)).setImageDrawable(item.getIcon());
        ((TextView)view.findViewById(R.id.text)).setText(item.getLabel());
        return view;
    }
}


public class AppListLoader extends AsyncTaskLoader<List<AppEntry>> {
    //一个有所有安装程序的自定义Loader
    final InterestingConfigChanges mLastConfig = new InterestingConfigChanges();
    public PackageManager mPm;

    List<AppEntry> mApps;
    PackageIntentReceiver mPackageObserver;

    public AppListLoader(Context context) {
        super(context);
        //获得packageManager供以后使用,请注意我们不直接使用context而是使用全局应用程序getContext()返回的上下文
        mPm = getContext().getPackageManager();
    }

    //到这我们的大部分工作已经完成了,这个功能会在后台调用并且会产生一些数据来由loader管理
    @Override
    public List<AppEntry> loadInBackground() {
        //返回所有已知的应用程序
        List<ApplicationInfo> app = mPm.getInstalledApplications(PackageManager.GET_UNINSTALLED_PACKAGES | PackageManager.GET_DISABLED_COMPONENTS);
        if (app == null) {
            app = new ArrayList<ApplicationInfo>();
        }
        final Context context = getContext();
        //创建对应的条目和他们的标签
        List<AppEntry> entries = new ArrayList<AppEntry>(app.size());
        for (int i = 0; i < app.size(); i++) {
            AppEntry entry = new AppEntry(this, app.get(i));
            entry.loadLabel(context);
            entries.add(entry);
        }
        //整理列表
        Collections.sort(entries, AppEntry.ALPHA_COMPARATOR);

        //Done
        return entries;
    }

    /**
     * 当有新的数据提供给客户端时调用,这个类将会处理,在这里增加了更多的逻辑
     */
    @Override
    public void deliverResult(List<AppEntry> apps) {
        if (isReset()) {
            //一个异步查询在loader停止加载的时候,不需要返回结果
            if (apps != null) {
                onReleaseResources(apps);
            }
        }
        List<AppEntry> oldApps = mApps;
        mApps = apps;

        if (isStarted()) {
            //如果Loader当前启动了,我们可以立刻发送结果
            super.deliverResult(apps);
        }
        //在这里可以释放oldapps相关的资源了如果你需要的话,现在新的结果已经被传递我们知道他不再使用
        if (oldApps != null) {
            onReleaseResources(oldApps);
        }
    }

    /**
     * 处理start Loader的请求
     */
    @Override
    protected void onStartLoading() {
        if (mApps != null) {
            //如果我们现在有了可获得的结果,马上传递出去
            deliverResult(mApps);
        }

        //开始观察应用程序数据的变化
        if (mPackageObserver == null) {
            mPackageObserver = new PackageIntentReceiver(this);
        }

        //获得配置信息的变化在最后一次构建应用程序列表时
        boolean configChange = mLastConfig.applyNewConfig(getContext().getResources());

        if (takeContentChanged() || mApps == null || configChange) {
            //如果数据跟最后一次加载有变化或者当前不可获取,那么开始加载
            forceLoad();
        }
    }

    /**
     * 处理stop请求
     */
    @Override
    protected void onStopLoading() {
        //如果可能的话试图取消当前加载任务
        cancelLoad();
    }
/*
    处理cancel的请求
 */
    @Override
    public void onCanceled(List<AppEntry> apps) {
        super.onCanceled(apps);
        onReleaseResources(apps);
    }

    /*
    处理完全加载Loader的请求
     */

    @Override
    protected void onReset() {
        super.onReset();
        //确保loader停止了
        onStopLoading();

        //我们可以释放apps相关的资源
        if (mApps!=null){
            onReleaseResources(mApps);
            mApps=null;
        }

        //停止监听更改
        if (mPackageObserver!=null){
            getContext().unregisterReceiver(mPackageObserver);
            mPackageObserver=null;
        }
    }
/*
释放资源的帮助类
 */
    private void onReleaseResources(List<AppEntry> apps) {
         //对于这个简单的list不需要做什么,如果是cursor之类的,可以在这里close
    }
}

10、AsyncTask和AsyncTaskLoader的区别

AsyncTaskLoader:

1)优势:会自动刷新数据变化,会自动处理Activity配置变化造成的影响,适合纯数据加载

2)劣势:不能实时通知ui刷新,不能再onLoadFinish时主动切换生命周期


AsyncTaks:

优势:可以与ui实时交互及replace操作

劣势:不会自动处理Activity配置变化造成的影响

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值