(一)
Google强烈建议在加载数据时,使用Loaders及其相关的机制。
它可以提供类似于AysncTask一样的异步请求数据加载的功能,实际上它就是来源于AysncTask的扩展并增加了很多人性化的功能,例如加载进度框、更好的控制API等。
(二)
特点:
1.适用于所有Activity和Fragment
2.提供异步加载数据功能
3.监视数据源,当数据发生变化时可以传送数据给界面
(三)
核心类和接口:
Class/Interface | 描述 |
LoaderManager | 一个与Activity和Fragment有关联的抽象类,用于管理一个或多个Loader实例。这有助于app管理长运行操作。使用它的最显著的例子是CursorLoader。每个Activity或Fragment只能有一个LoaderManager。而一个LoaderManager可以有多个loaders。 |
LoaderManager.LoaderCallbacks | 提供给客户端的一个callback接口,用于和LoaderManager进行交互。例如,你可以使用onCreateLoader()callback来创建一个新的loader。 |
AsyncTaskLoader | 一个抽象Loader,提供一个AsyncTask进行工作。 |
CursorLoader | AsyncTaskLoader的子类,用于向ContentResover请求,返回一个Cursor。这个类以标准的游标查询方式实现了Loader协议,建立了AsyncTaskLoader,使用一个后台线程来进行游标查询,不会阻塞app的UI。因此,使用这个loader是从ContentProvider加载异步数据的最好的方式。 |
(四)
Demo1:LoaderManagerExample
该Demo直接抄袭官方例子,用于获取手机里面的通讯录,使用了官方提供的类CursorLoader。
public class CursorLoaderListFragment extends ListFragment
implements SearchView.OnQueryTextListener, LoaderManager.LoaderCallbacks<Cursor> {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
@Override
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
// Give some text to display if there is no data. In a real
// application this would come from a resource.
setEmptyText("No phone numbers");
// We have a menu item to show in action bar.
setHasOptionsMenu(true);
// Create an empty adapter we will use to display the loaded data.
mAdapter = new SimpleCursorAdapter(getActivity(),
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);
setListAdapter(mAdapter);
// Prepare the loader. Either re-connect with an existing one,
// or start a new one.
<span style="white-space:pre"> </span>getLoaderManager().initLoader(0, null, this);
}
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
// Place an action bar item for searching.
MenuItem item = menu.add("Search");
item.setIcon(android.R.drawable.ic_menu_search);
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_IF_ROOM);
SearchView sv = new SearchView(getActivity());
sv.setOnQueryTextListener(this);
item.setActionView(sv);
}
public boolean onQueryTextChange(String newText) {
// Called when the action bar search text has changed. Update
// the search filter, and restart the loader to do a new query
// with this filter.
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getLoaderManager().restartLoader(0, null, this);
return true;
}
@Override
public boolean onQueryTextSubmit(String query) {
// Don't care about this.
return true;
}
@Override
public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[]{
ContactsContract.Contacts._ID,
ContactsContract.Contacts.DISPLAY_NAME,
ContactsContract.Contacts.CONTACT_STATUS,
ContactsContract.Contacts.CONTACT_PRESENCE,
ContactsContract.Contacts.PHOTO_ID,
ContactsContract.Contacts.LOOKUP_KEY,
};
<span style="white-space:pre"> </span>public Loader<Cursor> onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = ContactsContract.Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
String select = "((" + ContactsContract.Contacts.DISPLAY_NAME + " NOTNULL) AND ("
+ ContactsContract.Contacts.HAS_PHONE_NUMBER + "=1) AND ("
+ ContactsContract.Contacts.DISPLAY_NAME + " != '' ))";
<span style="white-space:pre"> </span>return new CursorLoader(getActivity(), baseUri,
CONTACTS_SUMMARY_PROJECTION, select, null,
ContactsContract.Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC");
}
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
}
public void onLoaderReset(Loader<Cursor> loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
LoaderManager.LoaderCallbacks
includes thesemethods:
onCreateLoader()
—Instantiate and return a newLoader
for the given ID.
onLoadFinished()
— Called when a previously created loader has finished its load.
onLoaderReset()
— Called when a previously created loader is being reset, thus making itsdata unavailable.
LoaderManager.LoaderCallbacks包含了三个方法:
onCreateLoader()--- 实例化和返回一个新创建的给定ID的loader
onLoadFinished()--- 当一个创建好的loader完成了load,调用此函数
onLoaderReset()--- 当一个创建好的loader要被reset,调用此函数,这样导致它的数据无效
Demo2:AsyncTaskLoaderExample
有时候CursorLoader并不能满足我们的需要,这时,AsyncTaskLoader就应运而生了,我们可以通过继承AsyncTaskLoader来实现我们想要的异步加载数据的功能(比如从网络上获取数据)。
public class MyAsyncTaskLoader extends AsyncTaskLoader<List<App>> {
private List<App> mApps;
public MyAsyncTaskLoader(Context context) {
super(context);
}
<strong> @Override
public List<App> loadInBackground() {
System.out.println("LifeCycle loadInBackground");
HttpClient httpClient = new DefaultHttpClient();
HttpGet httpGet = new HttpGet("yourdata.google.com");
mApps = new ArrayList<App>();
try {
HttpResponse httpResponse = httpClient.execute(httpGet);
if (httpResponse.getStatusLine().getStatusCode() == 200) {
String result = EntityUtils.toString(httpResponse.getEntity());
Gson gson = new Gson();
AppJson appJson = gson.fromJson(result, AppJson.class);
mApps = appJson.getData().getApps();
}
} catch (IOException e) {
e.printStackTrace();
}
return mApps;
}
@Override
protected void onStartLoading() {
System.out.println("LifeCycle onStartLoading");
forceLoad();
}
}
这样之后我们就可以在Activity中使用了:
public class MyActivity extends ActionBarActivity implements LoaderManager.LoaderCallbacks<List<App>> {
private ListView mListView;
private ProgressBar mProgressBar;
private MyAdapter mMyAdapter;
private ArrayList<App> mApps;
@Override
protected void onCreate(Bundle savedInstanceState) {
System.out.println("LifeCycle MyActivity->onCreate");
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_my);
mListView = (ListView) findViewById(R.id.listView);
mProgressBar = (ProgressBar) findViewById(R.id.progressBar);
mApps = new ArrayList<App>();
mMyAdapter = new MyAdapter(this, mApps);
mListView.setAdapter(mMyAdapter);
getSupportLoaderManager().initLoader(0, null, this);
}
@Override
public Loader<List<App>> onCreateLoader(int id, Bundle args) {
System.out.println("LifeCycle MyActivity->onCreateLoader");
return new MyAsyncTaskLoader(this);
}
@Override
public void onLoadFinished(Loader<List<App>> loader, List<App> data) {
System.out.println("LifeCycle MyActivity->onLoadFinished");
mProgressBar.setVisibility(View.GONE);
if (data instanceof ArrayList) {
mApps.clear();
mApps.addAll(data);
mMyAdapter.notifyDataSetChanged();
}
}
@Override
public void onLoaderReset(Loader<List<App>> loader) {
System.out.println("LifeCycle MyActivity->onLoaderReset");
mApps.clear();
mMyAdapter.notifyDataSetChanged();
mProgressBar.setVisibility(View.INVISIBLE);
}
}
Demo3:AdvancedLoaderManagerExample
在上面的例子都是简单的例子,并没有涉及判断一些条件和释放资源什么的,下面这个还是官方的例子,用于获取手机中安装的所有程序,有很多关于释放资源的,代码就不贴了,直接拷代码吧。
(五)备注:
loadInBackground方法
这是Loader的核心方法,必须重载,在这个方法里做的就是繁重的任务,比如获取网络数据啊什么的。
deliverResult方法
当数据到达客户端后,这个方法将被调用,该方法可以不重载,注意官方例子种的两个if语句:一个是isReset()这个方法用来判断Loader是否已经被重置,如果重置了,把资源释放掉;一个是isStarted(),如果Loader被启动那么就把数据传递出去(调super的传递方法)。
onStartLoading方法
这个它似乎没有强制我们重载,但是如果没有在里面调用forceLoad()方法,你会发现loadInBackground不会被调用(这个略坑爹)。