前言
在 Android 中白翻,任何耗時(shí)的操作都不能放在 UI 線程中,所以耗時(shí)的操作都需要使用異步加載來(lái)實(shí)現(xiàn)。其實(shí)滤馍,加載耗時(shí)數(shù)據(jù)的常用方式其實(shí)也挺多的岛琼,就讓我們來(lái)看一下
1、Thread + Handler
2巢株、AsyncTask
3槐瑞、Loader
前面兩種異步加載方式,相信大家是比較熟悉的阁苞,但是第三種方式困檩,可能有些人是沒(méi)怎么接觸過(guò)的,其實(shí)在 ContentProvider 中也可能存在耗時(shí)的操作那槽,這時(shí)候也應(yīng)該使用異步操作悼沿,而 Android 3.0 之后最推薦的異步操作就是 Loader,使用 Loader 機(jī)制能讓我們高效地加載數(shù)據(jù)
一骚灸、Loader 簡(jiǎn)介
Android 3.0 中引入了 Loader 機(jī)制糟趾,讓開(kāi)發(fā)者能輕松在 Activity 和 Fragment 中異步加載數(shù)據(jù),Loader 機(jī)制具有以下特征:
可用于每個(gè) Activity 或 Fragment
支持異步加載數(shù)據(jù)
監(jiān)控?cái)?shù)據(jù)源并在內(nèi)容變化時(shí)傳遞新結(jié)果
在某一配置更改后重建加載器時(shí)甚牲,會(huì)自動(dòng)重新連接上一個(gè)加載器的游標(biāo)义郑。因此,它們無(wú)需重新查詢其數(shù)據(jù)丈钙。
我們用一張圖來(lái)直觀地認(rèn)識(shí)下 Loader 機(jī)制和另外兩種做法之間的區(qū)別
從圖片中可以看出 Loader 機(jī)制的寫(xiě)法是相當(dāng)簡(jiǎn)潔的非驮,可以讓我們進(jìn)行快速的開(kāi)發(fā),而且效率方面也是非常高的雏赦。
二院尔、相關(guān)類和 API 介紹
本節(jié)內(nèi)容大部分來(lái)自官方文檔,詳細(xì)可以 點(diǎn)擊這里
在介紹 Loader 的使用之前喉誊,我們先來(lái)看一下與 Loader 機(jī)制相關(guān)的一些類和接口
類 / 接口 | 說(shuō)明 |
---|---|
LoaderManager | 一種與 Activity 或 Fragment 相關(guān)聯(lián)的抽象類邀摆,用于管理一個(gè)或多個(gè) Loader 實(shí)例。這有助于應(yīng)用管理與 Activity 或 Fragment 生命周期相關(guān)的伍茄、運(yùn)行時(shí)間較長(zhǎng)的操作栋盹。它常見(jiàn)的用法是 與 CursorLoader 一起使用,不過(guò)應(yīng)用也可以自由寫(xiě)入自己的加載器敷矫,用于加載其他類型的數(shù)據(jù) |
LoaderManager.LoaderCallbacks | 回調(diào)接口例获,用于客戶端與 LoaderManager 進(jìn)行交互,例如曹仗,可以使用 onCreateLoader() 回調(diào)方法創(chuàng)建新的加載器 |
Loader | 一種執(zhí)行異步加載數(shù)據(jù)的抽象類榨汤。這是加載器的基類。我們通常會(huì)使用 CursorLoader怎茫,但也可以實(shí)現(xiàn)自己的子類收壕。當(dāng)加載器處于活動(dòng)狀態(tài)時(shí)妓灌,應(yīng)監(jiān)控其數(shù)據(jù)源并在內(nèi)容變化時(shí)傳遞新結(jié)果 |
AsyncTaskLoader | 提供 AsyncTask 來(lái)執(zhí)行工作的抽象加載器 |
CursorLoader | AsyncTaskLoader 的子類,它將查詢 ContentResolver 并返回一個(gè) Cursor蜜宪。使用此加載器是從 ContentProvider 異步加載數(shù)據(jù)的最佳方式虫埂,而不用通過(guò) Activity 或 Fragment 的 API 來(lái)執(zhí)行托管查詢 |
以上便是 Loader 機(jī)制相關(guān)的類,但并不是我們創(chuàng)建的每個(gè)加載器都要用到上述所有的類和接口圃验。但是掉伏,為了初始化加載器以及實(shí)現(xiàn)一個(gè) Loader 類(如 CursorLoader),我們需要引用 LoaderManager澳窑。
2.1 加載器的使用
使用加載器的應(yīng)用通常包括:
Activity 或 Fragment
LoaderManager 的實(shí)例
一個(gè) CursorLoader斧散,用于加載由 ContentProvider 支持的數(shù)據(jù)。當(dāng)然我們也可以實(shí)現(xiàn)自己的 Loader 或 AsyncTaskLoader 子類摊聋,從其他的數(shù)據(jù)源中加載數(shù)據(jù)
一個(gè) LoaderManager.LoaderCallbacks 實(shí)現(xiàn)颅湘,可以使用它來(lái)創(chuàng)建新的加載器,并管理對(duì)現(xiàn)有加載器的引用
顯示加載器數(shù)據(jù)的方法栗精,如 SimpleCursorAdapter
使用 CursorLoader 時(shí)的數(shù)據(jù)源闯参,如 ContentProvider
啟動(dòng)加載器
LoaderManager 可在 Activity 或 Fragment 內(nèi)管理一個(gè)或多個(gè) Loader 實(shí)例,每個(gè) Activity 或 Fragment 中只有一個(gè) LoaderManager悲立。通過(guò)我們會(huì)在 Activity 的 onCreate() 方法或 Fragment 中的 onActivityCreate() 方法內(nèi)初始化 Loader
getSupportLoaderManager().initLoader(0鹿寨,null,this);
initLoader() 方法采用以下參數(shù):
用于標(biāo)識(shí)加載器的唯一 ID薪夕,在代碼示例中脚草,ID 為 0
在構(gòu)建時(shí)提供給加載器的可選參數(shù)(在代碼示例中,為 null)
LoaderManager.LoaderCallbacks 實(shí)現(xiàn)原献,LoaderManager 將調(diào)用該實(shí)現(xiàn)來(lái)報(bào)告加載器事件馏慨。在此示例中,本地類實(shí)現(xiàn)了 LoaderManager.LoaderCallbacks 接口姑隅,因此直接傳遞它對(duì)自身的引用 this
initLoader() 調(diào)用確保加載器已經(jīng)初始化且處于活動(dòng)狀態(tài)写隶,這可能會(huì)出現(xiàn)兩種結(jié)果:
如果指定 ID 的加載器已經(jīng)存在,那么將重復(fù)使用上次創(chuàng)建的加載器
如果指定 ID 的加載器不存在讲仰,則 initLoader() 將觸發(fā) LoaderManager.LoaderCallbacks 中的 onCreateLoader() 方法慕趴,在這個(gè)方法中,我們可以實(shí)現(xiàn)代碼以實(shí)例化并返回新的加載器
無(wú)論何種情況鄙陡,給定的 LoaderManager.LoaderCallbacks 實(shí)現(xiàn)均與加載器相關(guān)聯(lián)冕房,且在加載器狀態(tài)變化時(shí)調(diào)用。如果在調(diào)用時(shí)趁矾,調(diào)用程序處于啟動(dòng)狀態(tài)耙册,且請(qǐng)求的加載器已存在并生成了數(shù)據(jù),則系統(tǒng)將立即調(diào)用 onLoadFinish()
有一點(diǎn)要注意的是毫捣,initLoader() 方法將返回已創(chuàng)建的 Loader详拙,但我們不用捕獲它的引用帝际。LoaderManager 將自動(dòng)管理加載器的生命周期。LoaderManager 將根據(jù)需要啟動(dòng)和停止加載溪厘,并維護(hù)加載器的狀態(tài)及其相關(guān)內(nèi)容胡本。這意味著我們將很少與加載器直接進(jìn)行交互牌柄。當(dāng)特定事件發(fā)生時(shí)畸悬,我們通常會(huì)使用 LoaderManager.LoaderCallbacks 方法干預(yù)加載進(jìn)程。
重啟加載器
當(dāng)我們使用 initLoader()珊佣,它將使用含有指定 ID 的現(xiàn)有加載器(如有)蹋宦。如果沒(méi)有它會(huì)創(chuàng)建一個(gè)。但有時(shí)咒锻,我們想舍棄這些舊數(shù)據(jù)并重新開(kāi)始冷冗。
要舍棄舊數(shù)據(jù),我們需要使用 restartLoader()惑艇,例如蒿辙,當(dāng)用戶的查詢更改時(shí),SearchView.OnQueryTextListener 實(shí)現(xiàn)將重啟加載器滨巴。加載器需要重啟思灌,以便它能夠使用修正后的搜索過(guò)濾器執(zhí)行新查詢:
public boolean onQueryTextChanged(String newText){
mCurFilter = !TextUtils.isEmpty(newText) ? newText : null;
getSupportLoaderManager().restartLoader(0, null, this);
return true;
}
使用 LoaderManager 回調(diào)
LoaderManager.LoaderCallbacks 是一個(gè)支持客戶端與 LoaderManager 交互的回調(diào)接口
加載器(特別是 CursorLoader)在停止運(yùn)行后,仍需保留其數(shù)據(jù)恭取,這樣既可保留 Activity 或 Fragment 的 onStop() 和 onStart() 方法中的數(shù)據(jù)泰偿。當(dāng)用戶返回應(yīng)用時(shí),無(wú)需等待它重新加載這些數(shù)據(jù)蜈垮。
LoaderManager.LoaderCallbacks 接口包括以下方法
onCreateLoader():針對(duì)指定的 ID 進(jìn)行實(shí)例化并返回新的 Loader
onLoadFinished():將在先前創(chuàng)建的加載器完成加載時(shí)調(diào)用
onLoaderReset():將在先前創(chuàng)建的加載器重置且其數(shù)據(jù)因此不可用時(shí)調(diào)用
onCreateLoader()
當(dāng)我們嘗試訪問(wèn)加載器時(shí)(例如耗跛,通過(guò) initLoader()),該方法將檢查是否已存在由該 ID 指定的加載器攒发。如果沒(méi)有调塌,它將觸發(fā) LoaderManager.LoaderCallbacks 中的 onCreateLoader() 方法。在此方法中惠猿,我們可以創(chuàng)建加載器烟阐,通過(guò)這個(gè)方法將返回 CursorLoader,但我們也可以實(shí)現(xiàn)自己的 Loader 子類紊扬。
在下面的示例中蜒茄,onCreateLoader() 方法創(chuàng)建了 CursorLoader。我們必須使用它的構(gòu)造方法來(lái)構(gòu)建 CursorLoader餐屎。構(gòu)造方法 需要對(duì) ContentProvider 執(zhí)行查詢時(shí)所需的一系列完整信息
public CursorLoader(Context context, Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
super(context);
mObserver = new ForceLoadContentObserver();
mUri = uri;
mProjection = projection;
mSelection = selection;
mSelectionArgs = selectionArgs;
mSortOrder = sortOrder;
}
參數(shù)名 | 作用 |
---|---|
uri | 用于檢索內(nèi)容的 URI |
projection | 返回的列的列表檀葛。傳遞 null 時(shí),將返回所有列腹缩,這樣的話效率會(huì)很低 |
selection | 一種用于聲明返回那些行的過(guò)濾器屿聋,采用 SQL WHERE 子句格式空扎。傳遞 null 時(shí),將為指定的 URI 返回所有行 |
selectionArgs | 我們可以在 selection 中包含 ?润讥,它將按照在 selection 中顯示的順序替換為 selectionArgs 中的值 |
sortOrder | 行的排序依據(jù)转锈,采用 SQL ORDER BY 子句格式。傳遞 null 時(shí)楚殿,將使用默認(rèn)排序順序(可能并未排序) |
示例代碼:
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
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");
}
onloadFinished
當(dāng)先前創(chuàng)建的加載器完成加載時(shí)撮慨,將會(huì)調(diào)用此方法。該方法必須在為此加載器提供的最后一個(gè)數(shù)據(jù)釋放之前調(diào)用脆粥。此時(shí)砌溺,我們應(yīng)該移除所有使用的舊數(shù)據(jù)(因?yàn)樗鼈兒芸炀蜁?huì)被釋放),但不要自行釋放這些數(shù)據(jù)变隔,因?yàn)檫@些數(shù)據(jù)歸加載器所有规伐,加載器會(huì)處理它們耕餐。
當(dāng)加載器發(fā)現(xiàn)應(yīng)用不再使用這些數(shù)據(jù)時(shí)戴卜,將會(huì)釋放它們篷帅。例如桥帆,如果數(shù)據(jù)是來(lái)自 CursorLoader 的一個(gè)游標(biāo)猫态,則我們不應(yīng)手動(dòng)對(duì)其調(diào)用 close()脸哀。如果游標(biāo)放置在 CursorAdapter 中腊尚,則應(yīng)使用 swapCursor() 方法鹿蜀,使舊 Cursor 不會(huì)關(guān)閉
SimpleCursorAdapter mAdapter;
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
mAdapter.swapCursor(data);
}
onLoadReset
該方法將在 先前創(chuàng)建的加載器重置 且 數(shù)據(jù)因此不可用 時(shí)調(diào)用夏哭,通過(guò)此回調(diào)检柬,我們可以了解何時(shí)將釋放數(shù)據(jù),因此能夠及時(shí)移除其引用竖配。
此實(shí)現(xiàn)調(diào)用值為 null 的 swapCursor()
SimpleCursorAdapter mAdapter;
public void onLoaderReset(Loader<Cursor> loader) {
mAdapter.swapCursor(null);
}
三何址、Loader 機(jī)制的使用場(chǎng)景和使用方式
Loader 機(jī)制一般用于數(shù)據(jù)加載,特別是用于加載 ContentProvider 中的內(nèi)容进胯,比起 Handler + Thread 或者 AsyncTask 的實(shí)現(xiàn)方式用爪,Loader 機(jī)制能讓代碼更加的簡(jiǎn)潔易懂,而且是 Android 3.0 之后最推薦的加載方式胁镐。
Loader 機(jī)制的 使用場(chǎng)景 有:
展現(xiàn)某個(gè) Android 手機(jī)有多少應(yīng)用程序
加載手機(jī)中的圖片和視頻資源
訪問(wèn)用戶聯(lián)系人
下面用一個(gè)加載手機(jī)中的圖片文件夾的例子偎血,看看在實(shí)際開(kāi)發(fā)中如何運(yùn)用 Loader 機(jī)制進(jìn)行高效加載。
3.1 實(shí)現(xiàn)自己的加載器
加載器是我們加載數(shù)據(jù)的工具盯漂,通過(guò)將對(duì)應(yīng)的 URI 以及其他的查詢條件傳遞給加載器颇玷,便可讓加載器在后臺(tái)高效地加載數(shù)據(jù),等數(shù)據(jù)加載完成了便會(huì)返回一個(gè) Cursor.
public class AlbumLoader extends CursorLoader {
private static final Uri QUERY_URI = "content://media/external/file";
private static final String[] PROJECTION = {
"_id",
"bucket_id",
"bucket_display_name",
"_data",
"COUNT(*) AS " + "count"};
private static final String SELECTION =
"(media_type=? OR media_type =?) AND _size>0) GROUP BY (bucket_id"
private static final String[] SELECTION_ARGS = {
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_IMAGE),
String.valueOf(MediaStore.Files.FileColumns.MEDIA_TYPE_VIDEO)
};
private static final String BUCKET_ORDER_BY = "datetaken DESC";
private AlbumLoader(Context context, String selection, String[] selectionArgs) {
super(context, QUERY_URI, PROJECTION, SELECTION, SELECTION_ARGS, BUCKET_ORDER_BY);
}
public static CursorLoader newInstance(Context context) {
String selection = SELECTION;
String[] selectionArgs = SELECTION_ARGS;
return new AlbumLoader(context, selection, selectionArgs);
}
@Override
public Cursor loadInBackground() {
return super.loadInBackground();
}
}
3.2 實(shí)現(xiàn) LoaderCallbacks 進(jìn)行客戶端的交互
為了降低代碼的耦合度就缆,繼承 LoaderManager.Loadercallbacks 實(shí)現(xiàn) AlbumLoader 的管理類帖渠,將 Loader 的各種狀態(tài)進(jìn)行管理。
通過(guò)外部傳入 Context竭宰,采用弱引用的方式防止內(nèi)存泄露空郊,獲取 LoaderManager份招,并在 AlbumCollection 內(nèi)部定義了相應(yīng)的接口,將加載完成后返回的 Cursor 回調(diào)出去狞甚,讓外部的 Activity 或 Fragment 進(jìn)行相應(yīng)的處理锁摔。
public class AlbumCollection implements LoaderManager.LoaderCallbacks<Cursor> {
private static final int LOADER_ID = 1;
private WeakReference<Context> mContext;
private LoaderManager mLoaderManager;
private AlbumCallbacks mCallbacks;
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle args) {
Context context = mContext.get();
return AlbumLoader.newInstance(context);
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor data) {
Context context = mContext.get();
mCallbacks.onAlbumLoad(data);
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
Context context = mContext.get();
mCallbacks.onAlbumReset();
}
public void onCreate(FragmentActivity activity, AlbumCallbacks callbacks){
mContext = new WeakReference<Context>(activity);
mLoaderManager = activity.getSupportLoaderManager();
mCallbacks = callbacks;
}
public void loadAlbums(){
mLoaderManager.initLoader(LOADER_ID, null, this);
}
public interface AlbumCallbacks{
void onAlbumLoad(Cursor cursor);
void onAlbumReset();
}
}
3.3 主界面中的邏輯
看到這代碼是不是覺(jué)得特別簡(jiǎn)潔,讓 MainActivity 中繼承了 AlbumCollection 中的 AlbumCallback 接口哼审,接著 onCreate() 中實(shí)例化了 AlbumCollection谐腰,然后讓 AlbumCollection 開(kāi)始加載數(shù)據(jù)。
等數(shù)據(jù)加載完成后棺蛛,便將包含數(shù)據(jù)的 Cursor 回調(diào)在 onAlbumLoad() 方法中怔蚌,我們便可以進(jìn)行 UI 的更新巩步。
可以看到采用 Loader 機(jī)制旁赊,可以讓我們的 Activity 或 Fragment 中的代碼變得相當(dāng)?shù)暮?jiǎn)潔、清晰椅野,而且代碼耦合程度也相當(dāng)?shù)汀?/p>
public class MainActivity extends AppCompatActivity implements AlbumCollection.AlbumCallbacks{
private AlbumCollection mCollection;
private AlbumAdapter mAdapter;
private RecyclerView mRvAlbum;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mCollection = new AlbumCollection();
mCollection.onCreate(this, this);
mCollection.loadAlbums();
}
@Override
public void onAlbumLoad(Cursor cursor) {
mRvAlbum = (RecyclerView) findViewById(R.id.main_rv_album);
mRvAlbum.setLayoutManager(new LinearLayoutManager(this));
mRvAlbum.setAdapter(new AlbumAdapter(cursor));
}
@Override
public void onAlbumReset() {
}
}
以上便是本文的全部?jī)?nèi)容终畅,代碼我已經(jīng)放上 Github 了,需要完整代碼的 點(diǎn)擊這里竟闪。覺(jué)得有幫助的話离福,希望能幫忙給個(gè)喜歡,歡迎關(guān)注炼蛤。