您的位置:首页 > 移动开发 > Android开发

Android之Loader理解

2016-02-18 15:44 435 查看
在看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
 
onActivity
4000
Created(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

1ba3b
 
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这个SparseArrayCompat中,这个对象可以理解为一个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的过程.

好了,明白这个之后,我在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来实现自动重连接.
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签: