fresco 拾遺

一羡洛、內(nèi)存管理的獨到之處

在Fresco中煌恢,提供了PlatformDecoder這個接口用于處理bitmap的decode過程,下面包括了該接口的具體實現(xiàn)類:

類關(guān)系圖

在 ImagePipelineFactory 類中是根據(jù)不同的平臺去 build 不同的 decoder:

  /**
   * Provide the implementation of the PlatformDecoder for the current platform using the
   * provided PoolFactory
   *
   * @param poolFactory The PoolFactory
   * @return The PlatformDecoder implementation
   */
  public static PlatformDecoder buildPlatformDecoder(
      PoolFactory poolFactory,
      boolean directWebpDirectDecodingEnabled) {
    // 5.0及以上铛只,返回 ArtDecoder
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
      int maxNumThreads = poolFactory.getFlexByteArrayPoolMaxNumThreads();
      return new ArtDecoder(
          poolFactory.getBitmapPool(),
          maxNumThreads,
          new Pools.SynchronizedPool<>(maxNumThreads));
    } else {
      // directWebpDirectDecodingEnabled 為 true 且 小于4.4
      if (directWebpDirectDecodingEnabled
          && Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) {
        return new GingerbreadPurgeableDecoder();
      } else {// 其余情況跟磨,都使用 KitKatPurgeableDecoder
        return new KitKatPurgeableDecoder(poolFactory.getFlexByteArrayPool());
      }
    }
  }
/**
 * Bitmap decoder for ART VM (Lollipop and up).
 */
@TargetApi(Build.VERSION_CODES.LOLLIPOP)
@ThreadSafe
public class ArtDecoder implements PlatformDecoder
/**
 * Bitmap decoder (Gingerbread to Jelly Bean). API 9(2.3) —> API 16(4.1)
 * <p/>
 * <p>This copies incoming encoded bytes into a MemoryFile, and then decodes them using a file
 * descriptor, thus avoiding using any Java memory at all. This technique only works in JellyBean
 * and below.
 * decode 前會先把原始數(shù)據(jù)(encoded data)copy 到 MemoryFile 中去杆怕,借助 MemoryFile 把 encoded data 
 * 拷貝到 ashmem 中去族购,盡量避免在 Java Heap 上分配內(nèi)存而造成頻繁 GC 的問題
 */
public class GingerbreadPurgeableDecoder extends DalvikPurgeableDecoder
/**
 * Bitmap Decoder implementation for KitKat
 *
 * <p>The MemoryFile trick used in GingerbreadPurgeableDecoder does not work in KitKat. Here, we
 * instead use Java memory to store the encoded images, but make use of a pool to minimize
 * allocations. We cannot decode from a stream, as that does not support purgeable decodes.
 */
@TargetApi(Build.VERSION_CODES.KITKAT)
@ThreadSafe
public class KitKatPurgeableDecoder extends DalvikPurgeableDecoder
  • ArtDecoder 中通過 BitmapOptions 的 inBitmap 和 inTempStorage 去優(yōu)化內(nèi)存使用(inBitmap 是由 BitmapPool 去分配內(nèi)存,inTempStorage 是由SynchronizedPool 分配內(nèi)存陵珍,都是用緩存池的方式分配和回收內(nèi)存寝杖,做到對這些區(qū)域的內(nèi)存可管理,減少各個不同地方自行分配內(nèi)存)互纯,最終去調(diào)用 BitmapFactory.decodeStream() 方法瑟幕。
  • DalvikPurgeableDecoder 中設(shè)置 inPurgeable 和 inInputShareable 為 true,從注釋看伟姐,inPurgeable 能使 bitmap 的內(nèi)存分配到 ashmem 上收苏;對于通過 filedescriptor 去decode的方式,還要設(shè)置 inInputShareable 為true愤兵,只能夠使內(nèi)存分配到 ashmem 上鹿霸。
  private static BitmapFactory.Options getBitmapFactoryOptions(
      int sampleSize,
      Bitmap.Config bitmapConfig) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    options.inDither = true; // known to improve picture quality at low cost
    options.inPreferredConfig = bitmapConfig;
    // Decode the image into a 'purgeable' bitmap that lives on the ashmem heap
    options.inPurgeable = true;
    // Enable copy of of bitmap to enable purgeable decoding by filedescriptor
    options.inInputShareable = true;
    // Sample size should ONLY be different than 1 when downsampling is enabled in the pipeline
    options.inSampleSize = sampleSize;
    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB) {
      options.inMutable = true;  // no known perf difference; allows postprocessing to work
    }
    return options;
  }

