天天看點

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

1. Loader Framework

Loader framework在與content provider或者其他資料源進行操作時,提供了一種健壯的異步操作。

Loader Framework有那些特性:

  • Asynchronous data management

    Loader是在背景線程與資料源進行操作的,當資料源有新的資料時會在App中觸發一個Callback。

  • Lifecycle management

    當與Loader關聯的Activity或Fragment stop,那麼loader也會stop。是以,當發生configuration changes(如橫豎屏)時,在背景正在運作的

    Loader會繼續運作,而不是stop。

  • Cached data

    如果Loader異步處理的資料結構不能deliver,那麼Loader就好Cached這個資料結果,當一個接收資料的接收者準備好了,Loader就會把資料結果deliver到這個接收者。例如:當發生configuration change,Activity被重建的場景。

  • Leak protection

    如果一個Activity正在經曆一個configuration change,Loader framework會確定Context對象不會丢失而導緻洩露。Loader framework操作僅僅是Application context,是以主要的線程相關的洩露不會發生。

注意:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

對于任何Loader類型,我們必須定義三個Callback:

one that creates a new loader;one that runs whenever the loader delivers new data;one that runs when the loader is reset - i.e., the loader stops delivering data。

All callbacks - most importantly, the delivery of data - are reported on the UI thread。

Loader Framework就是在android.app包中的API,包含的類有LoaderManager,Loader,AsyncTaskLoader 和 CursorLoader。

關系圖:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

Loader

Loader Lifecycle

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders
6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

LoaderManager

LoaderManager類一個抽象類,它會管理Activity或Fragment使用的所有Loader。LoaderManager在Activity/Fragment與Loader之間扮演了一個中間人的角色。在Activity/Fragment中調用getLoaderManager()方法來擷取LoaderManager執行個體。

LoaderManager中四個主要的組成方法:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

Activity/Fragment通過LoaderManager.LoaderCallbacks接口與LoaderManager互動,LoaderMananger.LoaderCallbacks接口必須在Activity/Fragment中實作。

Eg:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

initLoader vs restartLoader

LoaderManager可以通過initLoader()或restartLoader()來建立一個Loader。

  • initLoader()

    如果Loader的辨別符(id)比對的話,initLoader()會重用可通路到的Loader。如果沒有Loader比對initLoader()中的ID,onCreateLoader()會請求一個新的Loader,之後data load被初始化,在onLoadFinished()中擷取資料結果;如果initLoader中的ID已經存在,那麼就在onLoadFinished()中直接接收最新的資料結果。

    Activity/Fragment的configuration change時使用initLoader()方法,這樣仍然能接收到最新的資料結果。

  • restartLoader()

    此方法不會重用Loader。如果有一個ID比對的Loader,restartLoader()會destroy這個Loader以及Loader的資料,然後再調用onCreateLoader()建立一個新的Loader。

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

LoaderCallbacks

相關的回調方法:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

回調方法做的工作:

onCreateLoader:Loader initialization

此回調方法初始化一個Loader,LoaderManager.initLoader()會觸發調用此回調方法,initLoader()會初始化一個帶唯一辨別符的Loader。如果initLoader()方法指定辨別符的Loader不存在,那麼onCreateLoader()回調方法會建立一個新的Loader,然後把建立後的Loader傳回給LoaderManager,然後LoaderManager會管理Loader的lifecycle和data loading。如果有同一辨別符的Loader,那麼就不會再建立新的Loader,而是使用已經存在的Loader通過onLoadFinished()回調方法傳輸最新的資料。

onLoadFinished():Data loading

當資料源擷取完或者更新時,會調用 onLoadFinisher() 回調方法加載資料;我們也可以在Activity/Fragment中通過調用 Loader.forceLoad() 方法強制擷取新的資料。

我們可以通過調用 Loader.cancelLoad() 方法取消資料加載。如果此方法在資料加載開始之前調用的話,那麼資料加載的請求會被取消;如果資料加載已經開始,那麼這個資料解決會被丢棄,而且不會傳輸到Activity/Fragment。

