Home>

Load large images efficiently

We often use many pictures when writing android programs.Different pictures will always have different shapes, different sizes,But in most cases,These pictures will be larger than the size required by our program.For example, most of the pictures displayed in the system picture library are taken with a mobile phone camera.The resolution of these pictures will be much higher than the resolution of our mobile screen.Everyone should know,The applications we write have a certain memory limit.Programs that take up too much memory are prone to oom (outofmemory) exceptions. We can see the maximum available memory for each application with the following code.

int maxmemory=(int) (runtime.getruntime (). maxmemory ()/1024);
log.d ("tag", "max memory is" + maxmemory + "kb");

So when showing high-resolution images,It is best to compress the picture first.The size of the compressed image should be similar to the size of the control used to display it.Displaying an oversized image on a small imageview does not bring any visual benefits,But it takes up a lot of our precious memory,And there may be a negative impact on performance.Let ’s take a lookHow to properly compress a large picture,While allowing it to display in the best size,Can also prevent the emergence of oom.

The bitmapfactory class provides multiple parsing methods (decodebytearray, decodefile, decoderesource, etc.) for creating bitmap objects. We should choose the appropriate method according to the source of the picture.For example, pictures in the SD card can use the decodefile method, pictures on the network can use the decodestream method, and pictures in the resource file can use the decoderesource method. These methods try to allocate memory for the already constructed bitmap,This will easily cause oom to appear. For this purpose, each parsing method provides an optional bitmapfactory.options parameter. Set the injustdecodebounds property of this parameter to true to prevent the parsing method from allocating memory for the bitmap.The return value is no longer a bitmap object, but is null. Although the bitmap is null, the outwidth, outheight, and outmimetype properties of bitmapfactory.options will be assigned.This technique allows us to obtain the image's length and width values ​​and mime type before loading the image, so as to compress the image according to the situation.As shown in the following code:

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;

In order to avoid oom anomaly, it is best to check the size of the picture when parsing each picture.Unless you trust the source of the picture,Make sure that these images do not exceed the available memory of your program.

Now that the size of the picture is known,We can then decide whether to load the entire image into memory or load a compressed version of the image into memory.The following factors are

What we need to consider:

Estimate the memory required to load the entire image.

How much memory are you willing to provide in order to load this picture.

The actual size of the control used to display this picture.

The screen size and resolution of the current device.

For example, your imageview is only 128 * 96 pixels in size.Just to show a thumbnail,At this time, it is obviously not worth loading a 1024 * 768 pixel image into the memory completely.

So how can we compress the picture?This can be achieved by setting the value of insamplesize in bitmapfactory.options.For example, we have a picture of 2048 * 1536 pixels,Set the value of insamplesize to 4 to compress this image to 512 * 384 pixels. Originally loading this picture required 13m of memory, and only 0.75m was needed after compression (assuming the picture is of type argb_8888, that is, each pixel occupies 4 bytes). The following method can be based on the width and height passed in,Calculate the appropriate insamplesize value:

public static int calculateinsamplesize (bitmapfactory.options options,    int reqwidth, int reqheight) {
  //height and width of the source image
  final int height=options.outheight;
  final int width=options.outwidth;
  int insamplesize=1;
  if (height>reqheight || width&reqwidth) {
    //Calculate the ratio of actual width to target width
    final int heightratio=math.round ((float) height/(float) reqheight);
    final int widthratio=math.round ((float) width/(float) reqwidth);
    //Select the smallest ratio of width to height as the value of insamplesize, so that the width and height of the final image can be guaranteed
    //must be greater than or equal to the width and height of the target.
    insamplesize=heightratio<widthratio?heightratio:widthratio;
  }
  return insamplesize;
}

Using this method,First you have to set the injustdecodebounds property of bitmapfactory.options to true and parse the image once.Then pass bitmapfactory.options to the calculateinsamplesize method along with the desired width and height, and you can get the appropriate insamplesize value. Parse the picture again later,Using the newly obtained insamplesize value and setting injustdecodebounds to false, you can get the compressed image.

public static bitmap decodesampledbitmapfromresource (resources res, int resid,    int reqwidth, int reqheight) {
  //Set injustdecodebounds to true for the first parsing to get the image size
  final bitmapfactory.options options=new bitmapfactory.options ();
  options.injustdecodebounds=true;
  bitmapfactory.decoderesource (res, resid, options);
  //call the method defined above to calculate the insamplesize value
  options.insamplesize=calculateinsamplesize (options, reqwidth, reqheight);
  //Use the obtained insamplesize value to parse the image again
  options.injustdecodebounds=false;
  return bitmapfactory.decoderesource (res, resid, options);
}

The following code is very simple to compress any picture into a 100 * 100 thumbnail,And displayed on imageview.

mimageview.setimagebitmap (

decodesampledbitmapfromresource (getresources (), r.id.myimage, 100, 100));

Use image caching technology