為了理解Facebook到底做了什么工作,在此之前我們需要了解在Android可以使用的堆內(nèi)存之間的區(qū)別秆乳。

  1. Java Heap(Dalvik Heap)懦鼠,這部分的內(nèi)存區(qū)域是由Dalvik虛擬機管理,通過Java中 new 關(guān)鍵字來申請一塊新內(nèi)存屹堰。這塊區(qū)域的內(nèi)存是由GC直接管理肛冶,能夠自動回收內(nèi)存。這塊內(nèi)存的大小會受到系統(tǒng)限制扯键,當內(nèi)存超過APP最大可用內(nèi)存時會OOM睦袖。
  2. Native Heap,這部分內(nèi)存區(qū)域是在C++中申請的荣刑,它不受限于APP的最大可用內(nèi)存限制馅笙,而只是受限于設(shè)備的物理可用內(nèi)存限制伦乔。它的缺點在于沒有自動回收機制,只能通過C++語法來釋放申請的內(nèi)存董习。
  3. Ashmem(Android匿名共享內(nèi)存)烈和,這部分內(nèi)存類似于Native內(nèi)存區(qū),但是它是受Android系統(tǒng)底層管理的皿淋,當Android系統(tǒng)內(nèi)存不足時招刹,會回收Ashmem區(qū)域中狀態(tài)是 unpin 的對象內(nèi)存塊,如果不希望對象被回收窝趣,可以通過 pin 來保護一個對象疯暑。

Ashmem不能被Java應(yīng)用直接處理,但是也有一些例外高帖,圖片就是其中之一缰儿。當你創(chuàng)建一張沒有經(jīng)過壓縮的Bitmap的時候畦粮,Android的API允許你指定是否是可清除的散址。

BitmapFactory.Options = new BitmapFactory.Options();
options.inPurgeable = true;
Bitmap bitmap = BitmapFactory.decodeByteArray(jpeg, 0, jpeg.length, options);

上面的代碼便是通過設(shè)置 inPurgeable 為 true 來創(chuàng)建一個 Purgeable Bitmap ,這樣decode出來的bitmap是在 Ashmem 內(nèi)存中宣赔,GC無法自動回收它预麸。當該Bitmap在被使用時會被 pin 住,使用完之后就 unpin 儒将,這樣系統(tǒng)就可以在將來某一時間釋放這部分內(nèi)存吏祸。

如果一個 unpinned 的 bitmap 在之后又要被使用,系統(tǒng)會運行時又將它重新 decode钩蚊,但是這個 decode 操作是發(fā)生在UI線程中的有可能會造成掉幀現(xiàn)象贡翘,因此該做法已經(jīng)被 Google 廢棄掉,轉(zhuǎn)為鼓勵使用 inBitmap 來告知
bitmap 解碼器去嘗試使用已經(jīng)存在的內(nèi)存區(qū)域砰逻,新解碼的 bitmap 會嘗試去使用之前那張 bitmap 在 heap 中所占據(jù)的 pixel data 內(nèi)存區(qū)域鸣驱,而不是去問內(nèi)存重新申請一塊區(qū)域來存放 bitmap。利用這種特性蝠咆,即使是上千張的圖片踊东,也只會僅僅只需要占用屏幕所能夠顯示的圖片數(shù)量的內(nèi)存大小。

