Loader是一個異步加載數據的類,它和AsyncTask有相似也有不一樣,今天咱們就先來學習下它。因爲是對比學習,因此咱們先來複習下AsyncTask的使用和特色。html
1、AsyncTaskandroid
參考自:http://www.open-open.com/lib/view/open1417955629527.html數據庫
package com.example.jacktony.myapplication03; import android.os.AsyncTask; /** * Created by Jack Tony on 2014/12/15. */ public class Task extends AsyncTask<String, Integer, Long>{ @Override protected void onPreExecute() { super.onPreExecute(); } @Override protected Long doInBackground(String... params) { return null; } @Override protected void onProgressUpdate(Integer... values) { super.onProgressUpdate(values); } @Override protected void onPostExecute(Long aLong) { super.onPostExecute(aLong); } @Override protected void onCancelled(Long aLong) { super.onCancelled(aLong); } }
1.1 它何時會被結束服務器
關於AsyncTask存在一個這樣普遍的誤解,不少人認爲一個在Activity中的AsyncTask會隨着Activity的銷燬而銷燬。而後事實並不是如此。AsyncTask會一直執行doInBackground()方法直到方法執行結束。一旦上述方法結束,會依據狀況進行不一樣的操做。app
AsyncTask的cancel方法須要一個布爾值的參數,參數名爲mayInterruptIfRunning,意思是若是正在執行是否能夠打斷
, 若是這個值設置爲true,表示這個任務能夠被打斷,不然,正在執行的程序會繼續執行直到完成。若是在doInBackground()方法中有一個循環操做,咱們應該在循環中使用isCancelled()來判斷,若是返回爲true,咱們應該避免執行後續無用的循環操做。異步
總之,咱們使用AsyncTask須要確保AsyncTask正確地取消。async
1.2 奇怪的cancel()ide
若是你調用了AsyncTask的cancel(false),doInBackground()仍然會執行到方法結束,只是不會去調用onPostExecute()方法。可是實際上這是讓應用程序執行了沒有意義的操做。那麼是否是咱們調用cancel(true)前面的問題就能解決呢?並不是如此。若是mayInterruptIfRunning設置爲true,會使任務儘早結束,可是若是的doInBackground()有不可打斷的方法,則它就會失效,好比這個BitmapFactory.decodeStream() IO操做。可是你能夠提早關閉IO流並捕獲這樣操做拋出的異常。可是這樣會使得cancel()方法沒有任何意義。函數
還有一種常見的狀況就是,在Activity中使用非靜態匿名內部AsyncTask類,因爲Java內部類的特色,AsyncTask內部類會持有外部類的隱式引用。詳細請參考細話Java:」失效」的private修飾符,因爲AsyncTask的生命週期可能比Activity的長,當Activity進行銷燬AsyncTask還在執行時,因爲AsyncTask持有Activity的引用,致使Activity對象沒法回收,進而產生內存泄露。因此很不建議用內部類來作異步處理。學習
另外一個問題就是在屏幕旋轉等形成Activity從新建立時AsyncTask數據丟失的問題。當Activity銷燬並創新建立後,還在運行的 AsyncTask會持有一個Activity的非法引用即以前的Activity實例。致使onPostExecute()沒有任何做用。
1.5 串行 or 並行
Android團隊從3.0開始認爲,開發者可能並不喜歡讓AsyncTask並行,因而Android團隊又把AsyncTask改爲了串行。固然這一次的修改並無徹底禁止 AsyncTask並行。你能夠經過設置executeOnExecutor(Executor)來實現多個AsyncTask並行。關於API文檔的描述以下
If we want to make sure we have control over the execution, whether it will run serially or parallel, we can check at runtime with this code to make sure it runs parallel:
public static void execute(AsyncTask as) { if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.HONEYCOMB_MR1) { as.execute(); } else { as.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } } //(This code does not work for API lvl 1 to 3)
並不是如此,使用AsyncTask雖然能夠以簡短的代碼實現異步操做,可是正如本文提到的,你須要讓AsyncTask正常工做的話,須要注意不少條條框框。推薦的一種進行異步操做的技術就是使用Loaders。下面就來談談這個從3.0引入的類,support包中也對低版本添加了支持。
2、Loader的特色
Loaders使得在一個Activity或fragment中異步加載數據變得容易。loaders有這些特性:
若是你想在一個Activity或者Fragment裏面進行異步數據的加載,不要再使用AsyncTask了。也不要認爲Loader只是用來作數據庫相關的事情(CursorLoaders),他能夠作不少事情。
3、Loader的管理者——LoaderManager
Loader是由一個loaderManager來管理的,每個Activity或Fragment都只有一個LoaderManager,可是一個LoaderManager能夠包含多個加載器。
3.1 創建loaderManager
在activity或者fragment中用getSupportLoaderManager()或getLoaderManager()獲得這個對象就好了。
getSupportLoaderManager()
getLoaderManager()
3.2 初始化loader的回調函數
getSupportLoaderManager().initLoader(0, null, new MyLoaderCallback());
經過initLoader就能夠初始化一個loader了,這個初始化很不完全,其實就是經過它能觸發回調方法中的onCreateLoader()方法,而onCreateLoader()方法會返回一個loader,因此這裏的初始化並無真正new一個對象,而是調用了onCreateLoader()來初始化,因此你須要在回調函數中的onCreateLoader()進行處理。由於咱們如今是學習loaderManager,還沒涉及到loader,因此在onCreatLoader中咱們沒創建一個loader,返回一個null對象。這也側面證實了initloader()並無真正初始化一個真正的loader,真正的初始化是在onCreatLoader()中進行的。
private class MyLoaderCallback implements LoaderManager.LoaderCallbacks { final private String TAG = getClass().getSimpleName(); /** * @param id * @param args * @return Object returned from onCreateLoader must not be a non-static inner member class */ @Override public Loader onCreateLoader(int id, Bundle args) { Log.i(TAG, "onCreateLoader"); //return new MyAsyncLoader(getApplicationContext());
return null; } @Override public void onLoadFinished(Loader loader, Object data) { Log.i(TAG, "onLoadFinished"); } @Override public void onLoaderReset(Loader loader) { Log.i(TAG, "onLoaderReset"); } }
3.3 參數
1.第一個參數須要在當前Activity範圍內找一個惟一的id進行傳入,方便控制loader。
2.第二個參數能夠傳入bundle,不少時候你不須要傳入任何參數,置null便可。
Bundle args = new Bundle(); args.putString("query",query); getLoaderManager().restartLoader(LOADER_ID,args, loaderCallbacks);
在Callback中獲得這個bundle
@Override public Loader onCreateLoader(int id, Bundle args) { Log.i(TAG, "onCreateLoader"); return new MyAsyncLoader(getApplicationContext()); }
3.4 結果
initLoader()的調用確保加載器被初始化和激活。它有兩個可能的結果:
① 若是這個加載器指定的id已經存在,上一次被建立的加載器就被重用。
② 若是這個加載器指定的id不存在,initLoader()方法觸發LoaderManager.LoaderCallbacks
方法onCreateLoader()。這是你實現實例化和返回一個新的加載器代碼的地方。
不管在任何狀況下,提供的LoaderManager.LoaderCallBacks實現都和加載器有關,並且將會在加載器狀態改變時被調用。若是調用調用者是在它的開始狀態,並且請求的加載器已經存在還產生了數據,接着系統就會直接調用onLoadFinished()方法(在initLoader()期間),因此你必須準備好這種狀況的發生。也就是說,這個加載器已經被初始化而且啓動了,那麼再initLoader的時候就會直接調用onLoadFinished()方法。(這中文翻譯的好蛋疼T_T)
3.5 初始化Loader後怎麼要用它麼?
initLoader()方法返回的是已經被建立的加載器(前提是你在callback中確實初始化了一個loader),可是你不須要獲取對它的引用。LoaderManager自動管理加載器的生命。Loadermanager在必要的時候啓動和中止加載,並且保持加載器的狀態和它關聯的內容。這意味着,你不多直接與加載器交互。當一個特別的事件發生時你一般使用LoaderManager.LoaderCallbacks的方法進行干預。總之,你如今進行的僅僅是一個初始化的動做,而不須要對這個初始化的loader作任何處理。
3.6 複用loader和從新初始化loader
當你像上面展現的那樣使用initLoader()時,若是對於指定的id的加載器已經存在了一個那將使用時這個存在的。若是沒有,將建立一個。可是有時你但願拋棄原來的老數據從新開始。
爲了清除你的老數據,你須要使用restartLoader()方法。
getLoaderManager().restartLoader(0, null, this);
當一個Fragment由於configuration變化被從新建立的時候(好比旋轉屏幕,改變語言等),你在onActivityCreated()裏面調用了initLoader()以後,他的LoaderManager調用了兩次onLoadFinished。
解決方案:
1. 若是代碼邏輯容許,能夠不用處理。
2. 將以前的result保存起來,檢查結果是否有變化。
3. 在onCreate()裏面調用setRetainInstance(true)。
3.8 一次性的loader
有時候咱們僅僅但願laoder啓動一次,好比:咱們點擊一個按鈕而後提交一些信息到服務器。在用戶點擊了一個按鈕後,咱們調用initLoader,這個時候用戶旋轉屏幕,在旋轉完屏幕以後,咱們想要使用以前loader獲得的結果。
注意:在旋轉屏幕的時候,咱們的Loader尚未提交完數據。像咱們以前用AsyncTask的話,是沒有辦法在Activity/Fragment從新建立以後拿到以前任務返回的result的。可是使用Loader就簡單多了。
解決方案:
在loaderManager中刪除掉這個id的loader,由於loaderManager可能管理多個loader,因此要用id來判斷
@Override public void onLoadFinished(Loader loader, Object data) { Log.i(TAG, "onLoadFinished"); getLoaderManager().destroyLoader(LOADER_ID); }
在Activity / Fragment建立時進行邏輯判斷,若是當前這個id的loader真正運行,那麼就從新初始化一下,若是沒有這個id的loader,那麼就不作任何處理(沒實際測試過效果)。
這裏用fragment舉例:
@Override public void onActivityCreated(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ... //Reconnect to the loader only if present if(getLoaderManager().getLoader(LOADER_ID) != null) { getLoaderManager().initLoader(LOADER_ID,null, this); } }
3.9 避免出現FragmentManager exceptions
你不能在LoaderCallback這些回調裏面直接建立FragmentTransaction(包括DialogFragment這種dialog)。解決方法就是使用Handler來處理FragmentTransaction。
4、Loader
終於來到了咱們的重點了,話說前面說了好多好多啊,終於鋪墊完畢了。
Loader一個執行異步加載數據的抽象類,這是一個加載器的基類。你通常可使用AsyncTaskLoader或CursorLoader,固然你也能夠實現本身的子類。當加載器被激活時,它們能夠被用來監視它們的數據資源,和在內容改變時發送新的結果。
注意:Loader只能是static的,否則的話他們就會保持一個對outer class的引用。
4.1 經常使用類
AsyncTaskLoader
一個提供異步任務的加載器
CursorLoader
一個用來查詢數據庫相關的加載器,是AsyncTaskLoader的子類。
4.2 回調方法
重要的回調
onStartLoading() onStopLoading() onReset() onForceLoad() // from Loader ORloadInBackground() // from AsyncTaskLoader
可選的回調
deliverResult() [override]
4.3 創建一個Loader類(必須是static class)
private class MyLoaderCallback implements LoaderManager.LoaderCallbacks { final private String TAG = getClass().getSimpleName(); /** * @param id * @param args * @return Object returned from onCreateLoader must not be a non-static inner member class */ @Override public Loader onCreateLoader(int id, Bundle args) { Log.i(TAG, "onCreateLoader"); return new MyAsyncLoader(getApplicationContext()); } @Override public void onLoadFinished(Loader loader, Object data) { Log.i(TAG, "onLoadFinished " + data); } @Override public void onLoaderReset(Loader loader) { Log.i(TAG, "onLoaderReset"); } }
public static class MyAsyncLoader extends AsyncTaskLoader<String> { final private String TAG = "MyAsyncLoader"; public MyAsyncLoader(Context context) { super(context); } @Override protected void onStartLoading() { super.onStartLoading(); Log.i(TAG, "onStartLoading");
forceLoad(); } @Override public String loadInBackground() { Log.i(TAG, "loadInBackground"); return "kale"; } @Override public void deliverResult(String data) { Log.i(TAG, "deliverResult"); } @Override protected void onStopLoading() { super.onStopLoading(); Log.i(TAG, "onStopLoading"); cancelLoad(); // Attempt to cancel the current load task if possible } @Override protected void onReset() { Log.i(TAG, "onReset"); super.onReset(); } }
輸出日誌:
initLoader時:
退出activity時
分析:
initLoader時首先調用callback中的onCreateLoader方法,返回一個loader,而後loaderManager開始管理這個loader,直接執行loader的onStartLoading方法,咱們能夠在這裏作點準備工做,準備工做作完了後就經過forceLoad()來執行loadInBackground()方法,在這裏進行加載各類數據,這個方法執行完畢後在callback中就會自動調用onLoadFinished()方法,告訴activity已經加載結束了,而且在public void onLoadFinished(Loader loader, Object data) 中,咱們能夠獲得結果的data對象。
分析:回調方法所在的線程
這個徹底能夠類比到AsyncTask,以前也有預料到這個結果,loadInBackground()是在主線程以外的線程運行的,其他的回調方法都是在主線程運行的。因此能夠這麼理解onStartLoading()作一些初始化的工做,真正作處理的代碼要放在loadInBackground()中,loadInBackground()中的代碼執行完畢後會自動傳輸數據,咱們在callback回調中接收就行了。至於怎麼結束,咱們能夠不怎麼管他,由於在當前activity退出時,它會自動調用onStop(),onRest(),我們在這裏面能夠作點收尾工做。
4.4 一個較爲完整的例子
通常咱們不可能就這麼簡單的用loader,裏面應該有一些邏輯處理,好比作點開始的準備和收尾工做之類的。
public static class MyAsyncLoader extends AsyncTaskLoader<String> { final private String TAG = "MyAsyncLoader"; private String mResult; public MyAsyncLoader(Context context) { super(context); } @Override protected void onStartLoading() { super.onStartLoading(); Log.i(TAG, "onStartLoading"); if (mResult != null) { //If we currently have a result available, deliver it immediately. deliverResult(mResult); } if (takeContentChanged() || mResult == null) { //If the data has changed since the last time it was loaded //or is not currently available, start a load. forceLoad(); // it'll call loadInBackground task } } @Override public String loadInBackground() { Log.i(TAG, "loadInBackground"); return "kale"; } @Override public void deliverResult(String data) { Log.i(TAG, "deliverResult"); if (isStarted()) { //If the Loader is currently started, we can immediately deliver its results. super.deliverResult(data); } } @Override protected void onStopLoading() { super.onStopLoading(); Log.i(TAG, "onStopLoading"); cancelLoad(); // Attempt to cancel the current load task if possible } @Override protected void onReset() { Log.i(TAG, "onReset"); super.onReset(); mResult = null; } }
4.5 CursorLoader
CursorLoader繼承自AsyncLoader,因此就不在多說了,它主要處理數據查詢方面的工做。
public static class KaleCursorLoader extends CursorLoader{public KaleCursorLoader(Context context) { super(context); } }
5、LoaderManager.LoaderCallbacks回調的方法
以前說了不少asyncLoader的東西,這裏正好用CursorLoader作例子。
5.1 onCreateLoader
當你試圖訪問一個裝載器時(例如,經過initLoader()),它會檢查那個裝載器是否被已存在的id所指定。若是它沒有,它將觸發LoaderManager.LoaderCallbacks
的onCreateLoader()方法。
在下面例子中,onCreateLoader()回調方法建立了一個CursorLoader。你必須使用它的構造方法來建立一個CursorLoader,它須要對ContentProvider執行一個查詢所須要的全套的信息。它可能須要:
// If non-null, this is the current filter the user has provided. String mCurFilter; ... 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(Contacts.CONTENT_FILTER_URI, Uri.encode(mCurFilter)); } else { baseUri = Contacts.CONTENT_URI; } // Now create and return a CursorLoader that will take care of // creating a Cursor for the data being displayed. String select = "((" + Contacts.DISPLAY_NAME + " NOTNULL) AND ("+ Contacts.HAS_PHONE_NUMBER + "=1) AND ("+ Contacts.DISPLAY_NAME + " != '')); return new CursorLoader(getActivity(), baseUri,CONTACTS_SUMMARY_PROJECTION, select, null, Contacts.DISPLAY_NAME + " COLLATE LOCALIZED ASC"); }
這裏怎麼去理解呢?就是說這個loader是查詢數據庫的,我給交給這個loader進行處理的前先把數據的URI,查詢的語句(select),排序方式(order by)等等做爲構造函數的參數傳遞到loader中,這個loader用這些東西去查究好了。
5.2 onLoadFinished
當先前建立的裝載器已經完成它的裝載工做時此方法將會被調用。這個方法要保證在爲這個裝載器提供的最終數據釋放以前被調用。在此刻你應該清除掉全部使用的舊數據(因爲它將很快被釋放),可是不要清除你本身傳遞(發佈)的數據,由於它的裝載機擁有它並將管理它。
一旦裝載機知道應用程序再也不使用那些數據就會釋放它們。例如,若是數據是從一個CursorLoader返回的cursor,你本身就應該調用它的close()方法。若是遊標被放置在一個CursorAdapter中,你應該使用swapCursor()方法以便舊的Cursor也被關掉。例如:
@Override public void onLoadFinished(Loader<Cursor> loader, Cursor data) { // do something data.close(); }
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ...
@Override 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); }
5.3 onLoaderReset
當先前被建立的裝載機被重置的時候這個方法就被調用,從而使它的數據無效。這個回調可讓你找到數據何時將要被釋放,這樣你就能夠釋放掉對它的引用。若是遊標被放置在一個CursorAdapter中,你應該實現調用swapCursor同時傳遞一個空值。
// This is the Adapter being used to display the list's data. SimpleCursorAdapter mAdapter; ... 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); }
6、給Loader添加進度 & 錯誤處理機制
6.1 添加進度
Loader不像AsyncTask那樣能夠有一個進度的回調方法,因此這裏要經過LocalBroadcastManager來進行處理。經過廣播的形式來傳遞進度,下面僅僅是一個舉例,和實際使用無關。
@Override protected void onStart() { //Receive loading status broadcasts in order to update the progress bar LocalBroadcastManager.getInstance(this).registerReceiver(loadingStatusReceiver, new IntentFilter(MyLoader.LOADING_ACTION)); super.onStart(); } @Override protected void onStop() { super.onStop(); LocalBroadcastManager.getInstance(this).unregisterReceiver(loadingStatusReceiver); } @Override public Result loadInBackground() { // Show progress bar Intent intent = new Intent(LOADING_ACTION).putExtra(LOADING_EXTRA,true); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); try { return doStuff(); } finally { // Hide progress bar intent = newIntent(LOADING_ACTION).putExtra(LOADING_EXTRA, false); LocalBroadcastManager.getInstance(getContext()).sendBroadcast(intent); } }
開始的時候創建廣播對象,結束的時候註銷廣播,在運行的時候能夠在某個時期發送廣播。
6.2 添加錯誤處理
你遇到error的時候一般只能簡單的返回null。
解決方法:
① 封裝一個結果和exception。好比Pair<T,Exception>。你的Loader的cache須要更智能一些,他能夠檢查result是否爲null或者是否有error。
② 在Loader中加入一個exception的變量。若是在出錯的時候給這個變量賦值,在finish時傳遞出去這個值,而後咱們就能夠在callback中進行檢查處理了。
public abstract class ExceptionSupportLoader<T>extends AsyncTaskLoader<T> {
private Exception lastException;
public ExceptionSupportLoader(Context context) { super(context); }
public Exception getLastException() { return lastException; }
@Override public T loadInBackground() { try{ return tryLoadInBackground(); } catch(Exception e) { this.lastException= e; return null; } } protected abstract T tryLoadInBackground() throws Exception; }
@Override public void onLoadFinished(Loader<Result>loader, Result result) { if(result == null) { Exception exception = ((ExceptionSupportLoader<Result>) loader).getLastException(); //Error handling } else { //Result handling } }
英文資源參考(PPT):http://download.csdn.net/detail/shark0017/8265393
參考自:
http://blog.csdn.net/dxj007/article/details/7880417
http://www.open-open.com/lib/view/open1417955629527.html
http://blog.csdn.net/liaoqianchuan00/article/details/24094913