Android 深入理解Loader机制 让APP轻装上阵

Android开发者都经历过APP UI开发不当 会造成overDraw,导致APP UI渲染过慢,但是很多人却没听过overLoad,overLoad一般是由于开发者在主线程操作耗时操作,导致程序变慢 甚至出现的anr的现象,那么android早已为这种现象提供完美的解决方案,就是今天给大家说的Loader机制。

一 Loader
Android的装载器(loader)是从Android 3.0新引入的API , 主要完成单线程耗时数据异步装载功能,并在数据有更新自动通知UI刷新的作用。业内也叫加载器,装载机。
Loader用途
Loader一般用在Activity和fragment异步加载数据,无需重新启动一个线程来执行数据加载,异步加载可以用asyncTask, 但是loader自带数据结果监听机制,可以方便优雅的进行UI更新。

作用和优点:

提供异步加载数据功能;
对数据源变化进行监听,实时更新数据;
在Activity配置发生变化(如横竖屏切换)时不避免数据重复加载;
适用于任何Activity和Fragment;

加载耗时数据常用方式
android开发者都知道不能再UI线程里去执行耗时操作,甚至在4.0里已经无法在主线程里去访问网络,那么一般加载耗时操作有以下办法。

1 2B加载


2b.jpg

2 普通加载

普通.jpg

3 文艺加载

使用Loader.jpg

为何说1和2是不可取呢,我们从Loader源码看起,文章结束会知道答案。

二 Loader实现

Loader源码在android.content下面,可见它的份量有多重,loader机制包括LoaderManager,Loader,LoaderCallbacks三部分,
LoaderManager 来管理我们的laoder实例,获取,初始化,重启一个loader,
Loader 来执行我们的异步操作,有开始,完成,后台加载中等接口实现
LoaderCallbacks 来执行我们的loader回调,主要是绑定分发Loader,完成加载,重置数据等。
流程如下图:


图片1.png

1 LoaderManager
LoaderManager是抽象类,负责管理一组Loader,主要定义执行Loader的一些抽象方法,类结构如下图:

LoadManager.jpg

从上图看以看出,Ta里面主要初始化loader,获取 重启,销毁一个loader,也包含一个内部成员变量LoaderCallback回调,主要方便我们在上层写回调实现累操作,但真正是由他的实现类LoaderManagerImpl去完成操作的,
LoaderManagerImpl 记录着一组LoaderInfo信息,(ps:Activity也同等拥有activityInfo一样,只不过记录是Activityrecord来完成,其实谷歌很多源码都是相同的),并且持有LoaderManager.LoaderCallbacks, mLoader等成员,负责对Loader和LoaderCallbacks的对应回调,内部基于观察者模式实现,源码不在解读;

2 Loader
Loader是具体来操作任务的类,负责去调用不同渠道的数据接口,比如数据库,contentProvider, 文件等。

loader.jpg

从大致的UML图我可以了解loader持有一个内部观察者,和一些注册注销观者的内部方法,并且已经暴露出来的加载操作的状态步骤的方法,包括加载中,取消加载,强制加载,内容发生改变等,
在平常的开发中,谷歌为我们提供了laoder的子类,AsyncTaskLoader,CursorLoader等子类, 源码不在介绍,现在说下他们的不同点。CursorLoader也是AsyncTaskLoader的子类,主要负责数据库查询的异步加载,AsyncTaskLoader可用来所有异步加载。

2.1 AsyncTaskLoader
AsyncTaskLoader继承了Loader, 除了拥有loader的功能,还有executePendingTask(), dispatchOnCancelled(),onLoadInBackground()等方法,最神奇的是他拥有AsyncTask的实例,并且实现Runnable,这是他能进行异步的原因所在。对AsyncTask不熟悉的请自我补脑,看如下代码,

AsyncTaskLoader.jpg

笔者看了源码,AsyncTaskLoader拥有AsyncTask,在自身实例化后开启一个线程,自我进行executePendingTask(),此方法里其实就在执行asyncTask的mTask.executeOnExecutor(mExecutor,(Void[])null);�来实现AsyncTaskLoader的自我监听机制,当然自身轮询和通信是离不开Handler的 因为整个android的通讯就是建立在Handler(底层binder)基础上,这里不再分析。

