Android知識點回顧之Loader

星泉毅發表於2017-12-06

Loader是谷歌在Android 3.0引入的非同步載入機制,能夠對資料非同步載入並顯示到Activity或Fragment上,使用者不需要對資料的生命週期進行管理,而是交給Loader機制來管理。

使用Loader的優點

假如我們需要從網路上獲取資料,通常的做法是使用子執行緒Thread+Handler或者是使用AsyncTask來處理。

Thread+Handler方法實現起來簡單直觀,不過會麻煩點,需要自己實現Handler子類,建立執行緒,還要管理Handler的生命週期。

AsyncTask實現起來會簡單些,無需自己管理執行緒和Handler。但是要管理AsyncTask的生命週期,要對Activity退出時的情況進行處理。否則可能會出現異常或記憶體洩露。

使用Loader無需關心執行緒和Handler的建立和銷燬,也無需自己管理資料整個的生命週期,Loader機制會自動幫我們處理好。我們唯一要處理的就是資料本身。

Loader使用的步驟:

  • 建立FragmentActivity或Fragment
  • 持有LoaderManager的例項
  • 實現Loader,用來載入資料來源返回的資料
  • 實現LoaderManager.LoaderCallbacks介面
  • 實現資料的展示
  • 提供資料的資料來源,如ContentProvider,伺服器下發的資料等

幾個相關的類

LoaderManager

管理Loader例項,並使之和FragmentActiivty或Fragment關聯上

一個Activity或Fragment有一個唯一的LoaderManager例項

一個LoaderManager例項可以管理多個Loader例項

可以在FragmentActivity或Fragmeng中使用getSupportLoaderManager()獲取到LoaderManager例項

可以使用 initLoader() 或 restartLoader() 方法開始進行資料的載入

//第一個引數,為唯一的ID,可以為任意整數,為Loader的唯一標識,這裡為0
//第二個引數,為Bundle型別,可以向Loader傳遞構造引數,這裡為null,表示Loader構造方法不需要引數
//第三個引數,LoaderManager對Loader各事件的呼叫,參考下面講到的 LoaderManager.LoaderCallbacks
getSupportLoaderManager().initLoader(0, null, new LoaderCallbacks<D>());
複製程式碼

LoaderManager.LoaderCallbacks

LoaderManager對Loader各種情況的回撥介面,包含三個回撥方法

  • onCreateLoader(int,Bundle) 在這裡需要自己建立Loader物件,int 為Loader的唯一標識,Bundle為Loader的構造引數,可為空
...
new LoaderManager.LoaderCallbacks<String>() {
            @Override
            public Loader<String> onCreateLoader(int id, Bundle args) {
                return new MyLoader();
            }
            ...
}
複製程式碼
  • onLoadFinished(Loader,D) 當LoaderManager載入完資料時回撥此方法,在這裡用UI展示資料給使用者。D為泛型,根據實際情況設定為所需的資料型別。和initLoader()LoaderCallbacks引數中的的泛型為同一型別
new LoaderManager.LoaderCallbacks<String>() {
            ...
            @Override
            public void onLoadFinished(Loader<String> loader, String data) {
                    show(data);
            }
            ...
}
複製程式碼
  • onLoaderReset(Loader) 當之前建立的Loader例項被重置的時候會回撥此方法,此時需要對所持有的的資料進行清除處理
new LoaderManager.LoaderCallbacks<String>() {
            ...
            @Override
            public void onLoaderReset(Loader<String> loader) {
                    show(null);
            }
            ...
}
複製程式碼

Loader

從資料來源獲取資料,並對資料進行載入,為抽象類,需要自己實現子類

或使用官方已經實現的兩個子類

  • AsyncTaskLoader(繼承此類的時候會遇到一個坑,見下面的分析) 處理非同步獲取資料
  • CursorLoader 處理ContentProvider返回的資料

實現AsyncTaskLoader遇到的一個坑

首先自定義一個 MyAsyncTaskLoader,繼承AsyncTaskLoader,會發現需要實現引數為Context的構造方法和實現 loadInBackground() 抽象方法

//繼承AsyncTaskLoader類,裡面的泛型為返回的資料的型別,這裡設為String
public class MyAsyncTaskLoader extends AsyncTaskLoader<String>{

    public MyAsyncTaskLoader(Context context) {
        super(context);
    }

    @Override
     public String loadInBackground() {
        //模擬載入
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //返回獲取到的資料
        return new String("MyAsyncTaskLoader Test Result");
    }   
}
複製程式碼

建立FragmentActivity

public class BaseActivity extends AppCompatActivity implements LoaderManager.LoaderCallbacks{
   @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.base_activity_layout);
//        addFragment();
        log("onCreate");
        loadData();
    }

    protected void loadData(){
        Log.e(getClassName(),"call");
        getSupportLoaderManager().initLoader(0, null, this);
    }

    protected String getClassName(){
        return getClass().getSimpleName();
    }

    @Override
    public Loader onCreateLoader(int id, Bundle args) {
        Log.e(getClassName(),"onCreateLoader");
        return new MyAsyncTaskLoader(BaseActivity.this);
    }

    @Override
    public void onLoadFinished(Loader loader, Object data) {
        Log.e(getClassName(),"data:"+data);
    }

    @Override
    public void onLoaderReset(Loader loader) {

    }
}
複製程式碼

