/* * Copyright (C) 2014 The Android Open Source Project * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package com.android.tv.settings.widget; import android.graphics.Bitmap; import android.util.Log; import android.util.SparseArray; import java.lang.ref.SoftReference; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; import java.util.ArrayList; // FIXME: this class saves recycle bitmap as SoftReference, which is too vulnerable to // be garbage collected due to other part of application is re-allocating lots of // memory, we will lose all SoftReference in a GC run. We should maintain // certain amount of recycled bitmaps in memory, we may also need remove bitmap from LRUCache // if we are not able to get a recycled bitmap here. public class RecycleBitmapPool { private static final String TAG = "RecycleBitmapPool"; private static final boolean DEBUG = false; // allow reuse bitmap with larger bytes, set to 0 to disable different byte size // FIXME: wait b/10608305 to be fixed then turn back on private static final int LARGER_BITMAP_ALLOWED_REUSE = 0; private static final boolean LARGER_BITMAP_ALLOWED = LARGER_BITMAP_ALLOWED_REUSE > 0; private static Method sGetAllocationByteCount; static { try { // KLP or later sGetAllocationByteCount = Bitmap.class.getMethod("getAllocationByteCount"); } catch (NoSuchMethodException e) { } } private final SparseArray>> mRecycled8888 = new SparseArray>>(); public RecycleBitmapPool() { } public static int getSize(Bitmap bitmap) { if (sGetAllocationByteCount != null) { try { return (Integer) sGetAllocationByteCount.invoke(bitmap); } catch (IllegalArgumentException e) { Log.e(TAG, "getAllocationByteCount() failed", e); } catch (IllegalAccessException e) { Log.e(TAG, "getAllocationByteCount() failed", e); } catch (InvocationTargetException e) { Log.e(TAG, "getAllocationByteCount() failed", e); } sGetAllocationByteCount = null; } return bitmap.getByteCount(); } private static int getSize(int width, int height) { if (width >= 2048 || height >= 2048) { return 0; } return width * height * 4; } public void addRecycledBitmap(Bitmap bitmap) { if (bitmap.isRecycled()) { return; } Bitmap.Config config = bitmap.getConfig(); if (config != Bitmap.Config.ARGB_8888) { return; } int key = getSize(bitmap); if (key == 0) { return; } synchronized (mRecycled8888) { ArrayList> list = mRecycled8888.get(key); if (list == null) { list = new ArrayList>(); mRecycled8888.put(key, list); } list.add(new SoftReference(bitmap)); if (DEBUG) { Log.d(TAG, list.size() + " add bitmap " + bitmap.getWidth() + " " + bitmap.getHeight()); } } } public Bitmap getRecycledBitmap(int width, int height) { int key = getSize(width, height); if (key == 0) { return null; } synchronized (mRecycled8888) { // for the new version with getAllocationByteCount(), we allow larger size // to be reused for the bitmap, otherwise we just looks for same size Bitmap bitmap = getRecycledBitmap(mRecycled8888.get(key)); if (sGetAllocationByteCount == null || bitmap != null) { return bitmap; } if (LARGER_BITMAP_ALLOWED) { for (int i = 0, c = mRecycled8888.size(); i < c; i++) { int k = mRecycled8888.keyAt(i); if (k > key && k <= key * LARGER_BITMAP_ALLOWED_REUSE) { bitmap = getRecycledBitmap(mRecycled8888.valueAt(i)); if (bitmap != null) { return bitmap; } } } } } if (DEBUG) { Log.d(TAG, "not avaialbe for " + width + "," + height); } return null; } private static Bitmap getRecycledBitmap(ArrayList> list) { if (list != null && !list.isEmpty()) { while (!list.isEmpty()) { SoftReference ref = list.remove(list.size() - 1); Bitmap bitmap = ref.get(); if (bitmap != null && !bitmap.isRecycled()) { if (DEBUG) { Log.d(TAG, "reuse " + bitmap.getWidth() + " " + bitmap.getHeight()); } return bitmap; } else { if (DEBUG) { Log.d(TAG, " we lost SoftReference to bitmap"); } } } } return null; } }