對象池技術:如何正確創(chuàng)建對象

如需轉載請評論或簡信韩脏,并注明出處其馏,未經允許不得轉載

目錄

前言

內存優(yōu)化不僅要從防止內存泄露入手善延,也要注意頻繁GC卡頓贷痪,內存抖動以及不必要的內存開銷造成的內存需求過大或者內存泄露腮出。而避免內存無用開銷就必須理解Android開發(fā)中的一個重要原則 — 對象復用

有關內存抖動產生原因可以看:五分鐘了解內存抖動

Android源碼中的對象池技術—Message

Android系統(tǒng)基于消息機制帖鸦,Handle的使用方法我們已經很熟悉了,主要分為如下幾步:

  1. 在主線程創(chuàng)建handler對象

  2. 在子線程中將需要被發(fā)送的數(shù)據(jù)用Message對象進行封裝

  3. 在子線程中調用handle.sendMessage方法發(fā)送消息

  4. 在主線程中通過handler.handleMessage接收消息

那么問題來了胚嘲,當項目比較復雜頻繁用到handle發(fā)送消息的時候作儿,是不是意味著就會創(chuàng)建大量的Message對象呢?Message是用來存儲消息的一個對象馋劈,很多時候它的”生命周期“往往是比較短暫的攻锰,但是如果gc并不能把Message及時回收晾嘶,是不是造成了內存資源的浪費呢?即使gc在不斷地及時進行回收娶吞,也有可能造成一定程度的內存抖動垒迂,那么我們有什么比較好的辦法呢?

Message相關變量介紹

  • public int what:用于定義此Message屬于何種操作妒蛇,以便用不同方式處理message
  • public Object obj:用于定義此Message傳遞的信息數(shù)據(jù)机断,通過它傳遞信息
  • public int arg1:傳遞一些整型數(shù)據(jù)時使用
  • public int arg2:傳遞一些整型數(shù)據(jù)時使用

如果message只需要攜帶int類型信息,優(yōu)先使用Message.arg1和Message.arg2來傳遞信息绣夺,會比用Bundle更省內存

Message對象創(chuàng)建方式

  • Message msg = new Message();

最普通的方式是通過構造方法創(chuàng)建吏奸,這樣在大量發(fā)送消息時就會造成我們前面說過的內存問題,這里就不過多介紹了

  • Message msg = Message.obtain();
  • Message msg = Handler.obtainMessage();

比較推薦使用這種方式乐导,具體原理我們一點點來分析

    Message next;
    private static final Object sPoolSync = new Object();
    private static Message sPool;
    private static int sPoolSize = 0;
    private static final int MAX_POOL_SIZE = 50;
    
    /**
     * Return a new Message instance from the global pool. Allows us to
     * avoid allocating new objects in many cases.
     */
    public static Message obtain() {
        synchronized (sPoolSync) {
            if (sPool != null) {
                Message m = sPool;
                sPool = m.next;
                m.next = null;
                m.flags = 0; // clear in-use flag
                sPoolSize--;
                return m;
            }
        }
        return new Message();
    }

從注釋中我們基本就可以看出這個方法的作用苦丁,從全局pool中返回一個Message對象,這個方法可以讓我們在許多情況下避免分配新對象物臂。從代碼中我們大致可以看出Message是一種鏈表的結構旺拉,包含數(shù)據(jù)域和指針域

假設鏈表的初始狀態(tài)

Message m = sPool;

sPool = m.next;

m.next = null;

m.flags = 0;

清除 in-use 標識

sPoolSize--;

Message Pool 的計數(shù)自減

return m;

最終m指向的對象會從鏈表中取出并返回

上面這個過程是一個從Message Pool中取出Message的過程(出棧),那么Message pool中的message是如何增加的呢(入棧)棵磷?

我們來看Message回收的方法蛾狗,在釋放完Message內部的一些資源后,我們主要來關注下面這個同步代碼塊

/**
 * Recycles a Message that may be in-use.
 * Used internally by the MessageQueue and Looper when disposing of queued Messages.
 */
