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实例
initLoader()方法参数如下:getLoaderManager().initLoader(0, null, this);
参数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此时还持有数据
onLoaderReset():当之前已经创建的某个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); }
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配置变化造成的影响