loader 编写一个android_Android之Loader理解
关于我:
90后全栈独立开发者,18 年至今为一名自由职业者,19年成立AgileStudio工作室,提供靠谱的软件定制服务。
在看Android的文档时,看到了这么一个东西: Loader
究竟是什么东西呢?
Introduced in Android 3.0, loaders make it easy to asynchronously load data in an activity or fragment. Loaders have these characteristics:
1、They are available to every Activity and Fragment. //支持Activity和Fragment
2、They provide asynchronous loading of data. //异步下载
3、They monitor the source of their data and deliver new results when the content changes. //当数据源改变时能及时通知客户端
4、They automatically reconnect to the last loader's cursor when being recreated after a configuration change. Thus, they don't need to re-query their data. //发生configuration change时自动重连接
看来这东西蛮强大的,开始我的探索之路吧.
先简单看一下它的用法先:
/**
* Demonstration of the use of a CursorLoader to load and display contacts
* data in a fragment.
*/
public class LoaderCursor extends Activity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
FragmentManager fm = getFragmentManager();
// Create the list fragment and add it as our sole content.
if (fm.findFragmentById(android.R.id.content) == null) {
CursorLoaderListFragment list = new CursorLoaderListFragment();
fm.beginTransaction().add(android.R.id.content, list).commit();
}
}
public static class CursorLoaderListFragment extends ListFragment
implements LoaderManager.LoaderCallbacks {
// This is the Adapter being used to display the list's data.
SimpleCursorAdapter mAdapter;
// If non-null, this is the current filter the user has provided.
String mCurFilter;
@Override public void onActivityCreated(Bundle savedInstanceState) {
mAdapter = new SimpleCursorAdapter(getActivity(),
android.R.layout.simple_list_item_2, null,
new String[] { Contacts.DISPLAY_NAME, Contacts.CONTACT_STATUS },
new int[] { android.R.id.text1, android.R.id.text2 }, 0);
setListAdapter(mAdapter);
getLoaderManager().initLoader(0, null, this);
}
@Override public void onListItemClick(ListView l, View v, int position, long id) {
// Insert desired behavior here.
Log.i("FragmentComplexList", "Item clicked: " + id);
}
// These are the Contacts rows that we will retrieve.
static final String[] CONTACTS_SUMMARY_PROJECTION = new String[] {
Contacts._ID,
Contacts.DISPLAY_NAME,
Contacts.CONTACT_STATUS,
Contacts.CONTACT_PRESENCE,
Contacts.PHOTO_ID,
Contacts.LOOKUP_KEY,
};
public Loader onCreateLoader(int id, Bundle args) {
// This is called when a new Loader needs to be created. This
// sample only has one Loader, so we don't care about the ID.
// First, pick the base URI to use depending on whether we are
// currently filtering.
Uri baseUri;
if (mCurFilter != null) {
baseUri = Uri.withAppendedPath(Contacts.CONTENT_FILTER_URI,
Uri.encode(mCurFilter));
} else {
baseUri = Contacts.CONTENT_URI;
}
// Now create and return a CursorLoader that will take care of
// creating a Cursor for the data being displayed.
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");
}
public void onLoadFinished(Loader loader, Cursor data) {
// Swap the new cursor in. (The framework will take care of closing the
// old cursor once we return.)
mAdapter.swapCursor(data);
// The list should now be shown.
if (isResumed()) {
setListShown(true);
} else {
setListShownNoAnimation(true);
}
}
public void onLoaderReset(Loader loader) {
// This is called when the last Cursor provided to onLoadFinished()
// above is about to be closed. We need to make sure we are no
// longer using it.
mAdapter.swapCursor(null);
}
}
}
这里是Android提供的实例代码,有删减。
从代码上看来,通过实现LoaderManager.LoaderCallbacks就行了.
在onCreateLoader里面实现你要请求的耗时操作,当异步线程操作完成之后就会从onLoadFinished返回数据.
用起来是不是很简单呢?下面具体来看一下它是怎么做到的吧.
getLoaderManager()是定义在Activity类的一个方法,返回类型LoaderManager,但这只是个接口,它真正的实现类是谁呢?
继续往下走,看到这个LoaderManagerImpl getLoaderManager(String who, boolean started, boolean create),方法时,答案便揭晓了.
下面我们来看看LoaderManager相关的类结构,省略了很多东西,但不影响我们的分析.
现在我们来到了LoaderManagerImp的initLoader方法了.
public Loader initLoader(int id, Bundle args, LoaderManager.LoaderCallbacks callback) {
if (mCreatingLoader) {
throw new IllegalStateException("Called while creating a loader");
}
LoaderInfo info = mLoaders.get(id);
if (info == null) {
// Loader doesn't already exist; create.
info = createAndInstallLoader(id, args, (LoaderManager.LoaderCallbacks)callback);
if (DEBUG) Log.v(TAG, " Created new loader " + info);
} else {
if (DEBUG) Log.v(TAG, " Re-using existing loader " + info);
info.mCallbacks = (LoaderManager.LoaderCallbacks)callback;
}
if (info.mHaveData && mStarted) {
// If the loader has already generated its data, report it now.
info.callOnLoadFinished(info.mLoader, info.mData);
}
return (Loader)info.mLoader;
}
这是一个新的Loader,那么info应该是null,转入执行createAndInstallLoader.
private LoaderInfo createAndInstallLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks callback) {
try {
mCreatingLoader = true;
LoaderInfo info = createLoader(id, args, callback);
installLoader(info);
return info;
} finally {
mCreatingLoader = false;
}
}
private LoaderInfo createLoader(int id, Bundle args,
LoaderManager.LoaderCallbacks callback) {
LoaderInfo info = new LoaderInfo(id, args, (LoaderManager.LoaderCallbacks)callback);
Loader loader = callback.onCreateLoader(id, args);
info.mLoader = (Loader)loader;
return info;
}
void installLoader(LoaderInfo info) {
mLoaders.put(info.mId, info);
if (mStarted) {
// The activity will start all existing loaders in it's onStart(),
// so only start them here if we're past that point of the activitiy's
// life cycle
info.start();
}
}
createLoader把必要的信息都封装在LoaderInfo类里面,留意以下这一行:
callback.onCreateLoader(id,arg),这里正是我们上面在客户端实现接口LoaderCallback的那个方法.
接着调用installLoader,这个方法把这次Loader的信息put进mLoader这个SparseArrayCompat中,这个对象可以理解为一个Map,它的性能比Map要好.
mStarted的值是true,它是在getLoaderManager的时候在Activity中传进来的true值.
好了,下面进入LoaderInfo的start方法了.
void start() {
if (mLoader != null) {
if (!mListenerRegistered) {
mLoader.registerListener(mId, this);
mListenerRegistered = true;
}
mLoader.startLoading();
}
}
mLoader就是在客户端实现的那个Loader,回到我们刚开始时的例子,它就是一个CursorLoader.
在分析CursorLoader的startLoading之前,我们先看一下这些Loader的类结构先:
从这些类的名称看来,真正实现了异步传输功能的类应该就是AsyncTaskLoader了,事实是不是这样呢?
继续深入下去:
这里的startLoading是调用了Loader类的方法,下文中我会用这样的方法来标识方法是属于哪个类的: 如Loader –> startLoading
Loader:
public final void startLoading() {
mStarted = true;
mReset = false;
mAbandoned = false;
onStartLoading();
}
CursorLoader:
protected void onStartLoading() {
if (mCursor != null) {
deliverResult(mCursor);
}
if (takeContentChanged() || mCursor == null) {
forceLoad();
}
}
AsynTaskLoader:
protected void onForceLoad() {
super.onForceLoad();
cancelLoad();
mTask = new LoadTask();
if (DEBUG) Slog.v(TAG, "Preparing load: mTask=" + mTask);
executePendingTask();
}
终于看到了LoadTask关键字啦,答案就要揭晓啦.
AsyncTaskLoader:
final class LoadTask extends AsyncTask implements Runnable {
private final CountDownLatch mDone = new CountDownLatch(1);
// Set to true to indicate that the task has been posted to a handler for
// execution at a later time. Used to throttle updates.
boolean waiting;
/* Runs on a worker thread */
@Override
protected D doInBackground(Void... params) {
if (DEBUG) Slog.v(TAG, this + " >>> doInBackground");
try {
D data = AsyncTaskLoader.this.onLoadInBackground();
return data;
} catch (OperationCanceledException ex) {
}
}
/* Runs on the UI thread */
@Override
protected void onPostExecute(D data) {
if (DEBUG) Slog.v(TAG, this + " onPostExecute");
try {
AsyncTaskLoader.this.dispatchOnLoadComplete(this, data);
} finally {
mDone.countDown();
}
}
}
AsyncTaskLoader:
protected D onLoadInBackground() {
return loadInBackground();
}
CursorLoader:
public Cursor loadInBackground() {
try {
Cursor cursor = getContext().getContentResolver().query(mUri, mProjection, mSelection,
mSelectionArgs, mSortOrder, mCancellationSignal);
if (cursor != null) {
// Ensure the cursor window is filled
cursor.getCount();
registerContentObserver(cursor, mObserver);
}
return cursor;
} finally {
synchronized (this) {
mCancellationSignal = null;
}
}
LoadTask原来是个AsyncTask类型,看到这里大家大家应该觉得有种豁然的感觉了吧.
在ForceLoad里面启动该线程,开始执行doInBackground,回调CursorLoader里面的loadInBackgroud,这个方法里面执行真正的耗时操作,
执行完之后一层一层返回,接着调用onPostExecute方法.
好了,现在数据总算是拿到了.
接着执行,把获取的数据往回调.
LoadTask -> onPostExecute
----->
AsynTaskLoader-> dispatchOnLoadComplete
----->
Loader->deliverResult
回调前面注册的loadComplete:
LoaderInfo -> onLoadComplete
---->
LoaderInfo ->callOnLoadFinished
把数据回调给客户端
mCallbacks.onLoadFinished(loader, data);
到这里就完美解释了Loader的特点2,异步
第三点当数据源改变时能及时通知客户端又是如何体现的呢?
这里用了观察者模式来实现.我们先看一下CursorLoader的构造函数:
mObserver = new ForceLoadContentObserver();
这个ForceLoadContentObserver是什么东西呢?
ForceLoadContentObserver继承了ContentObserver,这是Android内部的一个对象,继承了它,就能享受到数据变化时可以接收到通知(也就是观察者中的Subject),这里类似于数据库中的触发器.
先往下看:
在CursorLoader->loadInBackground方法中有这么一句:
registerContentObserver(cursor, mObserver);//注册观察者
答案揭晓了.
注册观察者后,当对应的URI发生变化是,会触发onChange方法
public void onChange(boolean selfChange) {
onContentChanged();
}
public void onContentChanged() {
if (mStarted) {
forceLoad(); //这里重新发送请求.
} else {
// This loader has been stopped, so we don't want to load
// new data right now... but keep track of it changing to
// refresh later if we start again.
mContentChanged = true;
}
}
对于forceLoad方法前面已经提高过了,大家应该还有印象吧.
最后一个问题,也就是第四点:如何做到在configuration change自动重链接的呢?
只要能回答这两个问题,这个问题就解决了.
<1>loader如何在configuration change之前保存数据?
<2>loader如何在configuration chage之后恢复数据并继续load?
LoaderManager:
还记得吗?Loader创建之初,在LoaderManagerImp->installLoader方法里面,
mLoaders.put(info.mId, info);
Info 是LoaderInfo对象,里面封装了Loader的相关信息,表示这个LoaderInfo的Key是mId.
就是在这里保存了loader.这样就回答了问题<1>
对于问题二,首先我们来了解一下configuration change发生之后会发生什么事情呢?
还记得这个生命周期图吗,Fragment的也是差不多的.
当configuration change发生之后,会先把原来的Activity销毁掉,然后再重新构建一个,
也就是会重走一遍onCreate->onStart->onResume的过程.
好了,明白这个之后,我在onStart方法里面找到了线索.
Activity:
protected void onStart() {
if (DEBUG_LIFECYCLE) Slog.v(TAG, "onStart " + this);
mCalled = true;
if (!mLoadersStarted) {
mLoadersStarted = true;
if (mLoaderManager != null) {
mLoaderManager.doStart();
} else if (!mCheckedForLoaderManager) {
mLoaderManager = getLoaderManager(null, mLoadersStarted, false);
}
mCheckedForLoaderManager = true;
}
getApplication().dispatchActivityStarted(this);
}
LoaderManagerImp:
void doStart() {
if (DEBUG) Log.v(TAG, "Starting in " + this);
if (mStarted) {
RuntimeException e = new RuntimeException("here");
e.fillInStackTrace();
Log.w(TAG, "Called doStart when already started: " + this, e);
return;
}
mStarted = true;
// Call out to sub classes so they can start their loaders
// Let the existing loaders know that we want to be notified when a load is complete
for (int i = mLoaders.size()-1; i >= 0; i--) {
mLoaders.valueAt(i).start();
}
}
留意doStart的For循环,真相大白了..
最后总结一下:
1、异步是通过AsynTaskLoader来实现的。
2、通过观察者模式来实现监控数据的变化.
3、通过Activity生命周期中的onStart来实现自动重连接.
loader 编写一个android_Android之Loader理解相关推荐
- 如何编写一个 Webpack Loader
前言 在平时自己由零搭建项目时,虽然基础配置都比较熟悉,比如配置 file-loader, url-loader, css-loader 等,配置不难,但究竟是怎么起作用的呢,今天就来说说如何编写一个 ...
- webpack系统学习(十四)如何编写一个loader
1. 创建一个简单的loader 所谓 loader 只是一个导出为函数的 JavaScript 模块.loader runner 会调用这个函数,然后把上一个 loader 产生的结果或者资源文件( ...
- [Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(二) —— 编写一个基本的 Spider 爬取微博用户信息
上一篇:[Python 爬虫] 使用 Scrapy 爬取新浪微博用户信息(一) -- 新建爬虫项目 在上一篇我们新建了一个 sina_scrapy 的项目,这一节我们开始正式编写爬虫的代码. 选择目标 ...
- WMI技术介绍和应用——使用VC编写一个半同步查询WMI服务的类
在<WMI技术介绍和应用--VC开发WMI应用的基本步骤>文中,我们介绍了VC使用WMI技术的基本框架.本节我将讲解封装和实现一个用于半同步查询WMI的类.(转载请指明出于breaksof ...
- 编写一个最简单的.php,学习猿地- 说明 如果我们要编写一个简单的PHP脚本,需要学习哪些...
说明 如果我们要编写一个简单的 PHP 脚本,需要学习哪些基础知识呢? PHP 基础 PHP 脚本可放置于文档中的任何位置. 标准 的 PHP 脚本以 <?php 开头,以 ?> 结尾: ...
- c语言中如何设计和编写一个应用系统?
C程序中,如何设计和编写一个应用系统? 一. C语言文件的操作 1. 文件操作的基本方法: C语言将计算机的输入输出设备都看作是文件.例如,键盘文件.屏幕文件等. 向屏幕输出一个信息,例如" ...
- 如何编写一个全新的 Git 协议
曾几何时,我在持续追踪自己的文件方面遇到一些问题.通常,我忘了自己是否将文件保存在自己的桌面电脑.笔记本电脑或者电话上,或者保存在了云上的什么地方.更有甚者,对非常重要的信息,像密码和Bitcoin的 ...
- 编写一个自己的完全C语言版本的memset函数,并且评价这个实现的性能和可移植性。
编写一个自己的完全C语言版本的memset函数,并且评价这个实现的性能和可移植性. memset这个函数的作用大致可以理解为将从指针指向的地址开始的指定字节长度的空间全部替换为指定的值.那么我们可以很 ...
- python脚本编写_如何用Python包编写一个简单的脚本,表达你对父母的爱?
全文共2800字,预计学习时长6分钟 在繁忙的工作生活中,我们经常忘记给所爱的人发WhatsApp.本教程将使用Python包Twilio编写一个简单的Python脚本来发送WhatsApp消息.我们 ...
最新文章
- reshape功能介绍_函数 reshape 的用法
- 22.2. 表管理(Table)
- Linux--缺页中断和交换技术
- 【多线程】阻塞队列的C++多线程 实现 BlockingQueue
- Android API Level对应Android版本一览表
- rgb 灰色_金属+RGB+无线,我要买爆这款海盗船VIRTUOSO鉴赏家游戏耳机
- 钱 | 钱,钱,钱,钱,钱,钱,钱!钱啊钱!(配音乐)
- 嗖嗖移动业务大厅代码_移动云应用性能监控,掌控云时代的多变应用
- android点赞动画仿twritter,【点赞动画仿抖音】Android 自定义view动画--酷炫点赞动画...
- 20分钟理解React Native For Android原理
- 使用cxf框架搭建webservice
- 渗透开发-XSS扫描器
- 三国群雄传ol服务器 修改,三国群英传ol 单机版,如何修改国战、暴率、经验等问题?...
- 5年迭代5次,抖音推荐系统演进历程
- WORD中插入三线制表格
- 研发工具之谷歌浏览器无法加载flash的问题
- 小程序使用wxs处理手机号
- matlab的persistent,MATLAB局部静态变量类型persistent
- MySql中 where IN 中多个参数查询
- 比尔·盖茨买百万亩农地成美“头号地主”,图扑数字孪生农场