2.2 CursorLoader
CursorLoader是AsyncTaskLoader的子类,内部持有ForceLoadContentObserver变量,观察者来实现对数据源的数据更新,执行加载数据操作,最重要的当然是离不开查询操作,内部主要代码:

CursorLoader

三 怎么使用loader

1 启动一个Loader
Activity初始化在oncreate()初始化,一个Activity或Fragment中LoaderManager管理一个或多个Loader实例,每个Activity或Fragment只有一个LoaderManager,我们可以在Activity的onCreate()或Fragment的onActivityCreated()里初始化一个Loader。例如:
getLoaderManager().initLoader(0, null, new DataLoaderCallback());

可以看见上面的initLoader()方法有三个参数:

第一个参数代表当前Loader的ID ,用来区分哪个loader;

第二个参数代表提供给Loader构造函数的参数,Bundle对象类型 ,可选;

第三个参数代表LoaderManager.LoaderCallbacks的回调实现 需要我自我实现。

上面initLoader()方法的调用一个Loader被初始化和激活的状态,该方法的调运有如下两种结果:

如果代表该Loader的ID已经存在,则后面创建的Loader将直接复用已经存在的;

如果代表该Loader的ID不存在,initLoader()会触发LoaderManager.LoaderCallbacks回调的onCreateLoader()方法创建一个Loader;

可以看见通过initLoader()方法可以将LoaderManager.LoaderCallbacks实例与Loader进行关联,且当Loader的状态变化时就被回调。所以说,如果调用者正处于其开始状态并且被请求的Loader已经存在,且已产生了数据,那么系统会立即调用onLoadFinished()(在initLoader()调用期间),所以你必须考虑到这种情况的发生。

当然了,intiLoader()会返回一个创建的Loader,但是你不用获取它的引用,因为LoadeManager会自动管理该Loader的生命周期,你只用在它回调提供的生命周期方法中做自己数据逻辑的处理即可。

2 实现LoaderManager.Callbacks回调

LoaderManager.LoaderCallbacks是LoaderManager的回调交互接口。LoaderManager.LoaderCallbacks包含以下三个方法:

onCreateLoader()
实例化并返回一个新创建给指定ID的Loader对象;第一启动时调用

onLoadFinished()
load完成之后回调此方法;每次都调用

onLoaderReset()
当创建好的Loader被reset时调用此方法,会清空已绑定数据,此时CreatLoader会重新执行

3 Loader使用实例

1》 初始化loader

getLoaderManager().initLoader(0, null, new DataLoaderCallback());

2》实现callback接口,注册自我监听回调

callback

当然你也可以用来绑定谷歌提供的CursorLoader ,在Loader创建的时候被调用,这里使用一个ContentProvider获取数据,所以使用CursorLoader返回数据

CursorLoader

3》 继承Loader,构造自我的数据绑定,和数据适配

Loader

在这里我们模拟了构造一组数据,当然你也可以在loadInBackgruond去读文件,访问网络,查询数据库

4 拓展
1》 用来自动刷新ContentPorvider

在我们使用CurSorLoader时大家都会考虑一种情况的处理—–当数据库发生变化时如何自动刷新当前UI,数据库在数据改变时通过ContentPorvider和ContentResolver发出通知,接着ContentProvider通知Cursor的观察者数据发生了变化,然后Cursor通知CursorLoader的观察者数据发生了变化,CursorLoader又通过ContentProvider加载新数据,完成后调用CursorAdapter的changeCursor()用新数据替换旧数据显示。

这个过程具体的实现步骤如下:

对获取的Cursor数据设置需要监听的URI(即,在ContentProvider的query()方法或者Loader的loadingBackground()方法中调用Cursor的setNotificationUri()方法);

在ContentProvider的insert()、update()、delete()等方法中调用ContentResolver的notifyChange()方法;

