Android Training精要(六)BitmapオブジェクトのOOM発生を防止する方法

11030 ワード

1.AsyncTaskを使用してbitmapピクチャを非同期でロードしてOOMを回避する:
 class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    private final WeakReference<ImageView> imageViewReference; 
    private int data = 0; 
    public BitmapWorkerTask(ImageView imageView) { 
        // Use a WeakReference to ensure the ImageView can be garbage collected 
        imageViewReference = new WeakReference<ImageView>(imageView); 
    } 
 
    // Decode image in background. 
    @Override 
    protected Bitmap doInBackground(Integer... params) { 
        data = params[0]; 
        return decodeSampledBitmapFromResource(getResources(), data, 100, 100)); 
    } 
 
    // Once complete, see if ImageView is still around and set bitmap. 
    @Override 
    protected void onPostExecute(Bitmap bitmap) { 
        if (imageViewReference != null && bitmap != null) { 
            final ImageView imageView = imageViewReference.get(); 
            if (imageView != null) { 
                imageView.setImageBitmap(bitmap); 
            } 
        } 
    } 
}

decodeSampledBitmapFromResourceメソッドの具体的な実装については、前編を参照してください. 
Android Training精要(五)Bitmapオブジェクトの実際のサイズとタイプを読み取る
複数のTaskがtaskをどのように処理するかという問題がある場合:imageviewコンポーネントにAsyncTaskの参照を保存し、taskが完了したかどうかを判断するために使用されます.
static class AsyncDrawable extends BitmapDrawable { 
    private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference; 
 
    public AsyncDrawable(Resources res, Bitmap bitmap, 
            BitmapWorkerTask bitmapWorkerTask) { 
        super(res, bitmap); 
        bitmapWorkerTaskReference = 
            new WeakReference<BitmapWorkerTask>(bitmapWorkerTask); 
    } 
 
    public BitmapWorkerTask getBitmapWorkerTask() { 
        return bitmapWorkerTaskReference.get(); 
    } 
}

taskを実行する前に、imageViewをバインドしてbitmapをロードするためのAsyncDrawableを作成します.
のtask
public void loadBitmap(int resId, ImageView imageView) { 
    if (cancelPotentialWork(resId, imageView)) { 
        final BitmapWorkerTask task = new BitmapWorkerTask(imageView); 
        final AsyncDrawable asyncDrawable = 
                new AsyncDrawable(getResources(), mPlaceHolderBitmap, task); 
        imageView.setImageDrawable(asyncDrawable); 
        task.execute(resId); 
    } 
}

public static boolean cancelPotentialWork(int data, ImageView imageView) { 
    final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView); 
 
    if (bitmapWorkerTask != null) { 
        final int bitmapData = bitmapWorkerTask.data; 
        if (bitmapData != data) { 
            // Cancel previous task 
            bitmapWorkerTask.cancel(true); 
        } else { 
            // The same work is already in progress 
            return false; 
        } 
    } 
    // No task associated with the ImageView, or an existing task was cancelled 
    return true; 
}

private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) { 
   if (imageView != null) { 
       final Drawable drawable = imageView.getDrawable(); 
       if (drawable instanceof AsyncDrawable) { 
           final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable; 
           return asyncDrawable.getBitmapWorkerTask(); 
       } 
    } 
    return null; 
}

最後にonPostExecuteメソッドでtaskがキャンセルされたか、または同じtaskではないかを判断する
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    ... 
    @Override 
    protected void onPostExecute(Bitmap bitmap) { 
        if (isCancelled()) { 
            bitmap = null; 
        } 
 
        if (imageViewReference != null && bitmap != null) { 
            final ImageView imageView = imageViewReference.get(); 
            final BitmapWorkerTask bitmapWorkerTask = 
                    getBitmapWorkerTask(imageView); 
            if (this == bitmapWorkerTask && imageView != null) { 
                imageView.setImageBitmap(bitmap); 
            } 
        } 
    } 
}

2.LruCacheを使用してBitmapキャッシュ(メモリキャッシュ)を構築する
private LruCache<String, Bitmap> mMemoryCache; 
 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
    ... 
    // Get max available VM memory, exceeding this amount will throw an 
    // OutOfMemory exception. Stored in kilobytes as LruCache takes an 
    // int in its constructor. 
    final int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); 
 
    // Use 1/8th of the available memory for this memory cache. 
    final int cacheSize = maxMemory / 8; 
 
    mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
        @Override 
        protected int sizeOf(String key, Bitmap bitmap) { 
            // The cache size will be measured in kilobytes rather than 
            // number of items. 
            return bitmap.getByteCount() / 1024; 
        } 
    }; 
    ... 
} 
 
public void addBitmapToMemoryCache(String key, Bitmap bitmap) { 
    if (getBitmapFromMemCache(key) == null) { 
        mMemoryCache.put(key, bitmap); 
    } 
} 
 
public Bitmap getBitmapFromMemCache(String key) { 
    return mMemoryCache.get(key); 
}

      bitmap.
public void loadBitmap(int resId, ImageView imageView) { 
    final String imageKey = String.valueOf(resId); 
 
    final Bitmap bitmap = getBitmapFromMemCache(imageKey); 
    if (bitmap != null) { 
        mImageView.setImageBitmap(bitmap); 
    } else { 
        mImageView.setImageResource(R.drawable.image_placeholder); 
        BitmapWorkerTask task = new BitmapWorkerTask(mImageView); 
        task.execute(resId); 
    } 
}