這聽起來很完美刚操,但是我們來看 inPurgeable:

        /**
         * @deprecated As of {@link android.os.Build.VERSION_CODES#LOLLIPOP}, this is
         * ignored.
         *
         * In {@link android.os.Build.VERSION_CODES#KITKAT} and below, if this
         * is set to true, then the resulting bitmap will allocate its
         * pixels such that they can be purged if the system needs to reclaim
         * memory. In that instance, when the pixels need to be accessed again
         * (e.g. the bitmap is drawn, getPixels() is called), they will be
         * automatically re-decoded.
         *
         * <p>For the re-decode to happen, the bitmap must have access to the
         * encoded data, either by sharing a reference to the input
         * or by making a copy of it. This distinction is controlled by
         * inInputShareable. If this is true, then the bitmap may keep a shallow
         * reference to the input. If this is false, then the bitmap will
         * explicitly make a copy of the input data, and keep that. Even if
         * sharing is allowed, the implementation may still decide to make a
         * deep copy of the input data.</p>
         *
         * <p>While inPurgeable can help avoid big Dalvik heap allocations (from
         * API level 11 onward), it sacrifices performance predictability since any
         * image that the view system tries to draw may incur a decode delay which
         * can lead to dropped frames. Therefore, most apps should avoid using
         * inPurgeable to allow for a fast and fluid UI. To minimize Dalvik heap
         * allocations use the {@link #inBitmap} flag instead.</p>
         *
         * <p class="note"><strong>Note:</strong> This flag is ignored when used
         * with {@link #decodeResource(Resources, int,
         * android.graphics.BitmapFactory.Options)} or {@link #decodeFile(String,
         * android.graphics.BitmapFactory.Options)}.</p>
         */
        @Deprecated
        public boolean inPurgeable;
  1. 在 LOLLIPOP(API 21—Android 5.0)時被棄用闸翅,fresco 5.0使用 inBitmap。
  2. 在 KITKAT 及之前使用設(shè)置為 true菊霜,當系統(tǒng)需要回收內(nèi)存時坚冀,bitmap 的 pixels 可以被清除,當 pixels 需要被重新訪問的時候(例如 bitmap draw或者調(diào)用 getPixels() 的時候)鉴逞,它們又可以重新被 decode 出來记某。
  3. 需要重新 decode 的話联喘,自然需要 encoded data,encoded data 可能來源于對原始那份 encoded data 的引用辙纬,或者是對原始數(shù)據(jù)的拷貝豁遭。具體是引用或者拷貝,就是根據(jù) inInputShareable 來決定的贺拣,如果是 true 那就是引用蓖谢,不然就是 deep copy磅崭,但是 inInputShareable 即使設(shè)置為 true集漾,不同的實現(xiàn)也可能是直接進行 deep copy。
  4. inPurgeable 能夠避免大的 Dalvik heap 內(nèi)存分配(從 API 11—Android
    3.0 開始)谓松,然而會犧牲 UI 的流暢性涡匀,因為重新 decode 的過程在 UI 線程中進行盯腌,會導致掉幀問題,因此不建議使用 inPurgeable陨瘩,推薦使用 inBitmap 特性腕够。
  5. 然而 inBitmap 這個特性直到 Android 3.0 之后才被支持,在 Android 4.4 之前重用的 bitmap 大小必須是一致的且必須是 jpeg 或 png 格式舌劳,從SDK 19(Android 4.4)開始帚湘,新申請的bitmap大小必須小于或者等于已經(jīng)賦值過的bitmap大小,新申請的bitmap與舊的bitmap必須有相同的解碼格式甚淡。

那么如何解決 drop frames 導致的卡頓問題大诸?

在 DalvikPurgeableDecoder 中可以看到,每次 decode 之后調(diào)用了 pinBitmap 方法贯卦。

  /**
   * Creates a bitmap from encoded bytes.
   *
   * @param encodedImage the encoded image with reference to the encoded bytes
   * @param bitmapConfig the {@link android.graphics.Bitmap.Config}
   * used to create the decoded Bitmap
   * @return the bitmap
   * @throws TooManyBitmapsException if the pool is full
   * @throws java.lang.OutOfMemoryError if the Bitmap cannot be allocated
   */
  @Override
  public CloseableReference<Bitmap> decodeFromEncodedImage(
      final EncodedImage encodedImage,
      Bitmap.Config bitmapConfig) {
    BitmapFactory.Options options = getBitmapFactoryOptions(
        encodedImage.getSampleSize(),
        bitmapConfig);
    CloseableReference<PooledByteBuffer> bytesRef = encodedImage.getByteBufferRef();
    Preconditions.checkNotNull(bytesRef);
    try {
      Bitmap bitmap = decodeByteArrayAsPurgeable(bytesRef, options);
      return pinBitmap(bitmap);
    } finally {
      CloseableReference.closeSafely(bytesRef);
    }
  }