通过上面两步我们就能实现CurSorLoader的自动数据刷新功能了;可以发现,所谓的CurSorLoader自动刷新也是对文章开头说的观察者模式,所以不再过多说明。
2》不使用ContentPorvider的自动刷新


自动数据刷

四Loaders相关源码流程

通过上面我们的源码分析和分析前那副图可以总结如下结论:

一次完整的数据加载流程为Activity调用LoaderManager的doStart()方法,LoaderManager调用Loader的startLoading()方法,然后Loader调运AsyncTaskLoader的doingBackground()方法进行耗时数据加载,紧接着AsyncTaskLoader回调LoaderManager的complete数据加载完成方法,接着又LoaderManager回调我们在Activity中实现的callback中的onLoadFinish()方法。

Acivity和Fragment的生命周期主动管理了LoaderManager,每个Activity用一个ArrayMap的mAllLoaderManager来保存当前Activity及其附属Frament的唯一LoaderManager;在Activity配置发生变化时,Activity在destory前会保存mAllLoaderManager,当Activity再重新创建时,会在Activity的onAttcach()、onCreate()、performStart()方法中恢复mAllLoaderManager。

LoaderManager给Activity提供了管理自己的一些方法;同时主动管理了对应的Loader,它把每一个Loader封装为LoadInfo对象,同时它负责主动调运管理Loader的startLoading()、stopLoading()、,forceLoad()等方法。

由于整个Activity和Fragment主动管理了Loader,所以关于Loader的释放(譬如Cursor要要主动关闭游标的等,文件流要置空等)不需要我们人为处理,Loader会帮我们很好的处理的;同时特别注意,对于CursorLoader,当我们数据源发生变化时Loader框架会通过ContentObserver调用onContentChanged的forceLoad方法重新请求数据进行回调刷新。

五 总结
通过前面基础实例、源码分析、拓展你会发现Loader很强大,例如在普通展现某个android手机有多少应用程序,加载已安装app时候,其实loader就能排上用场。
详细见谷歌对Loader介绍:
https://developer.android.com/reference/android/content/AsyncTaskLoader.html

PS:顺便说下AsyncTaskLoader与AsyncTask的区别,看完源码我们再回过头来总结性的说说他们二者区别,如下:

两者区别

最主要是加载数据,使用loader我们无须关注数据何时改变了,也无需关注activity的生命周期,做到数据不被重复多次加载情况,activty销毁数据自动释放的作用,做到一次加载多次使用的效果,我们可以依据需求,拿着loader变活灵通,这里的博大精深还需要你自己体会。
案例:https://github.com/NeglectedByBoss/Loader
更多文章请猛戳

第一时间获取大佬技术文章和行业动态请关注微信公众号!

开发者技术前线
最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 158,425评论 4 361
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 67,058评论 1 291
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 108,186评论 0 243
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 43,848评论 0 204
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 52,249评论 3 286
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 40,554评论 1 216
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 31,830评论 2 312
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 30,536评论 0 197
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 34,239评论 1 241
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 30,505评论 2 244
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 32,004评论 1 258
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 28,346评论 2 253
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 32,999评论 3 235
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 26,060评论 0 8
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 26,821评论 0 194
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 35,574评论 2 271
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 35,480评论 2 267

推荐阅读更多精彩内容

  • Android 自定义View的各种姿势1 Activity的显示之ViewRootImpl详解 Activity...
    passiontim阅读 170,565评论 25 707
  • 1 背景## 在Android中任何耗时的操作都不能放在UI主线程中,所以耗时的操作都需要使用异步实现。同样的,在...
    我是昵称阅读 1,173评论 0 3
  • 你一直低头 盯着书本 那么柔雅 那么恬静 不知道 我像你盯着书本那样 一直 静静地盯着你 我挠挠头 实在猜不透 哪...
    蜗牛在路上遇到了黄鹂阅读 238评论 0 0
  • 每次换电脑都得重新安装所有的环境和软件,为方便日后再次安装,特此记录。 1、安装 Homebrew 直接打开终端并...
    SwiftAlan阅读 1,352评论 0 0