void recycleUnchecked() {
    // Mark the message as in use while it remains in the recycled object pool.
    // Clear out all other details.
    flags = FLAG_IN_USE;
    what = 0;
    arg1 = 0;
    arg2 = 0;
    obj = null;
    replyTo = null;
    sendingUid = -1;
    when = 0;
    target = null;
    callback = null;
    data = null;

    synchronized (sPoolSync) {
        if (sPoolSize < MAX_POOL_SIZE) {
            next = sPool;
            sPool = this;
            sPoolSize++;
        }
    }
}

假設鏈表的初始狀態(tài)

g

next = sPool

sPool = this;

從上面obtain()recycler()兩個過程可以看出仪媒,Message類內部維護了一個用單鏈表實現(xiàn)棧結構緩沖池沉桌,并使用obtain()方法和recycler()方法進行出棧和入棧操作

如何在實際項目中使用對象池技術

Android為我們提供了一個對象池類androidx.core.util.Pools(或android.support.v4.util.Pools),源碼如下

package androidx.core.util;


import androidx.annotation.NonNull;
import androidx.annotation.Nullable;

public final class Pools {

    /**
     * Interface for managing a pool of objects.
     *
     * @param <T> The pooled type.
     */
    public interface Pool<T> {

        /**
         * @return An instance from the pool if such, null otherwise.
         */
        @Nullable
        T acquire();

        /**
         * Release an instance to the pool.
         *
         * @param instance The instance to release.
         * @return Whether the instance was put in the pool.
         *
         * @throws IllegalStateException If the instance is already in the pool.
         */
        boolean release(@NonNull T instance);
    }

    private Pools() {
        /* do nothing - hiding constructor */
    }

    /**
     * Simple (non-synchronized) pool of objects.
     *
     * @param <T> The pooled type.
     */
    public static class SimplePool<T> implements Pool<T> {
        private final Object[] mPool;

        private int mPoolSize;

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SimplePool(int maxPoolSize) {
            if (maxPoolSize <= 0) {
                throw new IllegalArgumentException("The max pool size must be > 0");
            }
            mPool = new Object[maxPoolSize];
        }

        @Override
        @SuppressWarnings("unchecked")
        public T acquire() {
            if (mPoolSize > 0) {
                final int lastPooledIndex = mPoolSize - 1;
                T instance = (T) mPool[lastPooledIndex];
                mPool[lastPooledIndex] = null;
                mPoolSize--;
                return instance;
            }
            return null;
        }

        @Override
        public boolean release(@NonNull T instance) {
            if (isInPool(instance)) {
                throw new IllegalStateException("Already in the pool!");
            }
            if (mPoolSize < mPool.length) {
                mPool[mPoolSize] = instance;
                mPoolSize++;
                return true;
            }
            return false;
        }

        private boolean isInPool(@NonNull T instance) {
            for (int i = 0; i < mPoolSize; i++) {
                if (mPool[i] == instance) {
                    return true;
                }
            }
            return false;
        }
    }

    /**
     * Synchronized) pool of objects.
     *
     * @param <T> The pooled type.
     */
    public static class SynchronizedPool<T> extends SimplePool<T> {
        private final Object mLock = new Object();

        /**
         * Creates a new instance.
         *
         * @param maxPoolSize The max pool size.
         *
         * @throws IllegalArgumentException If the max pool size is less than zero.
         */
        public SynchronizedPool(int maxPoolSize) {
            super(maxPoolSize);
        }

        @Override
        public T acquire() {
            synchronized (mLock) {
                return super.acquire();
            }
        }

        @Override
        public boolean release(@NonNull T element) {
            synchronized (mLock) {
                return super.release(element);
            }
        }
    }
}

源碼很簡單算吩,主要有Pool接口留凭、SimplePoolSynchronizedPool組成

原理:使用了“懶加載”的思想偎巢。當SimplePool初始化時蔼夜,不會生成N個T類型的對象存放在對象池中。而是當每次外部調用release()時压昼,才把釋放的T類型對象存放在對象池中求冷。要先放入,才能取出來

官方給出的使用Demo如下

public class MyPooledClass {
  private static final SynchronizedPool<MyPooledClass> sPool =
          new SynchronizedPool<MyPooledClass>(10);

  public static MyPooledClass obtain() {
      MyPooledClass instance = sPool.acquire();
      return (instance != null) ? instance : new MyPooledClass();
  }