為了讓 inPurgeable 的 bitmap 不被自動 unpinned 资柔,可以通過使用 jni 函數(shù) AndroidBitmap_lockPixels() 函數(shù)來強制 pin bitmap ,這樣我們就可以在 bitmap 被使用時不會被系統(tǒng)自動 unpinned 撵割,從而也就避免了 unpinned 的 bitmap 在重新被使用時又會被重新 decode 而引起的掉幀問題贿堰。

這做后,F(xiàn)resco 需要自己去管理這塊內(nèi)存區(qū)域睁枕,保證當這個 Bitmap 不再使用時官边,Ashmem 的內(nèi)存空間能被 unpin,F(xiàn)resco 選擇在 Bitmap 離開屏幕可視范圍時候(onDetachWindow等時候)外遇,通過調(diào)用 bitmap.recycle() 方法去做 unpin注簿。

參考:
Fresco介紹 - 一個新的android圖片加載庫
談?wù)刦resco的bitmap內(nèi)存分配
Fresco內(nèi)存機制(Ashmem匿名共享內(nèi)存)

?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末,一起剝皮案震驚了整個濱河市跳仿,隨后出現(xiàn)的幾起案子诡渴,更是在濱河造成了極大的恐慌,老刑警劉巖,帶你破解...
    沈念sama閱讀 216,591評論 6 501
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件妄辩,死亡現(xiàn)場離奇詭異惑灵,居然都是意外死亡,警方通過查閱死者的電腦和手機眼耀,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 92,448評論 3 392
  • 文/潘曉璐 我一進店門英支,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人哮伟,你說我怎么就攤上這事干花。” “怎么了楞黄?”我有些...
    開封第一講書人閱讀 162,823評論 0 353
  • 文/不壞的土叔 我叫張陵池凄,是天一觀的道長。 經(jīng)常有香客問我鬼廓,道長肿仑,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 58,204評論 1 292
  • 正文 為了忘掉前任碎税,我火速辦了婚禮尤慰,結(jié)果婚禮上,老公的妹妹穿的比我還像新娘蚣录。我一直安慰自己割择,他們只是感情好,可當我...
    茶點故事閱讀 67,228評論 6 388
  • 文/花漫 我一把揭開白布萎河。 她就那樣靜靜地躺著,像睡著了一般蕉饼。 火紅的嫁衣襯著肌膚如雪虐杯。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,190評論 1 299
  • 那天昧港,我揣著相機與錄音擎椰,去河邊找鬼。 笑死创肥,一個胖子當著我的面吹牛达舒,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播叹侄,決...
    沈念sama閱讀 40,078評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼巩搏,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了趾代?” 一聲冷哼從身側(cè)響起贯底,我...
    開封第一講書人閱讀 38,923評論 0 274
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎撒强,沒想到半個月后禽捆,有當?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體笙什,經(jīng)...
    沈念sama閱讀 45,334評論 1 310
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點故事閱讀 37,550評論 2 333
  • 正文 我和宋清朗相戀三年胚想,在試婚紗的時候發(fā)現(xiàn)自己被綠了琐凭。 大學時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 39,727評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡浊服,死狀恐怖淘正,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情臼闻,我是刑警寧澤鸿吆,帶...
    沈念sama閱讀 35,428評論 5 343
  • 正文 年R本政府宣布,位于F島的核電站述呐,受9級特大地震影響惩淳,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜乓搬,卻給世界環(huán)境...
    茶點故事閱讀 41,022評論 3 326
  • 文/蒙蒙 一思犁、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧进肯,春花似錦激蹲、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,672評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至环形,卻和暖如春策泣,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背抬吟。 一陣腳步聲響...
    開封第一講書人閱讀 32,826評論 1 269
  • 我被黑心中介騙來泰國打工萨咕, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人火本。 一個月前我還...
    沈念sama閱讀 47,734評論 2 368
  • 正文 我出身青樓危队,卻偏偏與公主長得像,于是被迫代替她去往敵國和親钙畔。 傳聞我的和親對象是個殘疾皇子茫陆,可洞房花燭夜當晚...
    茶點故事閱讀 44,619評論 2 354

推薦閱讀更多精彩內(nèi)容