Loading a picture in the ui interface of your application is a very simple thing,But when you need to load a lot of images on the interface,The situation becomes complicated.In many cases,(Such as using components such as listview, gridview or viewpager), the picture displayed on the screen can be continuously increased by events such as sliding the screenEventually leads to oom.

To ensure that memory usage is always maintained within a reasonable range,The images of the removed screen are usually recycled.At this point, the garbage collector also thinks that you no longer hold references to these pictures,Then perform gc operation on these pictures. It is very good to use this kind of thinking to solve the problem,But to make the program run fast,Load images quickly on the interface,You must also consider that after some pictures are recycled,The user slides it back into the screen again.At this time, reloading the image that has just been loaded is undoubtedly a performance bottleneck.You need to find a way to avoid this from happening.

at this time,Using memory cache technology can solve this problem very well.It allows components to quickly reload and process images.Let ’s take a look at how to use memory caching technology to cache images.So that your application can improve the response speed and fluency when loading many pictures.

Memory cache technology provides a fast way to access pictures that take up valuable memory in your application.The core class is lrucache (this class is provided in the android-support-v4 package). This class is very suitable for caching images.Its main algorithm principle is to store the most recently used objects in the linkedhashmap with a strong reference, and remove the least recently used objects from memory before the cache value reaches a preset value.

In the past, we often used the implementation of a very popular memory cache technology,I.e. softreference or weakreference. But this method is no longer recommended,Because starting with android 2.3 (api level 9), the garbage collector is more inclined to recycle objects holding soft or weak references,This makes soft and weak references unreliable.In addition, in android 3.0 (api level 11), the image data is stored in local memory.So it cannot be released in a predictable way,This has the potential risk of causing your application to overflow and crash.

In order to be able to choose an appropriate cache size for lrucache, there are several factors that should be taken into consideration,E.g:

How much memory can your device allocate for each application?

How many pictures can be displayed on the device screen at one time?How many images need to be preloaded,Because it may be displayed on the screen soon?

What is the screen size and resolution of your device?An ultra-high-resolution device (e.g. galaxy nexus), compared to a lower-resolution device (e.g. nexus s), holds the same number of pictures,Need more cache space.

Image size and size,And how much memory each image will take up.

How often are pictures accessed?Will some pictures be accessed more frequently than others?if so,You should probably keep some pictures in memory,Or use multiple lrucache objects to distinguish different groups of pictures.

Can you maintain a good balance between quantity and quality?Sometimes,Store multiple low-resolution images,In the background, it is more effective to open a thread to load high-resolution images.

There is no one specific cache size that can satisfy all applications.It's up to you.You should analyze the memory usage of the program,Then work out a suitable solution.A too small cache space,May cause images to be released and reloaded frequently,This does no good.And a too large cache space,It may still cause a java.lang.outofmemory exception.

Here is an example of using lrucache to cache images:

private lrucache<string, bitmap>mmemorycache;
@override
protected void oncreate (bundle savedinstancestate) {
  //Get the maximum value of available memory,Using memory beyond this value will cause an outofmemory exception.
  //lrucache passes the cache value through the constructor,In kb.
  int maxmemory=(int) (runtime.getruntime (). maxmemory ()/1024);
  //Use 1/8 of the maximum available memory value as the cache size.
  int cachesize=maxmemory/8;
  mmemorycache=new lrucache<string, bitmap>(cachesize) {
    @override
    protected int sizeof (string key, bitmap bitmap) {
      //override this method to measure the size of each image,Returns the number of pictures by default.
      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);
}

In this example,One-eighth of the memory allocated to the application by the system is used as the cache size.Among mid-to-high-profile phones,That's about 4 megabytes (32/8) of cache space.A full-screen gridview is filled with four 800x480 resolution images.It will take about 1.5 megabytes of space (800 * 480 * 4). Therefore, this cache size can store 2.5 pages of pictures.

When loading an image into imageview,First it checks in lrucache's cache.If a corresponding key is found,Will update the imageview immediately, otherwise start a background thread to load the image.

public void loadbitmap (int resid, imageview imageview) {
  final string imagekey=string.valueof (resid);
  final bitmap bitmap=getbitmapfrommemcache (imagekey);
  if (bitmap!=null) {
    imageview.setimagebitmap (bitmap);
  } else {
    imageview.setimageresource (r.drawable.image_placeholder);
    bitmapworkertask task=new bitmapworkertask (imageview);
    task.execute (resid);
  }
}
bitmapworkertask also puts key-value pairs of the newly loaded image into the cache.
[java] view plain copy
class bitmapworkertask extends asynctask<integer, void, bitmap>{
  //Load the image in the background.
  @override
  protected bitmap doinbackground (integer ... params) {
    final bitmap bitmap=decodesampledbitmapfromresource (
        getresources (), params [0], 100, 100);
    addbitmaptomemorycache (string.valueof (params [0]), bitmap);
    return bitmap;
  }
}
  • Previous C ++ implementation of common cache algorithms and LRU
  • Next C # most complete method for uploading pictures