onLoaderReset:Loader reset

當Activity/Fragment被銷毀或者調用 LoaderManager.destroyLoader(id) 時,Loader會被銷毀。Activity/Fragment會通過onLoaderReset()回調方法接收到Loader被銷毀,在onLoaderReset()回調方法中可以做些資源釋放的工作。

LoaderCallbacks線性工作時的流程圖:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

AsyncTaskLoader

AsyncTaskLoader會盡量保持同時進行的Task數(即運作的Thread)最少。例如,Activity/Fragment調用 forceLoad() 方法強制的連續的加載資料,但是不是每一次調用就會傳回結果。這個原因是AsyncTaskLoader初始化一個新的加載的話,會把之前的加載取消掉。這個意思是在加載完成之前反複多次的調用 forceLoad() 将會延遲傳輸資料結果直到最後調用加載完成。(In practice, this means that calling forceLoad() repeatedly before previous loads are finished will postpone the delivery of the result until the last invoked load is done.)

如果内容連續多次的改變,Loader的onLoadFinished()方法會在UI Thread頻繁調用并且UI Thread多次重新整理重繪UI控件,那麼我們可以調用 setUpdateThrottle(long delayMs) 方法設定AsyncTaskLoader連續加載資料的間隔時長。

2. CursorLoader

CursorLoader隻能使用Cursor對象從Content Provider傳輸資料,而不是從SQLite資料庫。

讀取通信錄示例:

public class ContactActivity extends ListActivity implements LoaderManager.LoaderCallbacks<Cursor> {

    private static final int CONTACT_LOADER_ID = ;

    private static String[] CONTACT_SUMMARY_PROJECTION = new String[] {ContactsContract.Contacts._ID, ContactsContract.Contacts.DISPLAY_NAME};

    private SimpleCursorAdapter cursorAdapter = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        initAdapter();
        getLoaderManager().initLoader(CONTACT_LOADER_ID, null, this);
    }

    private void initAdapter() {
        cursorAdapter = new SimpleCursorAdapter(this, android.R.layout.simple_list_item_1, null, new String[] {ContactsContract.Contacts.DISPLAY_NAME}, new int[] {android.R.id.text1}, );
        setListAdapter(cursorAdapter);
    }

    @Override
    public Loader<Cursor> onCreateLoader(int i, Bundle bundle) {
        return new CursorLoader(this, ContactsContract.Contacts.CONTENT_URI, CONTACT_SUMMARY_PROJECTION, null, null, ContactsContract.Contacts.DISPLAY_NAME + " ASC");
    }

    @Override
    public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
        cursorAdapter.swapCursor(cursor);
    }

    @Override
    public void onLoaderReset(Loader<Cursor> loader) {
        cursorAdapter.swapCursor(null);
    }
}
           

3. Custom Loaders

要定義一個完整的Loader需要包含下面的feature:

  • Loader lifecycle
  • Background loading
  • Content management
  • Deliver cached result

Loader lifecycle

Loader包含一系列的狀态轉變的方法,自定義Loader時可能需要實作:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

Loader State(狀态):

  • Reset

    The initial and final state of a loader, where it has released any cached data.

  • Started

    Starts an asynchronous data load and delivers the result through a cabllback invocation of LoaderCallback.onLoadFinished.

  • Stop

    The loader stops delivering data to the client. It may still load data in the background on content change, but the data is cached in the loader so that the latest data can be retrieved easily without initiating a new data load.

  • Abandoned

    Intermediate(中間的) state before reset, where data is stored until a new loader is connected to the data source. This is rarely used; the LoaderManager abandons loaders on restart so that the data is available while the restart is underway(處理之中).

    6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

一個Loader的lifecycle是被LoaderManager控制的,正常情況下Activity/Fragment不應該直接調用Loader的方法來修改狀态。

我們可以調用 forceLoad() 方法來顯示地強制加載資料。forceLoad() 和 startLoading() 之間的不同之處是 forceLoad()隻是強制加載新的資料,但是不會修改Loader的狀态。

