浅谈 Recycle 机制

这里的 Recycle 机制并不是指 Java 虚拟机中的垃圾回收机制,而是 Android 框架里十分常用的一种设计模式。基本思想很简单,当一个对象不再使用时把它储藏起来,不让虚拟机回收,需要的时候再从仓库里拿出来重新使用,这就避免了对象被回收后再重分配的过程。对于在应用的生命周期内(或者在循环中)需要频繁创建的对象来说这个机制特别实用,可以显著减少对象创建的次数,从而减少 GC 的运行时间。运用得当便可改善应用的性能。唯一的不足只是需要手动为废弃对象调用 recycle 方法。

如何实现?首先,我们需要一个仓库用于存放暂时不用的对象;需要新对象的时候我们不能使用 new 来分配一个新对象,所以还需要一个方法 obtain 来从仓库里获取对象;最后,便是 recycle 方法了,用于回收不再使用的对象。一个简单的实现如下所示,技术细节在注释里解释:

/**
 * Created by Tiou on 2014/7/15.
 * 一个实现 Recycle 机制的对象
 */
public class Data {
    /**
     * 对象池,就是上文所提到的对象仓库,用于暂时存放不用的对象。
     * 用链表来实现对象池结构,直观,高效,易用。
     * sPool 便是指向链表头部的引用
     */
    private static Data sPool;
    /**
     * 指向链表中的下一个元素,当 next 为 null 时表示已到达链表末端
     */
    private Data next;

    /**
     * 隐藏构造函数,避免对象被 new 关键字创建
     */
    private Data(){}

    /**
     * 从池里获取一个新对象,没有的话则返回一个新的实例
     * @return 可用的新对象
     */
    public static Data obtain(){
        if(sPool!=null){ // 池中有可用的对象
            // 对于对象池来说顺序并没有关系
            // 这里取链表的第一个对象,主要是因为方便
            Data data = sPool;
            sPool = sPool.next;
            data.next = null;
            return data;
        }
        return new Data();
    }

    /**
     * 将当前对象回收,一旦对象被回收,便不能再使用,代码中也不应存有任何到该对象的引用
     */
    public void recycle(){
        clear(); //清理对象
        // 把当前对象作为首元素按入链表中
        next = sPool;
        sPool = this;
    }

    /**
     * 重置对象到刚初始化时的状态
     */
    private void clear(){

    }
}

为什么说这是一个简单实现呢?因为这是一个不完善的实现。考虑一个场景,如果一次性 obtain 十万个对象,用完后再全部 recycle,以后每次可能规模就降到几十个,那这十万个对象的绝大多数就会停留在池里,既不会被用到,也不能被虚拟机回收,占用应用大量的内存。这是个十分糟糕的例子,但意思大致还是说得明白,池的容量不能无限大,不然便有内存泄漏的隐患。至于这个对象数量的上限该如何设置,这里并没有一个规定死的值,可灵活设置,简单说这是一个空间换时间的策略,可根据对象占用的空间,及应用具体需要用到的规模来设置一个合理值。

另外,obtainrecycle 这两个方法都不是原子操作,在多线程的应用场景下,可能会发生各种奇怪的问题。所以我们还要为这两个方法加锁,保证其是多线程安全的。

最终的效果在这个 gist.

至于具体的例子,这个机制在 Android 框架中实在是太常见了,都不用自己再造出什么例子。只要用过 Message, TypedArray, Parcel,甚至各种 Event 类,等等…都或多或少接触过 recycle 这个方法。这个机制如此常用,以至于 Android 还在 support lib v4 里提供一个 Pool 工具包。

大家可能会奇怪了:「我常常用 Message,也没调用过recycle,也不见得会怎样。」实际上不调用 recycle 确实不会在怎样,因为 Looper 已经帮我们处理好手尾了,只要记得发送过的 Message 不能再用便可以。那自己手动回收会怎样?因为 Looper 在调用 msg.recycle() 前并没有作检查,Message 的对象池来者不拒,不会对进入池中的对象是否已存在进行检查,一旦同一个 Message 被回收两次,便会发生糟糕的结果,对象池将会被破坏,变成一条循环链表,该 Message 所在节点后面的元素将会被孤立,虽不会造成内存泄漏,但将影响虚拟机回收的回收效率。更糟糕的是,Message 的 Recycle 机制将会失效。

大家可以试试下面的代码:

Message msg = Message.obtain();
System.out.println("First obtain:"+System.identityHashCode(msg));
msg.recycle();
msg.recycle();
System.out.println("After recycle twice");
System.out.println("Second obtain:"+System.identityHashCode(Message.obtain()));
System.out.println("Third obtain:"+System.identityHashCode(Message.obtain()));
System.out.println("Fourth obtain:"+System.identityHashCode(Message.obtain()));

输出结果:

First obtain:1122593040
After recycle twice
Second obtain:1122593040
Third obtain:1122593040
Fourth obtain:1122846456

可以看到,连续两次 obtain 返回相同的对象,一旦出现这样的 Bug,要找问题在哪出来绝对是很困难的,所以,绝对不要手动调用 Message#recycle. 不得不怀疑 Android 把这个方法声明为 public 是否合理的。

顺便再提一下,Event 类的回收机制也是由系统控制的,所以不要在监听器触发的方法外保留对监听事件的引用。

本文依据 Android Programming - Pushing the Limits 而作,该书同样犯了手动调用 Message#recycle 的错误,但仍不失为一本值得一读的技术书,值得推荐。

原文链接:https://dourok.info/2014/07/15/quick-overview-of-recycling-pattern-in-android/

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

推荐阅读更多精彩内容