Android LoaderManager與CursorLoader用法


一.基本概念
 1.LoaderManager
 LoaderManager用來負責管理與Activity或者Fragment聯系起來的一個或多個Loaders對象.
 每個Activity或者Fragment都有唯一的一個LoaderManager實例(通過getLoaderManager()方法獲得),用來啟動,停止,保持,重啟,關閉它的Loaders,這些功能可通過調用initLoader()/restartLoader()/destroyLoader()方法來實現.
 LoaderManager並不知道數據如何裝載以及何時需要裝載.相反,它只需要控制它的Loaders們開始,停止,重置他們的Load行為,在配置變換或數據變化時保持loaders們的狀態,並使用接口來返回load的結果.

 2.Loader
 Loades負責在一個單獨線程中執行查詢,監控數據源改變,當探測到改變時將查詢到的結果集發送到注冊的監聽器上.Loader是一個強大的工具,具有如下特點
 (1)它封裝了實際的數據載入.
 Activity或Fragment不再需要知道如何載入數據.它們將該任務委托給了Loader,Loader在后台執行查詢要求並且將結果返回給Activity或Fragment.
 (2)客戶端不需要知道查詢如何執行.Activity或Fragment不需要擔心查詢如何在獨立的線程中執行,Loder會自動執行這些查詢操作.
 這種方式不僅減少了代碼復雜度同事也消除了線程相關bug的潛在可能.
 (3)它是一種安全的事件驅動方式.
 Loader檢測底層數據,當檢測到改變時,自動執行並載入最新數據.
 這使得使用Loader變得容易,客戶端可以相信Loader將會自己自動更新它的數據.
 Activity或Fragment所需要做的就是初始化Loader,並且對任何反饋回來的數據進行響應.除此之外,所有其他的事情都由Loader來解決.

二.異步Loader的實現原理
 (1)執行異步載入的任務.為了確保在一個獨立線程中執行載入操作,Loader的子類必須繼承AsyncTaskLoader<D>而不是Loader<D>類.
 AsyncTaskLoader<D>是一個抽象Loader,它提供了一個AsyncTask來做它的執行操作.
 當定義子類時,通過實現抽象方法loadInBackground方法來實現異步task.該方法將在一個工作線程中執行數據加載操作.
 (2)在一個注冊監聽器中接收載入完成返回的結果.
 對於每個Loader來說,LoaderManager注冊一個OnLoadCompleteListener<D>,該對象將通過調用onLoadFinished(Loader<D> loader, D result)方法使Loader將結果傳送給客戶端.
 Loader通過調用Loader#deliverResult(D result),將結果傳遞給已注冊的監聽器.

三.Loader三種不同狀態.
 已啟動: 處於已啟動狀態的Loader會執行載入操作,並在任何時間將結果傳遞到監聽器中.已啟動的Loader將會監聽數據改變,當檢測到改變時執行新的載入.
 一旦啟動,Loader將一直處在已啟動狀態,一直到轉換到已停止和重置,這是唯一一種onLoadFinished永遠都會調用的狀態。
 已停止: 處於已停止狀態的Loader將會繼續監聽數據改變,但是不會將結果返回給客戶端.在已停止狀態,Loader可能被啟動或者重啟.
 重置: 當Loader處於重置狀態時,將不會執行新的載入操作,也不會發送新的結果,也不會檢測數據變化.
 當一個Loader進入重置狀態,它必須解除對應的數據引用,方便垃圾回收(客戶端也必須確定,在Loader無效之后,移除了所有對該數據的引用).
 通常,重置Loader不會兩次調用.然而,在某些情況下他們可能會啟動,所以如果必要的話,它們必須能夠適時重置.

四.接收Loader數據改變的通知.
 必須有一個觀察者接受數據源改變的通知.
 Loader必須實現這些Observer其中之一(ContentObserver,BroadcastReceiver等),來檢測底層數據源的改變.
 當檢測到數據改變,觀察者必須調用Loader#onContentChanged().在該方法中執行兩種不同操作:
 (1)如果Loader已經處於啟動狀態,就會執行一個新的載入操作; 
 (2)設置一個flag標識數據源有改變,這樣當Loader再次啟動時,就知道應該重新載入數據了.
 
