6. Loader

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,因此主要的線程相關的泄露不會發生。

注意:
這裏寫圖片描述

對於任何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。
關係圖:
這裏寫圖片描述

Loader

Loader Lifecycle
這裏寫圖片描述
這裏寫圖片描述

LoaderManager

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

LoaderManager中四個主要的組成方法:
這裏寫圖片描述
Activity/Fragment通過LoaderManager.LoaderCallbacks接口與LoaderManager交互,LoaderMananger.LoaderCallbacks接口必須在Activity/Fragment中實現。
Eg:
這裏寫圖片描述

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。

這裏寫圖片描述

LoaderCallbacks

相關的回調方法:
這裏寫圖片描述
回調方法做的工作:
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線性工作時的流程圖:
這裏寫圖片描述

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 = 0;

    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}, 0);
        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時可能需要實現:
這裏寫圖片描述
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(處理之中).
    這裏寫圖片描述

一個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(500);
        Random random = new Random();
        return random.nextInt(100);
    }
}

Activity:

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

    private final int CUSTOM_LOADER_ID = 0;

    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() 方法來檢查是否有內容要加載。
注意:
這裏寫圖片描述

Eg:
這裏寫圖片描述
Content Observation的生命週期:
從Loader started 到 reset,Content Observation 應該一直是活躍的。這樣Loader即使是stopped狀態也能繼續在後臺加載數據並且從Cache中提供新的數據。

### Delivering Cached Results
自定義Loader時,我們應該提供Cache,這樣可以更快的傳輸數據到Activity/Fragment。
我們可以直接調用 Loader.deliverResult() 方法傳輸數據到Activity/Fragment,也可以或者重寫 Loader.deliverResult() 方法把數據緩存起來。
這裏寫圖片描述
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 = 1;

    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 = 1;
    private final int SECOND_LOADER_ID = 2;

    @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;
        }
    }
}
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章