Forceload僅僅應該在started狀态時調用,否則,資料結果就不會被傳到Activity/Fragment。

Background Loading

Loader 應該在Background Thread異步地加載資料。

Eg:

Loader:

public class CustomLoader extends AsyncTaskLoader<Integer> {

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

    @Override
    protected void onStartLoading() {
        super.onStartLoading();
        // TODO 待添加緩存資料
        forceLoad();
    }

    // 在此方法中異步加載資料
    @Override
    public Integer loadInBackground() {
        return loadData();
    }

    // 模拟加載資料
    private int loadData() {
        SystemClock.sleep();
        Random random = new Random();
        return random.nextInt();
    }
}
           

Activity:

public class CustomLoaderActivity extends Activity implements LoaderManager.LoaderCallbacks<Integer> {

    private final int CUSTOM_LOADER_ID = ;

    private TextView content = null;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_custom_loader);

        content = (TextView) findViewById(R.id.custom_loader_content);
        getLoaderManager().initLoader(CUSTOM_LOADER_ID, null, this);
    }

    @Override
    public Loader onCreateLoader(int i, Bundle bundle) {
        return new CustomLoader(this);
    }

    @Override
    public void onLoadFinished(Loader loader, Integer data) {
        content.setText(Integer.toString(data));
    }

    @Override
    public void onLoaderReset(Loader loader) {
        // TODO 回收
    }
}
           

Content Management

當基本資料改變時,Loader應該自動地建立一個新的背景資料加載。是以,這個基本資料必須設定一個 observable,例如 CursorLoader利用ContentObserver來獲得content provider中的資料更新。

Observer機制典型:

  • Observable and Observer

    An in-memory data model of Java objects can be monitored by implementing the model as an Observable that reports changes to an Observer class. Both classes reside in the java.uitl package.

  • Broadcasted intent to a BroadcastReceiver

    A content-independent content notifier, this can be userd locally within an application or across process boundaries.

  • FileObserver

    Observers file system changes with an android.os.FileObserver that monitors a path in the file system and sends events when change occur. The events that are reported can be configured with an event filter, which can limit the observations, such as addition, deletion, and move.

當Observer收到一個更新的通知,它會上報到Loader來異步地加載新的資料,這個應該通過 forceLoad()或者onContentChanged() 方法來做。

當Loader started後,自定義的Loader調用 takeContentChanged() 方法來檢查是否有内容要加載。

注意:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

Eg:

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

Content Observation的生命周期:

從Loader started 到 reset,Content Observation 應該一直是活躍的。這樣Loader即使是stopped狀态也能繼續在背景加載資料并且從Cache中提供新的資料。

### Delivering Cached Results

自定義Loader時,我們應該提供Cache,這樣可以更快的傳輸資料到Activity/Fragment。

我們可以直接調用 Loader.deliverResult() 方法傳輸資料到Activity/Fragment,也可以或者重寫 Loader.deliverResult() 方法把資料緩存起來。

6. Loader1. Loader Framework2. CursorLoader3. Custom Loaders

Eg: Custom File Loader

FileLoader

public class FileLoader extends AsyncTaskLoader<List<String>> {

    // Cache the list of file names
    private List<String> fileNames = null;
    private SdCardObserver sdCardObserver = null;

    private class SdCardObserver extends FileObserver {

        public SdCardObserver(String path) {
            super(path, FileObserver.CREATE|FileObserver.DELETE);
        }

        @Override
        public void onEvent(int i, String s) {
            // This call will force a new asynchronous data load
            // if the loader is started otherwise it will keep
            // a reference that the data has changed for future loads.
            onContentChanged();
        }
    }

    public FileLoader(Context context) {
        super(context);
        String path = getContext().getFilesDir().getPath();
        Log.i(MyApplication.TAG, "FileLoader # path: " + path);
        sdCardObserver = new SdCardObserver(path);
    }

    @Override
    protected void onStartLoading() {
        super.onStartLoading();

        // Start observing the content
        sdCardObserver.startWatching();

        // 如果緩存資料不為空,直接傳輸緩存資料
        if (fileNames != null) {
            deliverResult(fileNames);
        }

        // 如果緩存資料為空或者有内容改變的話,就強制加載資料
        if (fileNames == null || takeContentChanged()) {
            forceLoad();
        }
    }

    @Override
    public List<String> loadInBackground() {
        File directory = getContext().getFilesDir();
        Log.i(MyApplication.TAG, "FileLoader # loadInBackground # directory: " + directory);
        return Arrays.asList(directory.list());
    }

    // 重寫deliverResult()方法把擷取到的資料緩存起來
    @Override
    public void deliverResult(List<String> data) {
        // 如果Loader是reset狀态,那麼直接傳回
        if (isReset()) {
            return;
        }

        Log.i(MyApplication.TAG, "FileLoader # deliverResult # data: " + data);

        // Cache the data
        fileNames = data;

        // Only deliver result if the loader is started
        if (isStarted()) {
            super.deliverResult(data);
        }
    }

    @Override
    protected void onStopLoading() {
        super.onStopLoading();
        // Try to cancle an ongoing load, because the result will not be delivered anyway.
        cancelLoad();
    }

    @Override
    protected void onReset() {
        super.onReset();
        sdCardObserver.stopWatching();
        clearResource();

    }

    private void clearResource() {
        fileNames = null;
    }
}
           

Activity:

public class FileListActivity extends ListActivity implements LoaderManager.LoaderCallbacks<List<String>> {

    private final int FILE_LOADER_ID = ;

    private ArrayAdapter<String> fileAdapter = null;
    private List<String> fileNames = new ArrayList<String>();

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getLoaderManager().initLoader(FILE_LOADER_ID, null, this);

        fileAdapter = new ArrayAdapter<String>(this, android.R.layout.simple_list_item_1, android.R.id.text1, fileNames);
        fileAdapter.setNotifyOnChange(true);
        setListAdapter(fileAdapter);
    }

    @Override
    public Loader<List<String>> onCreateLoader(int i, Bundle bundle) {
        return new FileLoader(this);
    }

    @Override
    public void onLoadFinished(Loader<List<String>> loader, List<String> data) {
        Log.i(MyApplication.TAG, "FileListActivity # onLoadFinished # data: " + data);

        fileAdapter.clear();
        fileAdapter.addAll(data);
        fileAdapter.notifyDataSetChanged();
    }

    @Override
    public void onLoaderReset(Loader<List<String>> loader) {
        fileNames = null;
        fileAdapter.clear();
    }
}
           

Handling Multiple Loaders

每個Activity/Fragment中隻能有一個LoaderManager,而一個LoaderManager可以有多個Loader。我們可通過Loader的Id來區分并管理。

Eg:

public class MulLoaderSkeletonActivity extends Activity implements LoaderManager.LoaderCallbacks {
    private final int FIREST_LOADER_ID = ;
    private final int SECOND_LOADER_ID = ;

    @Override
    protected void onCreate(@Nullable Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);

        getLoaderManager().initLoader(FIREST_LOADER_ID, null, this);
        getLoaderManager().initLoader(SECOND_LOADER_ID, null, this);
    }

    @Override
    public Loader onCreateLoader(int id, Bundle bundle) {
        switch (id) {
            case FIREST_LOADER_ID:
                return new FirstLoader(this);

            case SECOND_LOADER_ID:
                return new SecondLoader(this);
        }

        return null;
    }

    @Override
    public void onLoadFinished(Loader loader, Object o) {
        switch (loader.getId()) {
            case FIREST_LOADER_ID:
                // TODO
                break;

            case SECOND_LOADER_ID:
                // TODO
                break;
        }
    }

    @Override
    public void onLoaderReset(Loader loader) {
        switch (loader.getId()) {
            case FIREST_LOADER_ID:
                // TODO
                break;

            case SECOND_LOADER_ID:
                // TODO
                break;
        }
    }
}
           

繼續閱讀