Android Loader详解
来源:互联网 发布:怎么自学java 编辑:程序博客网 时间:2024/04/30 06:53
原文:http://blog.csdn.net/luohai859/article/details/23938291
在看Android的文档时,看到了这么一个东西: Loader
究竟是什么东西呢?
Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:
1、They are available to every Activity and Fragment. //支持Activity和Fragment
2、They provide asynchronous loading of data. //异步下载
3、They monitor the source of their data and deliver new results when the content changes. //当数据源改变时能及时通知客户端
4、They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data. //发生configuration change时自动重连接
看来这东西蛮强大的,开始我的探索之路吧.
先简单看一下它的用法先:
001
/**
002
* Demonstration of the use of a CursorLoader to load and display contacts
003
* data in a fragment.
004
*/
005
public
class
LoaderCursor
extends
Activity {
006
007
@Override
008
protected
void
onCreate(Bundle savedInstanceState) {
009
super
.onCreate(savedInstanceState);
010
011
FragmentManager fm = getFragmentManager();
012
013
// Create the list fragment and add it as our sole content.
014
if
(fm.findFragmentById(android.R.id.content) ==
null
) {
015
CursorLoaderListFragment list =
new
CursorLoaderListFragment();
016
fm.beginTransaction().add(android.R.id.content, list).commit();
017
}
018
}
019
020
021
public
static
class
CursorLoaderListFragment
extends
ListFragment
022
implements
LoaderManager.LoaderCallbacks<Cursor> {
023
024
// This is the Adapter being used to display the list's data.
025
SimpleCursorAdapter mAdapter;
026
027
// If non-null, this is the current filter the user has provided.
028
String mCurFilter;
029
030
@Override
public
void
onActivityCreated(Bundle savedInstanceState) {
031
032
mAdapter =
new
SimpleCursorAdapter(getActivity(),
033
android.R.layout.simple_list_item_2,
null
,
034
new
String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
035
new
int
[] { android.R.id.text1, android.R.id.text2 },
0
);
036
setListAdapter(mAdapter);
037
038
getLoaderManager().initLoader(
0
,
null
,
this
);
039
}
040
041
042
@Override
public
void
onListItemClick(ListView l, View v,
int
position,
long
id) {
043
// Insert desired behavior here.
044
Log.i(
"FragmentComplexList"
,
"Item clicked: "
+ id);
045
}
046
047
// These are the Contacts rows that we will retrieve.
048
static
final
String[] CONTACTS_SUMMARY_PROJECTION =
new
String[] {
049
Contacts._ID,
050
Contacts.DISPLAY_NAME,
051
Contacts.CONTACT_STATUS,
052
Contacts.CONTACT_PRESENCE,
053
Contacts.PHOTO_ID,
054
Contacts.LOOKUP_KEY,
055
};
056
057
public
Loader<Cursor> onCreateLoader(
int
id, Bundle args) {
058
// This is called when a new Loader needs to be created. This
059
// sample only has one Loader, so we don't care about the ID.
060
// First, pick the base URI to use depending on whether we are
061
// currently filtering.
062
Uri baseUri;
063
if
(mCurFilter !=
null
) {
064
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
065
Uri.encode(mCurFilter));
066
}
else
{
067
baseUri = Contacts.CONTENT_URI;
068
}
069
070
// Now create and return a CursorLoader that will take care of
071
// creating a Cursor for the data being displayed.
072
String select =
"(("
+ Contacts.DISPLAY_NAME +
" NOTNULL) AND ("
073
+ Contacts.HAS_PHONE_NUMBER +
"=1) AND ("
074
+ Contacts.DISPLAY_NAME +
" != '' ))"
;
075
return
new
CursorLoader(getActivity(), baseUri,
076
CONTACTS_SUMMARY_PROJECTION, select,
null
,
077
Contacts.DISPLAY_NAME +
" COLLATE LOCALIZED ASC"
);
078
}
079
080
public
void
onLoadFinished(Loader<Cursor> loader, Cursor data) {
081
// Swap the new cursor in. (The framework will take care of closing the
082
// old cursor once we return.)
083
mAdapter.swapCursor(data);
084
085
// The list should now be shown.
086
if
(isResumed()) {
087
setListShown(
true
);
088
}
else
{
089
setListShownNoAnimation(
true
);
090
}
091
}
092
093
public
void
onLoaderReset(Loader<Cursor> loader) {
094
// This is called when the last Cursor provided to onLoadFinished()
095
// above is about to be closed. We need to make sure we are no
096
// longer using it.
097
mAdapter.swapCursor(
null
);
098
}
099
}
100
101
}
这里是Android提供的实例代码,有删减。
从代码上看来,通过实现LoaderManager.LoaderCallbacks就行了.
在onCreateLoader里面实现你要请求的耗时操作,当异步线程操作完成之后就会从onLoadFinished返回数据.
用起来是不是很简单呢?下面具体来看一下它是怎么做到的吧.
getLoaderManager()是定义在Activity类的一个方法,返回类型LoaderManager,但这只是个接口,它真正的实现类是谁呢?
继续往下走,看到这个LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create),方法时,答案便揭晓了.
下面我们来看看LoaderManager相关的类结构,省略了很多东西,但不影响我们的分析.
现在我们来到了LoaderManagerImp的initLoader方法了.
01
public
<D> Loader<D> initLoader(
int
id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
02
if
(mCreatingLoader) {
03
throw
new
IllegalStateException(
"Called while creating a loader"
);
04
}
05
06
LoaderInfo info = mLoaders.get(id);
07
08
if
(info ==
null
) {
09
// Loader doesn't already exist; create.
10
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
11
if
(DEBUG) Log.v(TAG,
" Created new loader "
+ info);
12
}
else
{
13
if
(DEBUG) Log.v(TAG,
" Re-using existing loader "
+ info);
14
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
15
}
16
17
if
(info.mHaveData && mStarted) {
18
// If the loader has already generated its data, report it now.
19
info.callOnLoadFinished(info.mLoader, info.mData);
20
}
21
22
return
(Loader<D>)info.mLoader;
23
}
这是一个新的Loader,那么info应该是null,转入执行createAndInstallLoader.
01
private
LoaderInfo createAndInstallLoader(
int
id, Bundle args,
02
LoaderManager.LoaderCallbacks<Object> callback) {
03
try
{
04
mCreatingLoader =
true
;
05
LoaderInfo info = createLoader(id, args, callback);
06
installLoader(info);
07
return
info;
08
}
finally
{
09
mCreatingLoader =
false
;
10
}
11
}
12
13
private
LoaderInfo createLoader(
int
id, Bundle args,
14
LoaderManager.LoaderCallbacks<Object> callback) {
15
LoaderInfo info =
new
LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
16
Loader<Object> loader = callback.onCreateLoader(id, args);
17
info.mLoader = (Loader<Object>)loader;
18
return
info;
19
}
20
21
void
installLoader(LoaderInfo info) {
22
mLoaders.put(info.mId, info);
23
if
(mStarted) {
24
// The activity will start all existing loaders in it's onStart(),
25
// so only start them here if we're past that point of the activitiy's
26
// life cycle
27
info.start();
28
}
29
}
createLoader把必要的信息都封装在LoaderInfo类里面,留意以下这一行:
callback.onCreateLoader(id,arg),这里正是我们上面在客户端实现接口LoaderCallback的那个方法.
接着调用installLoader,这个方法把这次Loader的信息put进mLoader这个SparseArray<LoaderInfo>中,这个对象可以理解为一个Map,它的性能比Map要好.
mStarted的值是true,它是在getLoaderManager的时候在Activity中传进来的true值.
好了,下面进入LoaderInfo的start方法了.
01
void
start() {
02
if
(mLoader !=
null
) {
03
04
if
(!mListenerRegistered) {
05
mLoader.registerListener(mId,
this
);
06
mListenerRegistered =
true
;
07
}
08
mLoader.startLoading();
09
}
10
}
mLoader就是在客户端实现的那个Loader,回到我们刚开始时的例子,它就是一个CursorLoader.
在分析CursorLoader的startLoading之前,我们先看一下这些Loader的类结构先:
从这些类的名称看来,真正实现了异步传输功能的类应该就是AsyncTaskLoader了,事实是不是这样呢?
继续深入下去:
这里的startLoading是调用了Loader类的方法,下文中我会用这样的方法来标识方法是属于哪个类的: 如Loader –> startLoading
01
Loader:
02
public
final
void
startLoading() {
03
mStarted =
true
;
04
mReset =
false
;
05
mAbandoned =
false
;
06
onStartLoading();
07
}
08
09
CursorLoader:
10
protected
void
onStartLoading() {
11
if
(mCursor !=
null
) {
12
deliverResult(mCursor);
13
}
14
if
(takeContentChanged() || mCursor ==
null
) {
15
forceLoad();
16
}
17
}
18
19
AsynTaskLoader:
20
protected
void
onForceLoad() {
21
super
.onForceLoad();
22
cancelLoad();
23
mTask =
new
LoadTask();
24
if
(DEBUG) Slog.v(TAG,
"Preparing load: mTask="
+ mTask);
25
executePendingTask();
26
}
终于看到了LoadTask关键字啦,答案就要揭晓啦.
01
AsyncTaskLoader:
02
final
class
LoadTask
extends
AsyncTask<Void, Void, D>
implements
Runnable {
03
private
final
CountDownLatch mDone =
new
CountDownLatch(
1
);
04
05
// Set to true to indicate that the task has been posted to a handler for
06
// execution at a later time. Used to throttle updates.
07
boolean
waiting;
08
09
/* Runs on a worker thread */
10
@Override
11
protected D doInBackground(Void... params) {
12
if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
13
try {
14
D data = AsyncTaskLoader.this.onLoadInBackground();
15
return data;
16
} catch (OperationCanceledException ex) {
17
}
18
}
19
20
/* Runs on the UI thread */
21
@Override
22
protected
void
onPostExecute(D data) {
23
if
(DEBUG) Slog.v(TAG,
this
+
" onPostExecute"
);
24
try
{
25
AsyncTaskLoader.
this
.dispatchOnLoadComplete(
this
, data);
26
}
finally
{
27
mDone.countDown();
28
}
29
}
30
}
31
32
AsyncTaskLoader:
33
protected
D onLoadInBackground() {
34
return
loadInBackground();
35
}
36
37
CursorLoader:
38
public
Cursor loadInBackground() {
39
try
{
40
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
41
mSelectionArgs, mSortOrder, mCancellationSignal);
42
if
(cursor !=
null
) {
43
// Ensure the cursor window is filled
44
cursor.getCount();
45
registerContentObserver(cursor, mObserver);
46
}
47
return
cursor;
48
}
finally
{
49
synchronized
(
this
) {
50
mCancellationSignal =
null
;
51
}
52
53
}
LoadTask原来是个AsyncTask类型,看到这里大家大家应该觉得有种豁然的感觉了吧.
在ForceLoad里面启动该线程,开始执行doInBackground,回调CursorLoader里面的loadInBackgroud,这个方法里面执行真正的耗时操作,
执行完之后一层一层返回,接着调用onPostExecute方法.
好了,现在数据总算是拿到了.
接着执行,把获取的数据往回调.
LoadTask -> onPostExecute
----->
AsynTaskLoader-> dispatchOnLoadComplete
----->
Loader->deliverResult
回调前面注册的loadComplete:
LoaderInfo -> onLoadComplete
---->
LoaderInfo ->callOnLoadFinished
把数据回调给客户端
mCallbacks.onLoadFinished(loader, data);
到这里就完美解释了Loader的特点2,异步
第三点当数据源改变时能及时通知客户端又是如何体现的呢?
这里用了观察者模式来实现.我们先看一下CursorLoader的构造函数:
mObserver = new ForceLoadContentObserver();
这个ForceLoadContentObserver是什么东西呢?
ForceLoadContentObserver继承了ContentObserver,这是Android内部的一个对象,继承了它,就能享受到数据变化时可以接收到通知(也就是观察者中的Subject),这里类似于数据库中的触发器.
先往下看:
在CursorLoader->loadInBackground方法中有这么一句:
registerContentObserver(cursor, mObserver);//注册观察者
答案揭晓了.
注册观察者后,当对应的URI发生变化是,会触发onChange方法
01
public
void
onChange(
boolean
selfChange) {
02
onContentChanged();
03
}
04
05
public
void
onContentChanged() {
06
if
(mStarted) {
07
forceLoad();
//这里重新发送请求.
08
}
else
{
09
// This loader has been stopped, so we don't want to load
10
// new data right now... but keep track of it changing to
11
// refresh later if we start again.
12
mContentChanged =
true
;
13
}
14
}
对于forceLoad方法前面已经提高过了,大家应该还有印象吧.
最后一个问题,也就是第四点:如何做到在configuration change自动重链接的呢?
只要能回答这两个问题,这个问题就解决了.
<1>loader如何在configuration change之前保存数据?
<2>loader如何在configuration chage之后恢复数据并继续load?
LoaderManager:
还记得吗?Loader创建之初,在LoaderManagerImp->installLoader方法里面,
mLoaders.put(info.mId, info);
Info 是LoaderInfo对象,里面封装了Loader的相关信息,表示这个LoaderInfo的Key是mId.
就是在这里保存了loader.这样就回答了问题<1>
对于问题二,首先我们来了解一下configuration change发生之后会发生什么事情呢?
还记得这个生命周期图吗,Fragment的也是差不多的.
当configuration change发生之后,会先把原来的Activity销毁掉,然后再重新构建一个,
也就是会重走一遍onCreate->onStart->onResume的过程.
(会有其他情况,参照 Activity的ConfigChanges属性)
好了,明白这个之后,我在onStart方法里面找到了线索.
01
Activity:
02
protected
void
onStart() {
03
if
(DEBUG_LIFECYCLE) Slog.v(TAG,
"onStart "
+
this
);
04
mCalled =
true
;
05
06
if
(!mLoadersStarted) {
07
mLoadersStarted =
true
;
08
if
(mLoaderManager !=
null
) {
09
mLoaderManager.doStart();
10
}
else
if
(!mCheckedForLoaderManager) {
11
mLoaderManager = getLoaderManager(
null
, mLoadersStarted,
false
);
12
}
13
mCheckedForLoaderManager =
true
;
14
}
15
16
getApplication().dispatchActivityStarted(
this
);
17
}
18
19
LoaderManagerImp:
20
void
doStart() {
21
if
(DEBUG) Log.v(TAG,
"Starting in "
+
this
);
22
if
(mStarted) {
23
RuntimeException e =
new
RuntimeException(
"here"
);
24
e.fillInStackTrace();
25
Log.w(TAG,
"Called doStart when already started: "
+
this
, e);
26
return
;
27
}
28
29
mStarted =
true
;
30
31
// Call out to sub classes so they can start their loaders
32
// Let the existing loaders know that we want to be notified when a load is complete
33
for
(
int
i = mLoaders.size()-
1
; i >=
0
; i--) {
34
mLoaders.valueAt(i).start();
35
}
36
}
留意doStart的For循环,真相大白了..
最后总结一下:
1、异步是通过AsynTaskLoader来实现的。
2、通过观察者模式来实现监控数据的变化.
3、通过Activity生命周期中的onStart来实现自动重连接.
第二个文章
原文: http://blog.sina.com.cn/s/blog_62c5894901014g5x.html
Google倒是提供了一个标准的Loader,即CursorLoader,它是Loader的标准实现,如果你的数据能够用Cursor表示,比如来自SQLiteDatabase的数据就是标准的Cursor,那么这个类对你而言就够用了,具体如何使用CursorLoader,请参看如下两个例子:
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderThrottle.html
这个例子中牵涉的东西较多,除了我们关注的CursorLoader外,还包括ContentProvider、SQLiteOperHelper、SQLiteDatabase、Fragment、Uri等等常用概念,因此,在仔细阅读了本例后,你将学会如何让这这些类在你的应用中分工协作。
http://developer.android.com/resources/samples/ApiDemos/src/com/example/android/apis/app/LoaderCursor.html
这个例子要简单些,它教你如何获取联系人,虽然简单,却切中要害。
本文若是只关注这些,就没有意思了,毕竟在很多情况下,我们会感觉CursorLoader不顺眼,于是想写一个属于自己的、更帅的Loader,此时抽象类AsyncTaskLoader就会叫嚣着粉墨登场了,该抽象类定义了你的加载器异步加载数据需要实现的接口,那么如何实现呢?你可以去看下面的例子:
http://developer.android.com/reference/android/content/AsyncTaskLoader.html#loadInBackground()
为了让你以及我自己更好的学习自定义Loader这一伎俩,我把里头的关键代码摘了出来,咱们共同分析一番:
public static class AppListLoaderextends AsyncTaskLoader<List<AppEntry>>{
}
诸位请看loadInBackground方法,这是Loader的核心方法,必须得重载,你要在这里头实现加载数据的功能,看看名字就知道,该方法将在后台运行,这方法没有什么特别说的,自己想加什么样的数据,自己清楚。
再看deliverResult方法,当数据到达客户端后,这个方法将被调用,该方法可以不重载,你可以在其中根据需要实现传递数据的逻辑,请注意例子中对两个状态的判断(注:编程,就得养成if的好习惯),一个是isReset()这个方法用来判断Loader是否已经被重置,如果重置了,那么留着资源也没有啥用了,得把它释放掉;一个是isStarted(),如果Loader被启动那么就把数据传递出去:直接调super的传递方法就OK。
onStartLoading方法,必须得重载,且别忘记在里头调用forceLoad方法,你也看到,在例子中人家调用了deliverResult方法,并且创建了一个观察者来接收数据,在调用forceLoad时,请注意相应的条件判断- android loader 详解
- Android Loader详解
- android loader使用详解
- android loader 详解
- Android Loader详解
- Android Loader详解
- android loader 详解
- Android Loader详解
- Android Loader详解
- Android Loader详解
- android loader使用详解
- Android基础--Android Loader详解
- Android Loader详解一:概述
- Android Loader详解一:概述
- Android Loader详解一:概述
- Android Loader详解一:概述
- Android Loader详解一:概述
- Android-Universal-Image-Loader详解
- 阿里oss
- Fast RCNN 训练自己的数据集(3训练和检测)
- 数据结构与算法——无权最短路径算法的C++实现
- tomcat内置连接池管理
- 单例模式,工厂模式,代理模式汇总
- Android Loader详解
- Android5.0查看应用使用情况的权限
- 虚幻4 String2Asset String2Class String2Object
- 2015 Objective-C 三大新特性
- 【Linux学习笔记六】进程管理
- untiy 3d ShaderLab_第6章_VertexLit渲染路径_1_顶点照明
- BZOJ 2005([Noi2010]能量采集-数论)
- Elisp 入门笔记(1)
- swift 懒加载