五.CursorLoader實現LoaderManager.LoaderCallbacks接口方法.接口聲明及使用如下:
 public interface LoaderCallbacks<D> { 
  public Loader<D> onCreateLoader(int id, Bundle args); 
  public void onLoadFinished(Loader<D> loader, D data); 
  public void onLoaderReset(Loader<D> loader); 
 }
 
 onCreateLoader方法將在創建Loader時候調用,此時需要提供查詢的配置,如監聽一個URI.
 這個方法會在loader初始化的時調用,即調用下面的代碼時調用:
  getLoaderManager().initLoader(id, bundle, loaderCallbacks);
  initLoader函數原型為:
  <D> Loader<D> android.app.LoaderManager.initLoader(int id, Bundle bundle, LoaderCallbacks<D> loaderCallbacks);
  第1個參數loader的ID,可自定義一個常量值,便於實現多個Loader;
  第2個參數一般置null;
  第3個參數是實現了LoaderManager.LoaderCallbacks實現類對象.
 onLoadFinished方法,在Loader完成任務后調用,一般在此讀取結果.
 onLoaderReset方法是在配置發生變化時調用,一般調用下面的代碼后調用:
  getLoaderManager().restartLoader(id, bundle, loaderCallbacks);
  restartLoader方法參數同initLoader,重新初始化loader之后,需要用來釋放對前面loader查詢到的結果引用.
  
六.一個具體監聽通話記錄的示例
 在Activity中onCreate調用
  CallLogsLoaderListener callLogsCallback = new CallLogsLoaderListener(this);
  getLoaderManager().initLoader(0, null, callLogsCallback);
 
 在onDestroy調用
  getLoaderManager().destroyLoader(0);
 
 配置權限
  <uses-permission android:name="android.permission.WRITE_CALL_LOG" />
  <uses-permission android:name="android.permission.READ_CALL_LOG" />
 
 具體的類實現

import android.app.LoaderManager;
import android.content.Context;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.database.CursorWrapper;
import android.os.Bundle;
import android.provider.CallLog;
import android.util.Log;

public class CallLogsLoaderListener implements
        LoaderManager.LoaderCallbacks<Cursor> {

    private Context mContext;

    public CallLogsLoaderListener(Context context) {
        mContext = context;
    }

    @Override
    public Loader<Cursor> onCreateLoader(int id, Bundle args) {
        CursorLoader loader = new CursorLoader(mContext,
                CallLog.Calls.CONTENT_URI, null, null, null, null) {
            @Override
            public Cursor loadInBackground() {
                Cursor cursor = super.loadInBackground();
                if (cursor == null)
                    return null;
                CallLogsCursor callLogsCursor = new CallLogsCursor(cursor);
                return callLogsCursor;
            }
        };
        return loader;
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
    }

    public class CallLogsCursor extends CursorWrapper {

        public CallLogsCursor(Cursor cursor) {
            super(cursor);

            int nameIndex = cursor.getColumnIndex(CallLog.Calls.CACHED_NAME);
            int numberIndex = cursor.getColumnIndex(CallLog.Calls.NUMBER);
            int typeIndex = cursor.getColumnIndex(CallLog.Calls.TYPE);
            int dateIndex = cursor.getColumnIndex(CallLog.Calls.DATE);
            int durationIndex = cursor.getColumnIndex(CallLog.Calls.DURATION);

            // 從游標的最后索引往前查詢,因為最新的通話記錄在最后
            for (cursor.moveToLast(); !cursor.isBeforeFirst(); cursor
                    .moveToPrevious()) {
                // 姓名
                String name = cursor.getString(nameIndex);
                // 號碼
                String number = cursor.getString(numberIndex);
                // 類型
                int type = cursor.getInt(typeIndex);
                // 日期
                long date = cursor.getLong(dateIndex);
                // 通話時長
                int duration = cursor.getInt(durationIndex);

                Log.d("debug", "name=" + name + ", number=" + number
                        + ", type=" + type + ", date=" + date + ", duration="
                        + duration);
            }
        }
    }
}

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM