详细讲解Android中使用LoaderManager加载数据的方法
Android的设计之中,任何耗时的操作都不能放在UI主线程之中。所以类似于网络操作等等耗时的操作都需要使用异步的实现。而在ContentProvider之中,也有可能存在耗时的操作(当查询的数据量很大的时候),这个时候我们也需要使用异步的调用来完成数据的查询。
当使用异步的query的时候,我们就需要使用LoaderManager了。使用LoaderManager就可以在不阻塞UI主线程的情况下完成数据的加载。
(1)获取loaderManger:activity.getLoaderManager()
(2)loaderManager的事件回调接口,LoaderManager.LoaderCallbacks<D>
下面是一个demo,从contentprovider中query数据添加到listview中,是异步执行的。
MySQLiteOpenHeleper.java:
packagecom.app.loadermanager;
importandroid.content.Context;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.database.sqlite.SQLiteDatabase.CursorFactory;
importandroid.database.sqlite.SQLiteOpenHelper;
publicclassMySQLiteOpenHelperextendsSQLiteOpenHelper{
publicstaticfinalStringdb_name="test.db3";
publicstaticfinalintversion=1;
publicMySQLiteOpenHelper(Contextcontext){
super(context,db_name,null,version);
}
@Override
publicvoidonCreate(SQLiteDatabasedb){
Stringcreate_sql="createtabletb_student(_idintegerprimarykeyautoincrement,namevarchar(20),ageinteger)";
db.execSQL(create_sql);
}
@Override
publicvoidonUpgrade(SQLiteDatabasedb,intoldVersion,intnewVersion){
}
}
MyContentProvider.java
packagecom.app.loadermanager;
importandroid.content.ContentProvider;
importandroid.content.ContentUris;
importandroid.content.ContentValues;
importandroid.content.UriMatcher;
importandroid.database.Cursor;
importandroid.database.sqlite.SQLiteDatabase;
importandroid.net.Uri;
publicclassMyContentProviderextendsContentProvider{
privateMySQLiteOpenHelperhelper=null;
privatestaticfinalUriMatchermatcher=newUriMatcher(
UriMatcher.NO_MATCH);
privatestaticfinalintstudents=1;
static{
matcher.addURI("com.app.contentprovider","tb_student",students);
}
@Override
publicintdelete(Uriarg0,Stringarg1,String[]arg2){
return0;
}
@Override
publicStringgetType(Uriarg0){
returnnull;
}
@Override
publicUriinsert(Uriuri,ContentValuesvalues){
SQLiteDatabasedb=helper.getWritableDatabase();
intflag=matcher.match(uri);
switch(flag){
casestudents:
longid=db.insert("tb_student",null,values);
returnContentUris.withAppendedId(uri,id);
}
returnnull;
}
@Override
publicbooleanonCreate(){
helper=newMySQLiteOpenHelper(this.getContext());
returntrue;
}
@Override
publicCursorquery(Uriuri,String[]projection,Stringselection,
String[]selectionArgs,StringsortOrder){
SQLiteDatabasedb=helper.getWritableDatabase();
Cursorcursor=db.query("tb_student",projection,selection,selectionArgs,null,null,null);
returncursor;
}
@Override
publicintupdate(Uriuri,ContentValuesvalues,Stringselection,
String[]selectionArgs){
return0;
}
}
MainActivity.java:
packagecom.app.loadermanager;
importjava.util.ArrayList;
importandroid.annotation.SuppressLint;
importandroid.app.Activity;
importandroid.app.LoaderManager;
importandroid.content.CursorLoader;
importandroid.content.Loader;
importandroid.database.Cursor;
importandroid.net.Uri;
importandroid.os.Bundle;
importandroid.widget.ArrayAdapter;
importandroid.widget.ListView;
@SuppressLint("NewApi")
publicclassMainActivityextendsActivityimplements
LoaderManager.LoaderCallbacks<Cursor>{
LoaderManagermanager=null;
ListViewlistView=null;
@SuppressLint("NewApi")
@Override
protectedvoidonCreate(BundlesavedInstanceState){
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView=(ListView)this.findViewById(R.id.listview);
manager=this.getLoaderManager();
manager.initLoader(1000,null,this);
}
@Override
publicLoader<Cursor>onCreateLoader(intid,Bundlebundle){
CursorLoaderloader=newCursorLoader(this,
Uri.parse("content://com.app.contentprovider"),null,null,
null,null);
returnloader;
}
@Override
publicvoidonLoadFinished(Loader<Cursor>loader,Cursorcursor){
ArrayList<String>al=newArrayList<String>();
while(cursor.moveToNext()){
Stringname=cursor.getString(cursor.getColumnIndex("name"));
al.add(name);
}
ArrayAdapteradapter=newArrayAdapter(this,android.R.layout.simple_list_item_1,al);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
@Override
publicvoidonLoaderReset(Loader<Cursor>loader){
}
}
LoaderManager与生命周期
Activity和Fragment都拥有getLoaderManager的方法,其实Fragment的getLoaderManager去获取的就是Activity所管理的众多LoaderManager之一。
1.Who标签
先来看一下Activity的getLoaderManager方法:
LoaderManagerImplgetLoaderManager(Stringwho,booleanstarted,booleancreate){
if(mAllLoaderManagers==null){
mAllLoaderManagers=newArrayMap<String,LoaderManagerImpl>();
}
LoaderManagerImpllm=mAllLoaderManagers.get(who);
if(lm==null){
if(create){
lm=newLoaderManagerImpl(who,this,started);
mAllLoaderManagers.put(who,lm);
}
}else{
lm.updateActivity(this);
}
returnlm;
}
mAllLoaderManagers保存着一个Activity所拥有的所有LoaderManager,其key为String类型的who变量。若从Activity调用getLoaderManager,那么所得LoaderManager的who标签为(root):
publicLoaderManagergetLoaderManager(){
if(mLoaderManager!=null){
returnmLoaderManager;
}
mCheckedForLoaderManager=true;
mLoaderManager=getLoaderManager("(root)",mLoadersStarted,true);
returnmLoaderManager;
}
若从Fragment中使用getLoaderManager,则所得LoaderManager的who标签会根据Fragment的层级不同而不同,在Activity中处于最顶级的Fragment的who标签为:
android:fragment:X
X为序号。
而属于Fragment的Fragment所活的的LoaderManagerwho标签就成为了:
android:fragment:X:Y
甚至更大的深度。
2.LoaderManager状态量mLoadersStarted
在开篇分析的时候分析过LoaderManager内部保存了一个mStarted状态,很多操作会根据这个状态做不同处理。通过上边的分析也能看出,Fragment中获取LoaderManager最终是通过Activity获取的,在LoaderManager构造时,就将一个状态量mLoadersStarted传递了进去,这个状态量交给LoaderManager自行管理。
而无论是Fragment还是Activity,都有mLoadersStarted这样一个状态量,在onStart生命周期后为true,onStop后为false。所以在onStart生命周期后做initLoader操作就会使Loader一经初始化就开始运行了。
3.Fragment和Activity的生命周期
Fragment和Activity能够对LoaderManager产生影响的生命周期是一样的。
onStart
系统在onStart阶段会获取LoaderManager,如果成功获取,则调用LoaderManager的doStart(),这里需要特别说明的是,虽然所有LoaderManager都是保存在Activity中,但是在Activity的onStart生命周期其也只是会获取属于自己的(root)标签LoaderManager,而并不是将所有保存在mAllLoaderManagers里的Manager全部遍历一遍。
onStop(performStop)
处于onStop生命周期,但是系统内部是通过performStop调用的。在这里,同样会获取属于自己的LoaderManager,如果Activity是因为配置改变出发的onStop(旋转屏幕),则调用LoaderManager的doRetain()接口,如果不是,就调用LoaderManager的doStop()接口。
onDestroy(performDestroy)
调用LoaderManager的doDestroy()接口销毁LoaderManager。
4.LoaderManager的生命周期
因为LoaderManager与Fragment/Activity的生命周期紧密相连,所以想要用好LoaderManager就必须了解其自身的生命周期,这样就能把握数据的完整变化规律了。
正常的从出生到销毁:
doStart()->doReportStart()->doStop()->doDestroy()
Activity配置发生变化:
doStart()->doRetain()->finishRetain()->doReportStart()->doStart()->doStop()->doDestroy()
Fragment在onDestroyView()之后还会执行LoaderManager的doReportNextStart(),即:
doStart()->doRetain()->doReportNextStart()->finishRetain()->doReportStart()->doStart()->doStop()->doDestroy()
doStart()会将LoaderManager中保存的所有Loader都启动。最终是运行每一个Loader的onStartLoading()方法。只要是通过initLoader使用过的Loader都会记录在LoaderManager的mLoaders中,那么问题来了:
怎样在Fragment/Activity不销毁的前提下从LoaderManager中移除某个使用过的Loader呢?
答案就是使用LoaderManager的接口去除指定ID的Loader:
publicvoiddestroyLoader(intid)
这样就能在mLoaders中移除掉了,下次onStart的时候就没有这个Loader什么事了。
doReportStart()。如果Fragment上一次在销毁并重做,而且数据有效的话会在这里主动上报数据,最终走到callback的onLoadFinished中。
doStop()会停止mLoaders保存的所有Loader。最终是运行每一个Loader的onStopLoading()方法。
doDestroy()会清空所有有效和无效Loader,LoaderManager中不再存在任何Loader。
doRetain()会将LoaderManager的mRetaining状态置位true,并且保存retain时LoaderInfo的mStarted状态
finishRetain()如果之前所保存的mStarted与现在的不一样而且新的状态是停止的话,就停止掉这个Loader。否则若有数据并且不是要下次再上报(没有calldoReportNextStart)的话就上报给callback的onLoadFinished。
doReportNextStart(),根据第6条,已经能够理解了。当Fragment执行到onDestroyView生命周期时,对自己的LoaderManager发出请求:即使现在有数据也不要进行上报,等我重做再到onStart生命周期时再给我。