當執行的時候發現日誌值列印了onCreate,call,onCreateLoader,而預期中的 MyAsyncTaskLoader Test Result 並沒有輸出,也就是說 onLoadFinished 並未被回撥。除錯發現 MyAsyncTaskLoader 中的 loadInBackground() 方法也未執行。

這個是怎麼回事呢?

那麼只好檢視原始碼了,這裡所使用的都是 support-v4 的包。

檢視 AsyncTaskLoader 原始碼發現 loadInBackground() 方法的確為 abstract 型別,其被呼叫的地方是在一個叫做 LoadTask 的內部類中。

//可以把 ModernAsyncTask 看做 AsyncTask

final class LoadTask extends ModernAsyncTask<Void, Void, D> implements Runnable {
        ....
        @Override
        protected D doInBackground(Void... params) {
              ...
                D data = AsyncTaskLoader.this.onLoadInBackground();
              ...
        }
     .....
    }

 
複製程式碼

並且作為AsyncTaskLoader的一個全域性變數。

public abstract class AsyncTaskLoader<D> extends Loader<D> {
....
volatile LoadTask mTask;
....
}
複製程式碼

mTask 例項化和被執行的地方在 onForceLoad() 方法裡

    ...
    @Override
    protected void onForceLoad() {
        ...
        mTask = new LoadTask();
        ...
        executePendingTask();
    }
    ...
    void executePendingTask() {
        ...
            if (mUpdateThrottle > 0) {
                ...
                    mHandler.postAtTime(mTask, mLastLoadCompleteTime+mUpdateThrottle);
                    return;
                }
            }
           ...
            mTask.executeOnExecutor(mExecutor, (Void[]) null);
        }
    }
複製程式碼

mHandler.postAtTime 或者是 mTask.executeOnExecutor 這兩個地方就是執行 TaskLoader 的地方,並會呼叫到 doInBackground() 方法。

那麼到這裡我們可以猜測我們自定義的 MyAsyncLoader 的 loadInBackground() 未被執行,那麼 onForceLoad() 也應該未被執行。

沿著這條線索查詢看看這個 onForceLoad() 是在哪裡被呼叫的。發現其是在AsyncLoader 的父類 Loader 中的 forceLoad() 中被呼叫

public class Loader{
...
    public void forceLoad() {
        onForceLoad();
    }
...
}
複製程式碼

然後又看到註釋發現,此方法只能在 loader 開始的時候呼叫,還是找不到什麼頭緒。

Android知識點回顧之Loader

突然想到好像 CursorLoader 沒有這個問題,那麼看看它是不是有呼叫 forceLoad(),找了下,發現還果然有!是在 onStartLoading() 這個方法裡,並且只有這裡呼叫!

public class CursorLoader extends AsyncTaskLoader<Cursor> {
    ...
    @Override
    protected void onStartLoading() {
        if (mCursor != null) {
            deliverResult(mCursor);
        }
        if (takeContentChanged() || mCursor == null) {
            forceLoad();
        }
    }
    ...
}
複製程式碼

那麼我模仿下這個看看是不是真的能行,MyAsyncLoader 的程式碼修改如下:

//繼承AsyncTaskLoader類,裡面的泛型為返回的資料的型別,這裡設為String
public class MyAsyncTaskLoader extends AsyncTaskLoader<String>{

    public MyAsyncTaskLoader(Context context) {
        super(context);
    }
    
    //新增了這段程式碼
    @Override
    protected void onStartLoading() {
        forceLoad();
    }

    @Override
     public String loadInBackground() {
        //模擬載入
        try {
            Thread.sleep(3000);
        } catch (InterruptedException e) {
            e.printStackTrace();
        }
        //返回獲取到的資料
        return new String("MyAsyncTaskLoader Test Result");
    }   
}
複製程式碼

執行後發現真的能夠輸出了!看來問題是解決了。

最後一行為輸出的結果

問題是解決了,但是還是有一個疑問,這個 onStartLoading()是在哪裡被呼叫的呢?看來還是得看看原始碼。

從 getSupportLoaderManager().initLoader(0, null, this) 開始分析,發現最後是會呼叫到 onStartLoading()。

簡記如下,可自己對照著原始碼檢視:

  • LoaderManager的實現類為LoaderManagerImpl
  • init()方法裡面建立 LoaderInfo info
  • info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks)callback); 進入 createAndInstallLoader 方法
  • mCallbacks.onLoadFinished(loader, data); 進入 onLoadFinished 方法
  • createLoader(id, args, callback) 進入 createLoader 方法
  • installLoader(info); 進入 installLoader 方法
  • info.start(); 進入 start 方法
  • mLoader.startLoading(); 進入 startLoading 方法
  • onStartLoading();
  • 例子

    參考上面的AsyncLoader踩坑和官網例子(需要科學上網)

    -End-

相關文章