詳細(xì)講解Android中使用LoaderManager加載數(shù)據(jù)的方法
Android的設(shè)計(jì)之中,任何耗時(shí)的操作都不能放在UI主線程之中。所以類似于網(wǎng)絡(luò)操作等等耗時(shí)的操作都需要使用異步的實(shí)現(xiàn)。而在ContentProvider之中,也有可能存在耗時(shí)的操作(當(dāng)查詢的數(shù)據(jù)量很大的時(shí)候),這個(gè)時(shí)候我們也需要使用異步的調(diào)用來(lái)完成數(shù)據(jù)的查詢。
當(dāng)使用異步的query的時(shí)候,我們就需要使用LoaderManager了。使用LoaderManager就可以在不阻塞UI主線程的情況下完成數(shù)據(jù)的加載。
(1)獲取loaderManger:activity.getLoaderManager()
(2)loaderManager的事件回調(diào)接口, LoaderManager.LoaderCallbacks<D>
下面是一個(gè)demo,從contentprovider中query數(shù)據(jù)添加到listview中,是異步執(zhí)行的。
MySQLiteOpenHeleper.java:
package com.app.loadermanager;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteDatabase.CursorFactory;
import android.database.sqlite.SQLiteOpenHelper;
public class MySQLiteOpenHelper extends SQLiteOpenHelper {
public static final String db_name = "test.db3";
public static final int version = 1;
public MySQLiteOpenHelper(Context context) {
super(context, db_name, null, version);
}
@Override
public void onCreate(SQLiteDatabase db) {
String create_sql = "create table tb_student(_id integer primary key autoincrement,name varchar(20),age integer)";
db.execSQL(create_sql);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
}
}
MyContentProvider.java
package com.app.loadermanager;
import android.content.ContentProvider;
import android.content.ContentUris;
import android.content.ContentValues;
import android.content.UriMatcher;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
public class MyContentProvider extends ContentProvider {
private MySQLiteOpenHelper helper = null;
private static final UriMatcher matcher = new UriMatcher(
UriMatcher.NO_MATCH);
private static final int students = 1;
static {
matcher.addURI("com.app.contentprovider", "tb_student", students);
}
@Override
public int delete(Uri arg0, String arg1, String[] arg2) {
return 0;
}
@Override
public String getType(Uri arg0) {
return null;
}
@Override
public Uri insert(Uri uri, ContentValues values) {
SQLiteDatabase db = helper.getWritableDatabase();
int flag = matcher.match(uri);
switch (flag) {
case students:
long id = db.insert("tb_student", null, values);
return ContentUris.withAppendedId(uri, id);
}
return null;
}
@Override
public boolean onCreate() {
helper = new MySQLiteOpenHelper(this.getContext());
return true;
}
@Override
public Cursor query(Uri uri, String[] projection, String selection,
String[] selectionArgs, String sortOrder) {
SQLiteDatabase db = helper.getWritableDatabase();
Cursor cursor=db.query("tb_student", projection, selection, selectionArgs, null, null, null);
return cursor;
}
@Override
public int update(Uri uri, ContentValues values, String selection,
String[] selectionArgs) {
return 0;
}
}
MainActivity.java:
package com.app.loadermanager;
import java.util.ArrayList;
import android.annotation.SuppressLint;
import android.app.Activity;
import android.app.LoaderManager;
import android.content.CursorLoader;
import android.content.Loader;
import android.database.Cursor;
import android.net.Uri;
import android.os.Bundle;
import android.widget.ArrayAdapter;
import android.widget.ListView;
@SuppressLint("NewApi")
public class MainActivity extends Activity implements
LoaderManager.LoaderCallbacks<Cursor> {
LoaderManager manager = null;
ListView listView = null;
@SuppressLint("NewApi")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
listView = (ListView) this.findViewById(R.id.listview);
manager = this.getLoaderManager();
manager.initLoader(1000, null, this);
}
@Override
public Loader<Cursor> onCreateLoader(int id, Bundle bundle) {
CursorLoader loader = new CursorLoader(this,
Uri.parse("content://com.app.contentprovider"), null, null,
null, null);
return loader;
}
@Override
public void onLoadFinished(Loader<Cursor> loader, Cursor cursor) {
ArrayList<String> al = new ArrayList<String>();
while (cursor.moveToNext()) {
String name = cursor.getString(cursor.getColumnIndex("name"));
al.add(name);
}
ArrayAdapter adapter=new ArrayAdapter(this,android.R.layout.simple_list_item_1,al);
listView.setAdapter(adapter);
adapter.notifyDataSetChanged();
}
@Override
public void onLoaderReset(Loader<Cursor> loader) {
}
}
LoaderManager與生命周期
Activity和Fragment都擁有g(shù)etLoaderManager的方法,其實(shí)Fragment的getLoaderManager去獲取的就是Activity所管理的眾多LoaderManager之一。
1.Who標(biāo)簽
先來(lái)看一下Activity的getLoaderManager方法:
LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create) {
if (mAllLoaderManagers == null) {
mAllLoaderManagers = new ArrayMap<String, LoaderManagerImpl>();
}
LoaderManagerImpl lm = mAllLoaderManagers.get(who);
if (lm == null) {
if (create) {
lm = new LoaderManagerImpl(who, this, started);
mAllLoaderManagers.put(who, lm);
}
} else {
lm.updateActivity(this);
}
return lm;
}
mAllLoaderManagers保存著一個(gè)Activity所擁有的所有LoaderManager,其key為String類型的who變量。若從Activity調(diào)用getLoaderManager,那么所得LoaderManager的who標(biāo)簽為(root):
public LoaderManager getLoaderManager() {
if (mLoaderManager != null) {
return mLoaderManager;
}
mCheckedForLoaderManager = true;
mLoaderManager = getLoaderManager("(root)", mLoadersStarted, true);
return mLoaderManager;
}
若從Fragment中使用getLoaderManager,則所得LoaderManager的who標(biāo)簽會(huì)根據(jù)Fragment的層級(jí)不同而不同,在Activity中處于最頂級(jí)的Fragment的who標(biāo)簽為:
android:fragment:X
X為序號(hào)。
而屬于Fragment的Fragment所活的的LoaderManager who標(biāo)簽就成為了:
android:fragment:X:Y
甚至更大的深度。
2.LoaderManager狀態(tài)量mLoadersStarted
在開(kāi)篇分析的時(shí)候分析過(guò)LoaderManager內(nèi)部保存了一個(gè)mStarted狀態(tài),很多操作會(huì)根據(jù)這個(gè)狀態(tài)做不同處理。通過(guò)上邊的分析也能看出,F(xiàn)ragment中獲取LoaderManager最終是通過(guò)Activity獲取的,在LoaderManager構(gòu)造時(shí),就將一個(gè)狀態(tài)量mLoadersStarted傳遞了進(jìn)去,這個(gè)狀態(tài)量交給LoaderManager自行管理。
而無(wú)論是Fragment還是Activity,都有mLoadersStarted這樣一個(gè)狀態(tài)量,在onStart生命周期后為true,onStop后為false。所以在onStart生命周期后做initLoader操作就會(huì)使Loader一經(jīng)初始化就開(kāi)始運(yùn)行了。
3.Fragment和Activity的生命周期
Fragment和Activity能夠?qū)oaderManager產(chǎn)生影響的生命周期是一樣的。
onStart
系統(tǒng)在onStart階段會(huì)獲取LoaderManager,如果成功獲取,則調(diào)用LoaderManager的doStart(),這里需要特別說(shuō)明的是,雖然所有LoaderManager都是保存在Activity中,但是在Activity的onStart生命周期其也只是會(huì)獲取屬于自己的(root)標(biāo)簽LoaderManager,而并不是將所有保存在mAllLoaderManagers里的Manager全部遍歷一遍。
onStop(performStop)
處于onStop生命周期,但是系統(tǒng)內(nèi)部是通過(guò)performStop調(diào)用的。在這里,同樣會(huì)獲取屬于自己的LoaderManager,如果Activity是因?yàn)榕渲酶淖兂霭l(fā)的onStop(旋轉(zhuǎn)屏幕),則調(diào)用LoaderManager的doRetain()接口,如果不是,就調(diào)用LoaderManager的doStop()接口。
onDestroy(performDestroy)
調(diào)用LoaderManager的doDestroy()接口銷毀LoaderManager。
4.LoaderManager的生命周期
因?yàn)長(zhǎng)oaderManager與Fragment/Activity的生命周期緊密相連,所以想要用好LoaderManager就必須了解其自身的生命周期,這樣就能把握數(shù)據(jù)的完整變化規(guī)律了。
正常的從出生到銷毀:
doStart() -> doReportStart() -> doStop() -> doDestroy()
Activity配置發(fā)生變化:
doStart() -> doRetain() -> finishRetain() -> doReportStart() -> doStart() -> doStop() -> doDestroy()
Fragment在onDestroyView()之后還會(huì)執(zhí)行LoaderManager的doReportNextStart(), 即:
doStart() -> doRetain() -> doReportNextStart() -> finishRetain() -> doReportStart() -> doStart() -> doStop() -> doDestroy()
doStart()會(huì)將LoaderManager中保存的所有Loader都啟動(dòng)。最終是運(yùn)行每一個(gè)Loader的onStartLoading()方法。只要是通過(guò)initLoader使用過(guò)的Loader都會(huì)記錄在LoaderManager的mLoaders中,那么問(wèn)題來(lái)了:
怎樣在Fragment/Activity不銷毀的前提下從LoaderManager中移除某個(gè)使用過(guò)的Loader呢?
答案就是使用LoaderManager的接口去除指定ID的Loader:
public void destroyLoader(int id)
這樣就能在mLoaders中移除掉了,下次onStart的時(shí)候就沒(méi)有這個(gè)Loader什么事了。
doReportStart()。如果Fragment上一次在銷毀并重做,而且數(shù)據(jù)有效的話會(huì)在這里主動(dòng)上報(bào)數(shù)據(jù),最終走到callback的onLoadFinished中。
doStop()會(huì)停止mLoaders保存的所有Loader。最終是運(yùn)行每一個(gè)Loader的onStopLoading()方法。
doDestroy()會(huì)清空所有有效和無(wú)效Loader,LoaderManager中不再存在任何Loader。
doRetain()會(huì)將LoaderManager的mRetaining狀態(tài)置位true,并且保存retain時(shí)LoaderInfo的mStarted狀態(tài)
finishRetain()如果之前所保存的mStarted與現(xiàn)在的不一樣而且新的狀態(tài)是停止的話,就停止掉這個(gè)Loader。否則若有數(shù)據(jù)并且不是要下次再上報(bào)(沒(méi)有call doReportNextStart)的話就上報(bào)給callback的onLoadFinished。
doReportNextStart(),根據(jù)第6條,已經(jīng)能夠理解了。當(dāng)Fragment執(zhí)行到onDestroyView生命周期時(shí),對(duì)自己的LoaderManager發(fā)出請(qǐng)求:即使現(xiàn)在有數(shù)據(jù)也不要進(jìn)行上報(bào),等我重做再到onStart生命周期時(shí)再給我。
- Android 開(kāi)發(fā) 使用WebUploader解決安卓微信瀏覽器上傳圖片中遇到的bug
- Android利用CursorLoader實(shí)現(xiàn)短信驗(yàn)證碼自動(dòng)填寫(xiě)
- Android ImageLoader第三方框架解析
- Android開(kāi)發(fā)中類加載器DexClassLoader的簡(jiǎn)單使用講解
- 全面解析Android的開(kāi)源圖片框架Universal-Image-Loader
- Android開(kāi)發(fā)之ImageLoader本地緩存
- Android Universal ImageLoader 緩存圖片
- 從源代碼分析Android Universal ImageLoader的緩存處理機(jī)制
- Android開(kāi)發(fā)之ImageLoader使用詳解
- android CursorLoader用法介紹
- Android Loader詳細(xì)介紹及實(shí)例代碼
相關(guān)文章
Android自定義控件實(shí)現(xiàn)九宮格解鎖
這篇文章主要為大家詳細(xì)介紹了Android自定義控件實(shí)現(xiàn)九宮格解鎖,文中示例代碼介紹的非常詳細(xì),具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2022-06-06
Android時(shí)間對(duì)話框TimePickerDialog詳解
這篇文章主要為大家詳細(xì)介紹了Android時(shí)間對(duì)話框TimePickerDialog的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2017-02-02
Android布局耗時(shí)監(jiān)測(cè)的三種實(shí)現(xiàn)方式
在Android應(yīng)用開(kāi)發(fā)中,性能優(yōu)化是一個(gè)至關(guān)重要的方面,為了更好地監(jiān)測(cè)布局渲染的耗時(shí),我們需要一種可靠的實(shí)現(xiàn)方案,本文將介紹三種針對(duì)Android布局耗時(shí)監(jiān)測(cè)的實(shí)現(xiàn)方案,幫助開(kāi)發(fā)者及時(shí)發(fā)現(xiàn)并解決布局性能問(wèn)題,需要的朋友可以參考下2024-03-03
Android實(shí)現(xiàn)通過(guò)手勢(shì)控制圖片大小縮放的方法
這篇文章主要介紹了Android實(shí)現(xiàn)通過(guò)手勢(shì)控制圖片大小縮放的方法,結(jié)合實(shí)例形式分析了Android控制圖片縮放的原理、實(shí)現(xiàn)步驟與相關(guān)操作技巧,需要的朋友可以參考下2016-10-10
android IntentService實(shí)現(xiàn)原理及內(nèi)部代碼分享
android IntentService實(shí)現(xiàn)原理及內(nèi)部代碼分享,需要的朋友可以參考一下2013-06-06
Android TextView多文本折疊展開(kāi)效果
這篇文章主要為大家詳細(xì)介紹了Android TextView多文本折疊展開(kāi)效果的相關(guān)資料,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2016-10-10
Android轉(zhuǎn)場(chǎng)動(dòng)畫(huà)深入分析探究
對(duì)于一個(gè)動(dòng)畫(huà)而言,它是由多個(gè)分鏡頭組成的,而轉(zhuǎn)場(chǎng)就是分鏡之間銜接方式。轉(zhuǎn)場(chǎng)的主要目的,就是為了讓鏡頭與鏡頭之間過(guò)渡的更加自然,讓動(dòng)畫(huà)更加連貫,例如兩個(gè)頁(yè)面切換之間的銜接動(dòng)畫(huà)2022-10-10
Android實(shí)現(xiàn)自適應(yīng)屏幕的彈窗廣告
這篇文章主要為大家詳細(xì)介紹了Android實(shí)現(xiàn)自適應(yīng)屏幕的彈窗廣告,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下2018-07-07