  public void recycle() {
      sPool.release(this);
  }
}

但是它也有弊端窍霞,對象池沒有最終銷毀機制匠题,應該需要的注意點:

  • recycle對象時注意清空對象的變量

  • 當對象池滿時,獲取對象便只能通過new對象獲取但金,所以應該注意對象大小設定

  • 當長時間不使用對象池時應該注意銷毀對象池

  public void destoryPool() {
      if (sPool != null) {
          sPool = null;
      }
  }

總結

在一些需要大量創(chuàng)建對象的場景下韭山,慎用new!可能引起的內存問題比你想象的還要更大,可以考慮一下使用對象池來進行優(yōu)化

最后編輯于
?著作權歸作者所有,轉載或內容合作請聯(lián)系作者
禁止轉載钱磅,如需轉載請通過簡信或評論聯(lián)系作者巩踏。
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市续搀,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌菠净,老刑警劉巖禁舷,帶你破解...
    沈念sama閱讀 206,602評論 6 481
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件,死亡現(xiàn)場離奇詭異毅往,居然都是意外死亡牵咙,警方通過查閱死者的電腦和手機,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 88,442評論 2 382
  • 文/潘曉璐 我一進店門攀唯,熙熙樓的掌柜王于貴愁眉苦臉地迎上來洁桌,“玉大人,你說我怎么就攤上這事侯嘀×砹瑁” “怎么了?”我有些...
    開封第一講書人閱讀 152,878評論 0 344
  • 文/不壞的土叔 我叫張陵戒幔,是天一觀的道長吠谢。 經常有香客問我,道長诗茎,這世上最難降的妖魔是什么工坊? 我笑而不...
    開封第一講書人閱讀 55,306評論 1 279
  • 正文 為了忘掉前任,我火速辦了婚禮敢订,結果婚禮上王污,老公的妹妹穿的比我還像新娘。我一直安慰自己楚午,他們只是感情好昭齐,可當我...
    茶點故事閱讀 64,330評論 5 373
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著醒叁,像睡著了一般司浪。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發(fā)上把沼,一...
    開封第一講書人閱讀 49,071評論 1 285
  • 那天啊易,我揣著相機與錄音,去河邊找鬼饮睬。 笑死租谈,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播割去,決...
    沈念sama閱讀 38,382評論 3 400
  • 文/蒼蘭香墨 我猛地睜開眼窟却,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了呻逆?” 一聲冷哼從身側響起夸赫,我...
    開封第一講書人閱讀 37,006評論 0 259
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎咖城,沒想到半個月后茬腿,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經...
    沈念sama閱讀 43,512評論 1 300
  • 正文 獨居荒郊野嶺守林人離奇死亡宜雀,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 35,965評論 2 325
  • 正文 我和宋清朗相戀三年切平,在試婚紗的時候發(fā)現(xiàn)自己被綠了。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片辐董。...
    茶點故事閱讀 38,094評論 1 333
  • 序言:一個原本活蹦亂跳的男人離奇死亡悴品,死狀恐怖,靈堂內的尸體忽然破棺而出简烘,到底是詐尸還是另有隱情苔严,我是刑警寧澤,帶...
    沈念sama閱讀 33,732評論 4 323
  • 正文 年R本政府宣布夸研,位于F島的核電站邦蜜,受9級特大地震影響,放射性物質發(fā)生泄漏亥至。R本人自食惡果不足惜悼沈,卻給世界環(huán)境...
    茶點故事閱讀 39,283評論 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望姐扮。 院中可真熱鬧絮供,春花似錦、人聲如沸茶敏。這莊子的主人今日做“春日...
    開封第一講書人閱讀 30,286評論 0 19
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽惊搏。三九已至贮乳,卻和暖如春,著一層夾襖步出監(jiān)牢的瞬間恬惯,已是汗流浹背向拆。 一陣腳步聲響...
    開封第一講書人閱讀 31,512評論 1 262
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留酪耳,地道東北人浓恳。 一個月前我還...
    沈念sama閱讀 45,536評論 2 354
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親颈将。 傳聞我的和親對象是個殘疾皇子梢夯,可洞房花燭夜當晚...
    茶點故事閱讀 42,828評論 2 345