1、好处
个人觉得就是用起来方便,之前在查询数据的时候还得创建一个子线程,因为数据量过大的话可能导致程序ANR,但Loader内部直接创建了一个AsyncTask来解决这个问题。再就是当原始数据改变时,会收到通知,这个功能很强大。比如我们用一个ListView显示所有联系人,然后现在不退出应用,去通讯录添加一个联系人,在回来时界面就会自动改变。为什么会这么神奇,一会就知道了。
2、如何使用
比如我们获取一下手机sd上的所有图片,我把所有的图片名称都显示到listView上面。
Activity代码
public class ImageActivity extends AppCompatActivity {
private ListView mListView;
private ArrayAdapter<String> mAdapter;
private List<String> mData = new ArrayList<>();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_image);
initView();
getSupportLoaderManager().initLoader(0, null, mLoaderCallback);
}
private void initView() {
mListView = (ListView) findViewById(R.id.list_view);
mAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, mData);
mListView.setAdapter(mAdapter);
}
private LoaderManager.LoaderCallbacks<Cursor> mLoaderCallback = new LoaderManager.LoaderCallbacks<Cursor>() {
private final String[] IMAGE_PROJECTION = {
MediaStore.Images.Media.DATA, //图片的路径
MediaStore.Images.Media.DISPLAY_NAME,//图片的名称
MediaStore.Images.Media.DATE_ADDED, //图片的日期
MediaStore.Images.Media._ID}; //图片的id
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
CursorLoader cursorLoader = new CursorLoader(ImageActivity.this,
MediaStore.Images.Media.EXTERNAL_CONTENT_URI, IMAGE_PROJECTION,
null, null, IMAGE_PROJECTION[2] + " DESC");
return cursorLoader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
if (data != null) {
List<Image> images = new ArrayList<>();
int count = data.getCount();
if (count > 0) {
data.moveToFirst();
do {
String path = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[0]));
String name = data.getString(data.getColumnIndexOrThrow(IMAGE_PROJECTION[1]));
long dateTime = data.getLong(data.getColumnIndexOrThrow(IMAGE_PROJECTION[2]));
Image image = new Image(path, name, dateTime);
images.add(image);
} while (data.moveToNext());
}
Collections.addAll(mData, images.toString());
mAdapter.notifyDataSetChanged();
}
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
};
}
Image实体类
public class Image {
public String path;
public String name;
public long time;
public Image(String path, String name, long time){
this.path = path;
this.name = name;
this.time = time;
}
@Override
public String toString() {
return "\n" + "path:" + path.substring(path.lastIndexOf('/')) + "\n";//一开始打算把名字和时间都显示出来,但是太乱了,所以就显示了一个文件名
}
}
就这些使用起来非常简单
再来看一下,查寻手机上的联系人,看起来和查询手机上的图片差不多,相比之前,使用ContentResolver.query查询要方便的多。不过这个的查询的实现还是调用的ContentResolver.query方法。
public class ContactActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks<Cursor> {
private ListView mListView;
private SimpleCursorAdapter mAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mAdapter = new SimpleCursorAdapter(this,
android.R.layout.simple_list_item_2, null,
new String[]{ContactsContract.Contacts.DISPLAY_NAME, ContactsContract.Contacts.CONTACT_STATUS},
new int[]{android.R.id.text1, android.R.id.text2}, 0);
mListView = (ListView) findViewById(R.id.list_view);
mListView.setAdapter(mAdapter);
//这个地方初始化了我们的loader
getLoaderManager().initLoader(0, null, this);
}
/** 查询的列 */
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri = Contacts.CONTENT_URI;
//查询条件
String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ Contacts.DISPLAY_NAME + " != '' ))";
return new CursorLoader(ContactActivity.this, baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
}
总之使用的步骤是,
1、需要在Activity或Fragment里面,LoaderManager只能在Activity获取到
2、调用getSupportLoaderManager().initLoader(0, null, mLoaderCallback);加载数据
3、实现LoaderCallbacks接口,然后在onCreateLoader方法中创建一个Cursor(注意这个方法是在子线程中运行的)。
4、创建完成后会调用onLoadFinished方法,在这个方法中拿到数据就可以进行显示了。
3、实现原理
先说一下
getSupportLoaderManager().initLoader(0, null, mLoaderCallback);
做了什么,因为我们要初始化数据总是要调用这一句代码。Activity与Fragment都可以调用,先看一下Fragment把
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
}
if (mActivity == null) {
throw new IllegalStateException("Fragment " + this + " not attached to Activity");
}
mCheckedForLoaderManager = true;
mLoaderManager = mActivity.getLoaderManager(mWho, mLoadersStarted, true);
return mLoaderManager;
}
可以看到Fragment的mLoaderManager 是由Activity的getLoaderManager方法来创建的,看一下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;
}
LoaderManager是一个抽象类,它的实现类就是LoaderManagerImpl。可以看到getLoaderManager方法会通过mAllLoaderManagers去get一个LoaderManagerImpl如果没有获取到,则创建一个。fragment传过来的key是一个变量mWho,看一下这个mWho赋值的地方。
final void setIndex(int index, Fragment parent) {
mIndex = index;
if (parent != null) {
mWho = parent.mWho + ":" + mIndex;
} else {
mWho = "android:fragment:" + mIndex;
}
}
通过这个可以看出每一个mWho都是唯一的值,这时在看一下Activity的getLoaderManager在创建的时候给的是什么。
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
}
mCheckedForLoaderManager = true;
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
return mLoaderManager;
}
“root”?是的!没错就是root!现在可以看出不管是那个Fragment创建的LoaderManager都会添加到Activity的map里面来进行统一维护。
再看一下它的initLoader方法都执行了那些操作
public abstract <D> Loader<D> initLoader(int id, Bundle args,LoaderManager.LoaderCallbacks<D> callback);
额,就一个抽象方法,还是去看它的实现类LoaderManagerImpl的initLoader方法把。
@SuppressWarnings("unchecked")
public <D> Loader<D> initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks<D> callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
if (DEBUG) Log.v(TAG, "initLoader in " + this + ": args=" + args);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks<Object>)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader<D>)info.mLoader;
}
这里的LoaderInfo类见名知意,Loader类的一些信息,比如Loader的一些状态、Loader对象,传进来的callback。当我们第一次调用这个方法时mLoaders.get(id)返回的肯定是null,也就会调用createAndInstallLoader。
private LoaderInfo createAndInstallLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
try {
mCreatingLoader = true;
LoaderInfo info = createLoader(id, args, callback);
installLoader(info);
return info;
} finally {
mCreatingLoader = false;
}
}
这里其实主要看一下createLoader方法就可以了
private LoaderInfo createLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks<Object> callback) {
LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks<Object>)callback);
Loader<Object> loader = callback.onCreateLoader(id, args);
info.mLoader = (Loader<Object>)loader;
return info;
}
在这个方法中调用的callback.onCreateLoader(id, args);来创建一个Loader,这个callback就是我们一开始通过initLoader传进来的。创建完了,现在去看一下当数据改变时是怎么执行的。
类的关系:CursorLoader继承于AsyncTaskLoader,AsyncTaskLoader继承于Loader
@Override
public Cursor loadInBackground() {
synchronized (this) {
if (isLoadInBackgroundCanceled()) {
throw new OperationCanceledException();
}
mCancellationSignal = new CancellationSignal();
}
try {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) {
try {
// Ensure the cursor window is filled.
cursor.getCount();
cursor.registerContentObserver(mObserver);
} catch (RuntimeException ex) {
cursor.close();
throw ex;
}
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
}
当数据发生改变时会调用CursorLoader的loadInBackground可以看到在这个方法中用了ContentResolver进行了查询,不过这个是子类的方法要分析过程还是的看父类
Loader类
public final class ForceLoadContentObserver extends ContentObserver {
public ForceLoadContentObserver() {
super(new Handler());
}
@Override
public boolean deliverSelfNotifications() {
return true;
}
@Override
public void onChange(boolean selfChange) {
onContentChanged();
}
}
这个是Loader的一个内部类,很明显是一个观察者模式,当你注册之后,数据发生改变会调用onContentChanged();
public void onContentChanged() {
if (mStarted) {
forceLoad();
} else {
mContentChanged = true;
}
}
onContentChanged()方法又调用了forceLoad();
public void forceLoad() {
onForceLoad();
}
protected void onForceLoad() {
}
可以看到当数据发生改变是最后会调用onForceLoad方法这个方法是protected的,也就是说需要子类去实现,看一看它的子类AsyncTaskLoader是如何实现的。
@Override
protected void onForceLoad() {
super.onForceLoad();
cancelLoad();
mTask = new LoadTask();
if (DEBUG) Log.v(TAG, "Preparing load: mTask=" + mTask);
executePendingTask();
}
这里创建了一个LoadTask()在executePendingTask();方法中会在线程池中执行这个任务,看一下LoadTask类,这个类很重要.
//AsyncTaskLoader的内部类LoadTask
final class LoadTask extends AsyncTask<Void, Void, D> implements Runnable {
//主要方法
@Override
protected D doInBackground(Void... params) {
if (DEBUG) Log.v(TAG, this + " >>> doInBackground");
try {
D data = AsyncTaskLoader.this.onLoadInBackground();
if (DEBUG) Log.v(TAG, this + " <<< doInBackground");
return data;
} catch (OperationCanceledException ex) {
if (!isCancelled()) {
throw ex;
}
if (DEBUG) Log.v(TAG, this + " <<< doInBackground (was canceled)", ex);
return null;
}
}
}
它调用了onLoadInBackground();
protected D onLoadInBackground() {
return loadInBackground();
}
loadInBackground()方法是一个抽象方法需要子类去实现。刚才上面的CursorLoader的代码就是这个抽象方法的实现,CursorLoader类在loadInBackground中有这样一行代码。
cursor.registerContentObserver(mObserver);
这个mObserver的类型就是Loader的内部类ForceLoadContentObserver。到这里总算缕清了了,注册观察者,然后通知观察者的逻辑。
查询完事了,在说一下,它是什么时候调用的onLoadFinished来通知我们进行界面更新的。还是在LoadTask中,doInBackground方法执行子类的耗时查询,执行完之后肯定会调用onPostExecute。
@Override
protected void onPostExecute(D data) {
if (DEBUG) Log.v(TAG, this + " onPostExecute");
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
mDone.countDown();
}
}
它又调用了dispatchOnLoadComplete
void dispatchOnLoadComplete(LoadTask task, D data) {
if (mTask != task) {
if (DEBUG) Log.v(TAG, "Load complete of old task, trying to cancel");
dispatchOnCancelled(task, data);
} else {
if (isAbandoned()) {
// This cursor has been abandoned; just cancel the new data.
onCanceled(data);
} else {
commitContentChanged();
mLastLoadCompleteTime = SystemClock.uptimeMillis();
mTask = null;
if (DEBUG) Log.v(TAG, "Delivering result");
deliverResult(data);
}
}
}
这个方法调用了deliverResult
public void deliverResult(D data) {
if (mListener != null) {
mListener.onLoadComplete(this, data);
}
}
这个mListener在Loader中,是Loader的内部类就这一个方法, 并通过Loader类的registerListener方法来赋值。
public interface OnLoadCompleteListener<D> {
public void onLoadComplete(Loader<D> loader, D data);
}
public void registerListener(int id, OnLoadCompleteListener<D> listener) {
if (mListener != null) {
throw new IllegalStateException("There is already a listener registered");
}
mListener = listener;
mId = id;
}
看mListener的赋值又得回到LoaderManagerImpl中,在LoaderManagerImpl里面的内部类LoaderInfo中实现了这个接口,并在调用initLoader方法时会Loader的registerListener方法,主要看一下LoaderInfo实现的onLoadComplete方法。
@Override
public void onLoadComplete(Loader<Object> loader, Object data) {
//主要代码
if (mData != data || !mHaveData) {
mData = data;
mHaveData = true;
if (mStarted) {
callOnLoadFinished(loader, data);
}
}
//...省略
}
调用了callOnLoadFinished方法
void callOnLoadFinished(Loader<Object> loader, Object data) {
//主要代码
mCallbacks.onLoadFinished(loader, data);
//...省略
}
还有一个onLoaderReset方法,这个方法是在Activity和Fragment,销毁时调用,所以在这个方法可以做一些移除Loader引用的操作。这回总算知道了这三个回调方法是怎么回事了。关于他的生命周期是如何管理的读者自己感兴趣的话自己分析试试看吧。当然强大Loader还不止这些,你可以继承AsyncTaskLoader来实现一些自己的逻辑,比如说数据库操作。
http://blog.csdn.net/murphykwu/article/details/35287883
http://www.codeceo.com/article/android-asyn-loader.html