loader 编写一个android_简析 Android Loader 机制下状态保留的实现

Loader 是 Android 3.0 HoneyComb 中随 Fragment 一同引入的一个异步数据加载机制,旨在给开发者带来更便利的异步数据库查询方式,当然我们可以自己扩展 Loader 类来实现更灵活的数据加载功能。

本文不会介绍 Loader 的使用方法,这里我们来分析一下它是如何做到状态保留的。首先我们来看看不用它时异步加载数据是怎样的一种情景:

如果不做特殊处理的话,每次屏幕旋转或进入退出多窗口模式(窗口布局改变)时 Activity 就会被销毁重启,那么这时就需要我们手动保存 Activity 中一些重要的状态,比如用户正在浏览的内容 Id,键入的文字,大部分 Widgets 的状态能够自动保存。但即使这样,我们并不能将大量的实体数据存在状态 Bundle 里,因为其序列化后的 Parcel 对象在 Binder 中的传输大小是有限制的,很容易出现问题。为了解决这个问题,我们通常会使用全局对象,包括单例和静态成员变量,使用一个不会随 Activity 销毁的控制器来集中存储这些实体数据。再者,直接设置旋转屏幕时不重建 Activity,我认为这是最差的一种方法,状态管理一定是要做的,而不能通过这种方法避免对状态的维护。

然而如果我们使用 Loader 来加载数据,你就会发现当 Activity 或 Fragment 因为突发情况被重建时,刚才加载的数据仍然存在,LoaderCallbacks 能立即调用 onLoadFinished 方法来提交数据。没有任何手动维护状态的过程,这是怎么做到的呢?

下面我们就来一步一步分析一下。

首先我们在 onLoadFinished 处下一个断点,来跟踪它是如何被调用的:

这是在加载结束后,旋转屏幕导致 Fragment 重建,紧接着 onLoadFinished 方法被调用的调用栈。下层基本就是 Activity 声明周期的处理,我们直接跳过,来看 FragmentHostCallback #reportLoaderStart 这个方法:

void reportLoaderStart() {

if (mAllLoaderManagers != null) {

final int N = mAllLoaderManagers.size();

LoaderManagerImpl loaders[] = new LoaderManagerImpl[N];

for (int i=N-1; i>=0; i--) {

loaders[i] = (LoaderManagerImpl) mAllLoaderManagers.valueAt(i);

}

for (int i=0; i

LoaderManagerImpl lm = loaders[i];

lm.finishRetain();

lm.doReportStart();

}

}

}

这里我们能看到,mAllLoaderManagers 这个数组一定盛放了之前保留下来的 LoaderManager 对象,对它调用 finishRetain 就会导致其管理的 Loader 对象触发 onLoadFinished 来进行状态恢复。所以下面我们就应该关心一下 mAllLoaderManagers 是怎么来的了。根据命名它显然不是一个静态变量,所以我们直接检索对其赋值的语句,能够找到:

void restoreLoaderNonConfig(SimpleArrayMap loaderManagers) {

mAllLoaderManagers = loaderManagers;

}

OK,那么被保留的 LoaderManagers 一定是在这里被交至 FragmentHostCallback 对象的。

下一步我们就可以为这个方法打一个断点,继续跟踪是谁触发的它。

可以发现 Activity 一被创建,保留的对象就会被恢复,我们来看看具体实现:

NonConfigurationInstances nc =

(NonConfigurationInstances) getLastNonConfigurationInstance();

if (nc != null) {

mFragments.restoreLoaderNonConfig(nc.loaders);

}

这是 FragmentActivity#onCreate 的一段代码,getLastNonConfigurationInstance 方法拿到了之前保留的对象。这个方法直接返回了 Activity 中的 mLastNonConfigurationInstances 成员变量。Well,它又是在哪被赋值的呢?通过上面的方法,可以找到,在 attach 这个方法中,它被当做一个参数传了进来。

到这里,我们一直在跟踪变量,是不是很无聊,接下来终于轮到与 AMS 交互了。

这里我们变一下分析方法,冒泡式的寻找基层 caller 不容易跟踪最终的对象是被哪个参数生成的。所以这里我索性直接在 ActivityThread$H 这个 Handler 类的 handleMessage 上打个断点,这是 AMS 通过 Binder 通讯后本地对象处理的入口。

上面我们分析出,mLastNonConfigurationInstances 是由参数传递来的,而这个参数正是 ActivityClientRecord 这个类,在 Handler 的处理方法中,我们也能看到正在处理的 Message 的 obj 就是这个 ActivityClientRecord对象,然而里面的 lastNonConfigurationInstances 成员变量是 null?这当然正常,因为 AMS 与本地通讯肯定是不能携带本地对象的嘛,所以这个对象肯定是保存在本地,然后被某一步查询出来插进 record 中的。

继续查找发现 record 对象中途竟然被掉包,在 handleRelaunchActivity 这个方法中,ActivityThread 通过一个名为 mActivities 的 Map,以 token 为键查出了一个新的 record 对象!token 是什么呢?它其实就是 AMS 用于标识一个 Activity 所创建的一个 Binder,由于 Binder 可以通过 writeStrongBinder 方法被写进 Parcel 中从而在 Binder 上传输,所以这里 AMS 就可以将之前需要重启的 Activity 的 token 放在 record 中传给本地应用了。然而掉包后的 record 对象仍然没有 lastNonConfigurationInstances 这个对象,依然是 null。

好吧,掐头去尾,这个 lastNonConfigurationInstances 到底是什么时候被赋值的呢?这里我只好用二分法来锁定它被赋值的时机。最终终于锁定到了 performDestroyActivity 这个方法:

if (getNonConfigInstance) {

try {

r.lastNonConfigurationInstances

= r.activity.retainNonConfigurationInstances();

} catch (Exception e) {

if (!mInstrumentation.onException(r.activity, e)) {

throw new RuntimeException(

"Unable to retain activity "

+ r.intent.getComponent().toShortString()

+ ": " + e.toString(), e);

}

}

}

Activity 的 retainNonConfigurationInstances 方法将一系列状态整理后返回,最后赋给了 record。

Recap

至此我们再理顺一下思路:

Activity 进入 Relaunch 阶段首先会进入 Destroy 的生命周期,然而这里有一个特殊的 flag 指明:我们需要把一个对象传递给下一个 Activity 实例,虽然 Activity 对象不是同一个了,但其中的某些东西是可以移花接木顺延过来的(其中就包括 LoaderManager),这样,尽管上一个 Activity 被 Destroy 阶段销毁了,但是其中一部分数据直接通过 attach 方法被拿到新的 Activity 里了。所以,也仅限于 Relaunch,其他的自然销毁默认情况下是仍然不会保留那部分数据的。

这里可以大胆猜测一下 Android 之所以这么做的原因,为什么会出现 Relaunch?几种情况,比如屏幕旋转,系统配置改变,需要重新 setTheme 等等,销毁 Activity 再建一个是最安全的方法,不会出现一些由于状态改变而造成的非预期状况,然而对于转屏而言,整个 Activity 被丢掉了显然有些可惜,所以系统需要保留状态,状态又分为两种:一种是可以写进 Parcel 的,这种状态在由于内存紧张而造成资源回收时也能得以保留;而另一种就是通过 NonConfigurationInstances 这种方法保存,它在进程退出时就会直接销毁,但是进程都退出了,Relaunch 是肯定不会走的了,所以这类状态就是应用有意重启时想保留的状态,不做任何序列化工作,直接存储对象,能够保留最完整的内存结构。

完。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值