Android bitmap image Optimization

Source: Internet
Author: User

Graphical images are inevitably used in Android Application Development, which generates bitmap objects. If the bitmap object is not properly processed during development, the out of memory (OOM) exception is easily generated. The following are some notes for using bitmap objects:

  • An android application can only use 16 MB of memory at most.
    Compatibility definition document (CDD) section 3.7 describes the size allocated by devices with different screen resolutions and density in the VM.

Screen Size Screen Density Application memory
Small/normal/large Ldpi/mdpi 16 MB
Small/normal/large Tvdpi/hdpi 32 MB
Small/normal/large Xhdpi 64 MB
Xlarge Mdpi 32 MB
Xlarge Tvdpi/hdpi 64 MB
Xlarge Xhdpi 128 MB

  • Bitmap objects occupy memory, especially some photos. For example, if a Google Nexus image with a resolution of 2592x1936 is about 5 MB, The argb_8888 color format is used (this format is used by default after 2.3) loading this image requires 2592 MB of memory (1936*4
    Bytes.
  • In Android, many controls, such as listview, gridview, and viewpaper, usually contain many images. In particular, a large number of images may be loaded during fast sliding. Therefore, image processing is particularly important.

The following describes how to optimize the display of Bitmap in four directions:
  • Optimize large images-pay attention to Bitmap Processing techniques so that they do not exceed the maximum memory limit
Normally, our UI does not need exquisite images. For example, when we use gallery to display a photo taken by a camera, the resolution of your device is usually less than the resolution of the photo. The bitmapfactory class provides several image decoding methods (decodebytearray (), decodefile (), decoderesource (). You can specify the decoding options through bitmapfactory. Options. When the injustdecodebounds attribute is set to true, the bitmap object is not generated, but the decoding information of the image (image resolution and type: outwidth, outheight, outmimetype) is returned, and the scaling value can be calculated through the resolution, then set injustdecodebounds to false and pass in the zoom value to zoom in the image. It is worth noting that injustdecodebounds may be smaller than 0, and you need to make a judgment.
BitmapFactory.Options options = new BitmapFactory.Options();options.inJustDecodeBounds = true;BitmapFactory.decodeResource(getResources(), R.id.myimage, options);int imageHeight = options.outHeight;int imageWidth = options.outWidth;String imageType = options.outMimeType;

Now we know the image density. Setting the insamplesize value in bitmapfactory. Options can reduce the image size. For example, if insamplesize is set to 4, an image with a length of 1/4x1/4x1/16 X is generated. When insamplesize
<1 indicates 1 by default. The system provides a calculateinsamplesize () method to calculate this value:

public static int calculateInSampleSize(            BitmapFactory.Options options, int reqWidth, int reqHeight) {    // Raw height and width of image    final int height = options.outHeight;    final int width = options.outWidth;    int inSampleSize = 1;    if (height > reqHeight || width > reqWidth) {        if (width > height) {            inSampleSize = Math.round((float)height / (float)reqHeight);        } else {            inSampleSize = Math.round((float)width / (float)reqWidth);        }    }    return inSampleSize;}

Create a complete thumbnail:

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId,        int reqWidth, int reqHeight) {    // First decode with inJustDecodeBounds=true to check dimensions    final BitmapFactory.Options options = new BitmapFactory.Options();    options.inJustDecodeBounds = true;    BitmapFactory.decodeResource(res, resId, options);    // Calculate inSampleSize    options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight);    // Decode bitmap with inSampleSize set    options.inJustDecodeBounds = false;    return BitmapFactory.decodeResource(res, resId, options);}

We set it to imageview:

mImageView.setImageBitmap(    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
  • Do not process Bitmap-image download/resize In the UI thread, and do not place it in the UI thread. You can use asynctask to process concurrency issues.

We just mentioned bitmapfactory. decode * methods, it is worth noting that none of these methods can be executed in the UI thread, because their loading process is unreliable and may cause ANR of the application.
How can this problem be solved? We need asynctask to process concurrency. Asynctask provides a simple method to execute some operations in the background thread and feedback the results to the UI thread. Here is an example:

class BitmapWorkerTask extends AsyncTask {    private final WeakReference imageViewReference;    private int data = 0;    public BitmapWorkerTask(ImageView imageView) {        // Use a WeakReference to ensure the ImageView can be garbage collected        imageViewReference = new WeakReference(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);            }        }    }}
public void loadBitmap(int resId, ImageView imageView) {    BitmapWorkerTask task = new BitmapWorkerTask(imageView);    task.execute(resId);}

When we use asynctask in listview and gridview, some problems will occur. For example, when listview slides fast, its child view is not recycled, we do not know when asynctask will be completed. It may be that childview has been recycled before asynctask is executed. Here we will describe a method to avoid this situation:
Create a drawable subclass to reference the image returned after the task is executed.

static class AsyncDrawable extends BitmapDrawable {    private final WeakReference bitmapWorkerTaskReference;    public AsyncDrawable(Resources res, Bitmap bitmap,            BitmapWorkerTask bitmapWorkerTask) {        super(res, bitmap);        bitmapWorkerTaskReference =            new WeakReference(bitmapWorkerTask);    }    public BitmapWorkerTask getBitmapWorkerTask() {        return bitmapWorkerTaskReference.get();    }}

Before executing bitmapworkertask, create an asyncdrawable to bind the target imageview:

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);    }}

The cancelpotentialwork method is called before assigning values to the imageview. It uses the cancel () method to cancel expired tasks.

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;}

In the last step, modify the onpostexecute () method in bitmapworkertask.

class BitmapWorkerTask extends AsyncTask {    ...    @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);            }        }    }}
  • Cache Bitmap-Using Cache can improve image loading speed and improve user experience
  1. Memory Cache

