详细讲解Android中使用LoaderManager加载数据的方法
android的设计之中,任何耗时的操作都不能放在ui主线程之中。所以类似于网络操作等等耗时的操作都需要使用异步的实现。而在contentprovider之中,也有可能存在耗时的操作(当查询的数据量很大的时候),这个时候我们也需要使用异步的调用来完成数据的查询。
当使用异步的query的时候,我们就需要使用loadermanager了。使用loadermanager就可以在不阻塞ui主线程的情况下完成数据的加载。
(1)获取loadermanger:activity.getloadermanager()
(2)loadermanager的事件回调接口, loadermanager.loadercallbacks<d>
下面是一个demo,从contentprovider中query数据添加到listview中,是异步执行的。
mysqliteopenheleper.java:
package com.app.loadermanager; import android.content.context; import android.database.sqlite.sqlitedatabase; import android.database.sqlite.sqlitedatabase.cursorfactory; import android.database.sqlite.sqliteopenhelper; public class mysqliteopenhelper extends sqliteopenhelper { public static final string db_name = "test.db3"; public static final int version = 1; public mysqliteopenhelper(context context) { super(context, db_name, null, version); } @override public void oncreate(sqlitedatabase db) { string create_sql = "create table tb_student(_id integer primary key autoincrement,name varchar(20),age integer)"; db.execsql(create_sql); } @override public void onupgrade(sqlitedatabase db, int oldversion, int newversion) { } }
mycontentprovider.java
package com.app.loadermanager; import android.content.contentprovider; import android.content.contenturis; import android.content.contentvalues; import android.content.urimatcher; import android.database.cursor; import android.database.sqlite.sqlitedatabase; import android.net.uri; public class mycontentprovider extends contentprovider { private mysqliteopenhelper helper = null; private static final urimatcher matcher = new urimatcher( urimatcher.no_match); private static final int students = 1; static { matcher.adduri("com.app.contentprovider", "tb_student", students); } @override public int delete(uri arg0, string arg1, string[] arg2) { return 0; } @override public string gettype(uri arg0) { return null; } @override public uri insert(uri uri, contentvalues values) { sqlitedatabase db = helper.getwritabledatabase(); int flag = matcher.match(uri); switch (flag) { case students: long id = db.insert("tb_student", null, values); return contenturis.withappendedid(uri, id); } return null; } @override public boolean oncreate() { helper = new mysqliteopenhelper(this.getcontext()); return true; } @override public cursor query(uri uri, string[] projection, string selection, string[] selectionargs, string sortorder) { sqlitedatabase db = helper.getwritabledatabase(); cursor cursor=db.query("tb_student", projection, selection, selectionargs, null, null, null); return cursor; } @override public int update(uri uri, contentvalues values, string selection, string[] selectionargs) { return 0; } }
mainactivity.java:
package com.app.loadermanager; import java.util.arraylist; import android.annotation.suppresslint; import android.app.activity; import android.app.loadermanager; import android.content.cursorloader; import android.content.loader; import android.database.cursor; import android.net.uri; import android.os.bundle; import android.widget.arrayadapter; import android.widget.listview; @suppresslint("newapi") public class mainactivity extends activity implements loadermanager.loadercallbacks<cursor> { loadermanager manager = null; listview listview = null; @suppresslint("newapi") @override protected void oncreate(bundle savedinstancestate) { super.oncreate(savedinstancestate); setcontentview(r.layout.activity_main); listview = (listview) this.findviewbyid(r.id.listview); manager = this.getloadermanager(); manager.initloader(1000, null, this); } @override public loader<cursor> oncreateloader(int id, bundle bundle) { cursorloader loader = new cursorloader(this, uri.parse("content://com.app.contentprovider"), null, null, null, null); return loader; } @override public void onloadfinished(loader<cursor> loader, cursor cursor) { arraylist<string> al = new arraylist<string>(); while (cursor.movetonext()) { string name = cursor.getstring(cursor.getcolumnindex("name")); al.add(name); } arrayadapter adapter=new arrayadapter(this,android.r.layout.simple_list_item_1,al); listview.setadapter(adapter); adapter.notifydatasetchanged(); } @override public void onloaderreset(loader<cursor> loader) { } }
loadermanager与生命周期
activity和fragment都拥有getloadermanager的方法,其实fragment的getloadermanager去获取的就是activity所管理的众多loadermanager之一。
1.who标签
先来看一下activity的getloadermanager方法:
loadermanagerimpl getloadermanager(string who, boolean started, boolean create) { if (mallloadermanagers == null) { mallloadermanagers = new arraymap<string, loadermanagerimpl>(); } loadermanagerimpl lm = mallloadermanagers.get(who); if (lm == null) { if (create) { lm = new loadermanagerimpl(who, this, started); mallloadermanagers.put(who, lm); } } else { lm.updateactivity(this); } return lm; }
mallloadermanagers保存着一个activity所拥有的所有loadermanager,其key为string类型的who变量。若从activity调用getloadermanager,那么所得loadermanager的who标签为(root):
public loadermanager getloadermanager() { if (mloadermanager != null) { return mloadermanager; } mcheckedforloadermanager = true; mloadermanager = getloadermanager("(root)", mloadersstarted, true); return mloadermanager; }
若从fragment中使用getloadermanager,则所得loadermanager的who标签会根据fragment的层级不同而不同,在activity中处于最*的fragment的who标签为:
android:fragment:x
x为序号。
而属于fragment的fragment所活的的loadermanager who标签就成为了:
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:
public void destroyloader(int id)
这样就能在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。否则若有数据并且不是要下次再上报(没有call doreportnextstart)的话就上报给callback的onloadfinished。
doreportnextstart(),根据第6条,已经能够理解了。当fragment执行到ondestroyview生命周期时,对自己的loadermanager发出请求:即使现在有数据也不要进行上报,等我重做再到onstart生命周期时再给我。
推荐阅读
-
详细讲解Android中使用LoaderManager加载数据的方法
-
实例讲解Android中ContentProvider组件的使用方法
-
Android开发中类加载器DexClassLoader的简单使用讲解
-
Android开发中Listview动态加载数据的方法示例
-
Android开发中Listview动态加载数据的方法示例
-
深入讲解数据库中Decimal类型的使用以及实现方法
-
在React组件中详细讲解this的使用方法。
-
详细介绍使用MSScriptControl在C#中读取json数据的方法
-
在React组件中详细讲解this的使用方法。
-
深入讲解数据库中Decimal类型的使用以及实现方法