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的初学者。我用两个片段制作了一个视图寻呼机。我在第一个片段中有一些回收器视图项。一个回收器视图项将填充一个片段。在滑动到下一个片段时,我可以加载我已滑动的回收器视图项的详细信息吗?或者如何在滑动时将回收器视图项的位置传递到下一个片段,请提供帮助。
本文向大家介绍Xcode使用教程详细讲解(全),包括了Xcode使用教程详细讲解(全)的使用技巧和注意事项,需要的朋友参考一下 Xcode使用教程详细讲解是本文要介绍的内容,Xcode是一个款强大的IDE开发环境,就像你在写Windows程序时需要VS2005一样 需要要Xcode为你写Mac程序提供环境。因此,如果你要成为Mac 程序的开发者,灵活运用Xcode工具是你必须做的第一步。 1)我们
本文向大家介绍详细解读Python中解析XML数据的方法,包括了详细解读Python中解析XML数据的方法的使用技巧和注意事项,需要的朋友参考一下 Python可以使用 xml.etree.ElementTree 模块从简单的XML文档中提取数据。 为了演示,假设你想解析Planet Python上的RSS源。下面是相应的代码: 运行上面的代码,输出结果类似这样: 很显然,如果你想做进一步的处理,
本文向大家介绍详细讲解安全升级MySQL的方法,包括了详细讲解安全升级MySQL的方法的使用技巧和注意事项,需要的朋友参考一下 MySQL升级是非常必要的. 我们在Percona Support上列出了关于MySQL升级最佳实践的各种问题.这篇文章推荐了一些不同情况下升级MySQL的方法. 为什么MySQL升级是必须的? 原因有很多,比如:为了使用新增的特性,基于性能方面的考量, 修复的bug.
本文向大家介绍详细讲解Java的泛型,包括了详细讲解Java的泛型的使用技巧和注意事项,需要的朋友参考一下 我们知道,使用变量之前要定义,定义一个变量时必须要指明它的数据类型,什么样的数据类型赋给什么样的值。 假如我们现在要定义一个类来表示坐标,要求坐标的数据类型可以是整数、小数和字符串,例如: 针对不同的数据类型,除了借助方法重载,还可以借助自动装箱和向上转型。我们知道,基本数据类型可以自动装箱
本文向大家介绍详细讲解JavaScript中的this绑定,包括了详细讲解JavaScript中的this绑定的使用技巧和注意事项,需要的朋友参考一下 this 可以说是 javascript 中最耐人寻味的一个特性,就像高中英语里各种时态,比如被动时态,过去时,现在时,过去进行时一样,无论弄错过多少次,下一次依然可能弄错。本文启发于《你不知道的JavaScript上卷》,对 javasript