Since android3.1, Google has provided a caching class called lrucache. Previously, we implemented caching using soft references or weak references, but Google does not recommend this, because the GC recovery frequency is increased after android2.3.
When using lrucache, we need to set a cache size for it. Setting a smaller cache does not work. Setting a higher cache size will also lead to oom. Therefore, setting a cache size is a technical activity.

private LruCache mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {    ...    // Get memory class of this device, exceeding this amount will throw an    // OutOfMemory exception.    final int memClass = ((ActivityManager) context.getSystemService(            Context.ACTIVITY_SERVICE)).getMemoryClass();    // Use 1/8th of the available memory for this memory cache.    final int cacheSize = 1024 * 1024 * memClass / 8;    mMemoryCache = new LruCache(cacheSize) {        @Override        protected int sizeOf(String key, Bitmap bitmap) {            // The cache size will be measured in bytes rather than number of items.            return bitmap.getByteCount();        }    };    ...}public void addBitmapToMemoryCache(String key, Bitmap bitmap) {    if (getBitmapFromMemCache(key) == null) {        mMemoryCache.put(key, bitmap);    }}public Bitmap getBitmapFromMemCache(String key) {    return mMemoryCache.get(key);}

In this way, when bitmap is used in imageview, it can be obtained from the cache first. If the cache does not exist, it will be obtained from the network:

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);    }}

Update bitmapworkertask.

class BitmapWorkerTask extends AsyncTask {    ...    // 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;    }    ...}

2. Use the hard disk cache
We will use disklrucache to implement hard disk cache.

private DiskLruCache mDiskCache;private static final int DISK_CACHE_SIZE = 1024 * 1024 * 10; // 10MBprivate static final String DISK_CACHE_SUBDIR = "thumbnails";@Overrideprotected void onCreate(Bundle savedInstanceState) {    ...    // Initialize memory cache    ...    File cacheDir = getCacheDir(this, DISK_CACHE_SUBDIR);    mDiskCache = DiskLruCache.openCache(this, cacheDir, DISK_CACHE_SIZE);    ...}class BitmapWorkerTask extends AsyncTask {    ...    // 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(String.valueOf(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    if (!mDiskCache.containsKey(key)) {        mDiskCache.put(key, bitmap);    }}public Bitmap getBitmapFromDiskCache(String key) {    return mDiskCache.get(key);}// 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 getCacheDir(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.getExternalStorageState() == Environment.MEDIA_MOUNTED            || !Environment.isExternalStorageRemovable() ?                    context.getExternalCacheDir().getPath() : context.getCacheDir().getPath();    return new File(cachePath + File.separator + uniqueName);}

It is worth mentioning that the android API does not provide the disklrucache interface, and you need to port it from the 4.x source code to the application. Source Code address:
libcore/luni/src/main/java/libcore/io/DiskLruCache.java

3. Sometimes the objects will be reloaded when switching between the horizontal and vertical screens, so that the cache will be lost. To avoid this problem, in addition to setting the portrait screen in manifest and not updating the screen, fragment is used for saving:

private LruCache mMemoryCache;@Overrideprotected void onCreate(Bundle savedInstanceState) {    ...    RetainFragment mRetainFragment =            RetainFragment.findOrCreateRetainFragment(getFragmentManager());    mMemoryCache = RetainFragment.mRetainedCache;    if (mMemoryCache == null) {        mMemoryCache = new LruCache(cacheSize) {            ... // Initialize cache here as usual        }        mRetainFragment.mRetainedCache = mMemoryCache;    }    ...}class RetainFragment extends Fragment {    private static final String TAG = "RetainFragment";    public LruCache mRetainedCache;    public RetainFragment() {}    public static RetainFragment findOrCreateRetainFragment(FragmentManager fm) {        RetainFragment fragment = (RetainFragment) fm.findFragmentByTag(TAG);        if (fragment == null) {            fragment = new RetainFragment();        }        return fragment;    }    @Override    public void onCreate(Bundle savedInstanceState) {        super.onCreate(savedInstanceState);        setRetainInstance(true);    }}

Original article: http://developer.android.com/training/displaying-bitmaps/index.html

Contact Us

The content source of this page is from Internet, which doesn't represent Alibaba Cloud's opinion; products and services mentioned on that page don't have any relationship with Alibaba Cloud. If the content of the page makes you feel confusing, please write us an email, we will handle the problem within 5 days after receiving your email.

If you find any instances of plagiarism from the community, please send an email to: info-contact@alibabacloud.com and provide relevant evidence. A staff member will contact you within 5 working days.

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.