前の
BitmapWorkerTask
追加を変更する必要があります
bitmap
キャッシュへ
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    ... 
    // Decode image in background. 
    @Override 
    protected Bitmap doInBackground(Integer... params) { 
        final Bitmap bitmap = decodeSampledBitmapFromResource( 
                getResources(), params[0], 100, 100)); 
        addBitmapToMemoryCache(String.valueOf(params[0]), bitmap); 
        return bitmap; 
    } 
}

3.
HDDキャッシュの使用
Use a Disk Cache
:
ロードされた画像が多すぎたり、appがシステムアプリケーションによって中断されたりすると、メモリが消去されて再ロードされます.ハードディスク(HDD)上のキャッシュを使用する必要があります.読み書きハードディスク(HDD)のキャッシュは時間がかかります.スレッド処理が必要です.
private DiskLruCache mDiskLruCache; 
private final Object mDiskCacheLock = new Object(); 
private boolean mDiskCacheStarting = true; 
private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MB 
private static final String DISK_CACHE_SUBDIR = "thumbnails"; 
 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
    ... 
    // Initialize memory cache 
    ... 
    // Initialize disk cache on background thread 
    File cacheDir = getDiskCacheDir(this, DISK_CACHE_SUBDIR); 
    new InitDiskCacheTask().execute(cacheDir); 
    ... 
} 
 
class InitDiskCacheTask extends AsyncTask<File, Void, Void> { 
    @Override 
    protected Void doInBackground(File... params) { 
        synchronized (mDiskCacheLock) { 
            File cacheDir = params[0]; 
            mDiskLruCache = DiskLruCache.open(cacheDir, DISK_CACHE_SIZE); 
            mDiskCacheStarting = false; // Finished initialization 
            mDiskCacheLock.notifyAll(); // Wake any waiting threads 
        } 
        return null; 
    } 
} 
 
class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> { 
    ... 
    // Decode image in background. 
    @Override 
    protected Bitmap doInBackground(Integer... params) { 
        final String imageKey = String.valueOf(params[0]); 
 
        // Check disk cache in background thread 
        Bitmap bitmap = getBitmapFromDiskCache(imageKey); 
 
        if (bitmap == null) { // Not found in disk cache 
            // Process as normal 
            final Bitmap bitmap = decodeSampledBitmapFromResource( 
                    getResources(), params[0], 100, 100)); 
        } 
 
        // Add final bitmap to caches 
        addBitmapToCache(imageKey, bitmap); 
 
        return bitmap; 
    } 
    ... 
} 
 

public void addBitmapToCache(String key, Bitmap bitmap) { 
    // Add to memory cache as before 
    if (getBitmapFromMemCache(key) == null) { 
        mMemoryCache.put(key, bitmap); 
    } 
    // Also add to disk cache 
    synchronized (mDiskCacheLock) { 
        if (mDiskLruCache != null && mDiskLruCache.get(key) == null) { 
            mDiskLruCache.put(key, bitmap); 
        } 
    } 
} 
 
public Bitmap getBitmapFromDiskCache(String key) { 
    synchronized (mDiskCacheLock) { 
        // Wait while disk cache is started from background thread 
        while (mDiskCacheStarting) { 
            try { 
                mDiskCacheLock.wait(); 
            } catch (InterruptedException e) {} 
        } 
        if (mDiskLruCache != null) { 
            return mDiskLruCache.get(key); 
        } 
    } 
    return null; 
} 
 
// Creates a unique subdirectory of the designated app cache directory. Tries to use external 
// but if not mounted, falls back on internal storage. 
public static File getDiskCacheDir(Context context, String uniqueName) { 
    // Check if media is mounted or storage is built-in, if so, try and use external cache dir 
    // otherwise use internal cache dir 
    final String cachePath = 
            Environment.MEDIA_MOUNTED.equals(Environment.getExternalStorageState()) || !isExternalStorageRemovable() ? getExternalCacheDir(context).getPath() :  context.getCacheDir().getPath(); 
    return new File(cachePath + File.separator + uniqueName); 
}

4.MemoryCacheとFragmentの結合
private LruCache<String, Bitmap> mMemoryCache; 
@Override 
protected void onCreate(Bundle savedInstanceState) { 
    RetainFragment retainFragment = 
            RetainFragment.findOrCreateRetainFragment(getFragmentManager()); 
    mMemoryCache = retainFragment.mRetainedCache; 
    if (mMemoryCache == null) { 
        mMemoryCache = new LruCache<String, Bitmap>(cacheSize) { 
         // Initialize cache here as usual 
        } 
        retainFragment.mRetainedCache = mMemoryCache; 
    } 
  
} 
 
class RetainFragment extends Fragment { 
    private static final String TAG = "RetainFragment"; 
    public LruCache<String, Bitmap> mRetainedCache; 
    public RetainFragment() {} 
 
    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) { 
        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG); 
        if (fragment == null) { 
            fragment = new RetainFragment(); 
            fm.beginTransaction().add(fragment, TAG).commit(); 
        } 
        return fragment; 
    } 
 
    @Override 
    public void onCreate(Bundle savedInstanceState) { 
        super.onCreate(savedInstanceState); 
        setRetainInstance(true); 
    } 
}