Before the order:

For personal reasons, I want to make fun of this platform. There are a lot of things written in the draft, because I am used to the MAC shortcut keys, SO I operated it, but the things are gone. Fortunately, I contacted the platform and the later version will be optimized.

preface

The processing of the big picture has always been a must-ask question in the interview, and it is also a requirement in the work from time to time. For this, I will analyze it based on different scenarios. (Of course, many of the content here are integrated by referring to other blogs, in order to improve myself in the future)

Scenario 1: Large images on a small ImageView

Displaying a large image on a small ImageView doesn’t provide any visual benefit, but it can take up quite a bit of our precious memory. There is a limit to the amount of memory the system can provide for your application (exceeding this will result in OOM), so you can see the maximum available memory for each application in the code below

int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024);
Log.d("TAG", "Max memory is " + maxMemory + "KB");
Copy the code

For example, if your ImageView is only 12,896 pixels in size to display a thumbnail, it’s not worth loading a 1024,768 pixel image into memory. In this case, it is imperative to compress images. So how do you compress properly, and that leads to this BitmapFactory class. The BitmapFactory class provides several parsing methods (decodeByteArray, decodeFile, decodeResource, etc.) to create Bitmap objects. We should choose the appropriate method based on the image source. DecodeFile can be used for SD card images, decodeStream can be used for network images, and decodeResource can be used for images in resource files. These methods try to allocate memory for bitmaps that have already been built, which can easily result in OOM. To this end, each resolution method provides an optional bitmapFactory. Options parameter. Setting this to true disables the inJustDecodeBounds method from allocating memory to bitmaps and returning a bitmap object. But is null. Although the Bitmap is null, the bitmapFactory. Options properties outWidth, outHeight, and outMimeType are all assigned values. This technique allows us to get the length and width and MIME type of the image before loading it, so we can compress the image as needed. Code sample

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;
Copy the code

So how can we compress the image? This is done by setting the value of inSampleSize in bitmapFactory. Options. For example, if you have an image with 20481536 pixels, set inSampleSize to 4 to compress the image to 512384 pixels. Loading the image used to take up 13M of memory, but when compressed, it takes up 0.75m (assuming the image is ARGB_8888, or 4 bytes per pixel). The following method calculates the appropriate inSampleSize value based on the width and height passed in:

public static int calculateInSampleSize(BitmapFactory.Options options, int reqWidth, Final int height = options.outheight; // Final int height = options.outheight; final int width = options.outWidth; int inSampleSize = 1; If (height > reqHeight | | width > reqWidth) {/ / calculate the actual wide high and the target width ratio of final int heightRatio = math.h r ound (height/(float) (float) reqHeight); final int widthRatio = Math.round((float) width / (float) reqWidth); InSampleSize = heightRatio < widthRatio? // inSampleSize = heightRatio < widthRatio? heightRatio : widthRatio; } return inSampleSize; }Copy the code

Once we provide a method for calculating the appropriate inSampleSize value, we’ll use it in detail.

public static Bitmap decodeSampledBitmapFromResource(Resources res, int resId, int reqWidth, Int reqHeight) {// Set inJustDecodeBounds to true, Final bitmapFactory.options Options = new bitmapFactory.options (); options.inJustDecodeBounds = true; BitmapFactory.decodeResource(res, resId, options); InSampleSize Any value less than or equal to 1 is the same as 1. Note: the * decoder uses final values based on powers of 2, and any other values round * to the nearest power of 2. options.inSampleSize = calculateInSampleSize(options, reqWidth, reqHeight); / / the use of access to inSampleSize values again resolution picture options. The inJustDecodeBounds = false; return BitmapFactory.decodeResource(res, resId, options); }Copy the code

Note the inJustDecodeBounds variable above. Parsing to true does not return a bitmap but null, which means the system will not allocate memory to that bitmap. The third step is to call the method directly in the project

mImageView.setImageBitmap(
    decodeSampledBitmapFromResource(getResources(), R.id.myimage, 100, 100));
Copy the code

This completes the first scenario.

The second scenario: loading multiple images on a page

It’s relatively easy to handle a single image on a page, but if you load a bunch of images on a page, you might have to worry about getting OOM. You might say limit the memory size to display as many images as possible, but if you’re using a ListView, you can theoretically hold as many photos as possible. Then we need to consider how to ensure that the memory usage is always maintained in a reasonable range, while loading images quickly in the interface, you can load these images quickly, without OOM.

  • 1. Ensure that the memory usage is maintained within a reasonable range

In fact, it’s not guaranteed in this place, it’s guaranteed in the listView, gridView. Therefore, it is unavoidable to use these controls in real scenarios. A lot of blogs talk about caching (basically LruCache), which is problematic. LruCache does not reduce memory usage, it does not free memory, but increases memory usage. The purpose of using LruCache is not to avoid OOM but to speed up loading and avoid repeatedly retrieving images from the network or disk. How to ensure that the listView will not display OOM when loading multiple images? Listview reuse and optimization, by rewriting getView(…) The reason you don’t increase memory in your ListView is because the ListView reuses the previous ImageView, and the ImageView resetimageBitMap frees the reference to the previous bitmap, The previous bitmap will be recycled, so that memory remains the same, no matter how many images hang

  • 2. Load images quickly

If we have to fetch images from the network every time, or from disk every time, this will be time-consuming and will affect the user experience. At this time, cache technology (LruCache) will be used to limit the size of memory, and at the same time, pictures will be saved in memory as much as possible, so that the reading speed will be much faster, enhance the user experience, only when the scope of memory constraints, we will consider to disk, network access. The size of the cache should be 1/8 of the maximum available memory.

// Get the maximum value of available memory. Using more than this value will cause an OutOfMemory exception. // LruCache passes in cached values in kilobytes through the constructor. int maxMemory = (int) (Runtime.getRuntime().maxMemory() / 1024); // Use 1/8 of the maximum available memory value as the cache size. int cacheSize = maxMemory / 8;Copy the code

Here is an example of using LruCache to cache images:

private LruCache<String, Bitmap> mMemoryCache; @override protected void onCreate(Bundle savedInstanceState) {Override protected void onCreate(Bundle savedInstanceState) {Override protected void onCreate(Bundle savedInstanceState) { // LruCache passes in cached values in kilobytes through the constructor. 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, returning the number of images 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); } -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- / / priority from memory load, then it should be loaded from disk, morally slightly off here, 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 put new loading pictures of key/value pair in the cache. Class BitmapWorkerTask extends AsyncTask<Integer, Void, Bitmap> {// Load an 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; }}Copy the code

So far, you don’t have to worry about the OOM if you want to load large images or lots of images into your application. Of course, the quoted blog has talked about the realization of the photo wall effect, in fact, is a list of pictures, I think there is nothing to talk about, if interested in Android photo wall application implementation, which is used in asynchronous, network request and other knowledge.

The third scenario is full loading and displaying the giant image

In the first scenario, we talked about how to compress a large image to display on a small ImageView, but there is actually a case for image loading where a single image is very large and compression is not allowed. Such as display: world map, Qingming River map, weibo long map and so on. So what to do about this need? In addition, considering the memory situation, it is impossible to load the whole image into memory at one time, so it must be partial loading, so we need to use a class:

  • BitmapRegionDecoder

Secondly, since the screen display is endless, then at least add a drag gesture up and down around, so that the user can drag to view.

To sum up, the purpose of this blog is to customize a View to display a giant image, which users can drag to View. The general effect picture is as follows:BitmapRegionDecoder is used to display a rectangular area of an image. This class is great if you need to display a specific area of an image.

For the use of this class, very simple, since it is to display a certain area of the image, then at least one method to set the image; A method is passed into the displayed area; See:

BitmapRegionDecoder provides a series of newInstance methods to construct objects, supporting the passing of file paths, file descriptors, file inputStrem, etc.

  • BitmapRegionDecoder bitmapRegionDecoder = BitmapRegionDecoder.newInstance(inputStream, false);

That takes care of the incoming image we need to process, so the next step is to display the specified area.

  • bitmapRegionDecoder.decodeRegion(rect, options);

Parameters is obviously a the rect, 2 it is BitmapFactory. The Options, you can control the picture inSampleSize inPreferredConfig, etc. Here’s an example:

mImageView = (ImageView) findViewById(R.id.id_imageview); try { InputStream inputStream = getAssets().open("tangyan.jpg"); Bitmapfactory.options = new bitmapFactory.options (); tmpOptions.inJustDecodeBounds = true; BitmapFactory.decodeStream(inputStream, null, tmpOptions); int width = tmpOptions.outWidth; int height = tmpOptions.outHeight; / / set the display area of the center of the picture BitmapRegionDecoder BitmapRegionDecoder = BitmapRegionDecoder. NewInstance (inputStream, false); BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; Bitmap bitmap = bitmapRegionDecoder.decodeRegion(new Rect(width / 2 - 100, height / 2 - 100, width / 2 + 100, height / 2 + 100), options); mImageView.setImageBitmap(bitmap); } catch (IOException e) { e.printStackTrace(); }Copy the code

The above code, is to use BitmapRegionDecoder to load images of assets, call. BitmapRegionDecoder decodeRegion parsing images in the middle of the rectangular area, return to bitmap, finally displayed in the ImageView.Of course, with the ability to display large images and a page is not displayed, it will involve dragging, zooming, double-clicking and a series of gestures, this referenceAndroid HD loading long or large image scheme(This article has a few hicks, but it’s a good place to get started).



Summary: This is basically what happens when you load a large image scene. But here I want to do some other extensions of the scene.

extension

Scenario # 1: Refer to blogsPerformance Optimization – Several ways to compress and optimize Android images

The image compression mentioned above is by changing the value of inSample, in fact, it is only one of the image compression methods. In fact, there are replacement image format, quality compression, sampling rate compression, zoom compression, call JPEG compression and so on.

  • Image format compression

Android currently uses PNG, JPEG, and WebP image formats,

PNG: lossless compressed image format, which supports Alpha channel and is used for cutting image materials on Android

Jpeg: lossy compressed image format, does not support transparent background, suitable for large image compression with rich colors, such as photos, not suitable for logo

Webp: Lossless WebP is a picture format that provides lossless compression and lossless compression at the same time, derived from video encoding format VP8, from Google’s official website, lossless WebP is 26% smaller than PNG on average, lossless WebP is 25%~34% smaller than JPEG on average, lossless WebP supports Alpha channel, lossless WebP is also supported under certain conditions, Lossy webp in Android4.0 (API 14) after support, lossless and transparent in Android4.3 (API18) after support, using webp can maintain the picture clarity, can effectively reduce the image occupied by the disk space size

  • The quality of compressed

Quality compression will not change the image in memory size, will only reduce the size of the image of disk space occupied, because quality will not change the resolution of the image compression, and image in the memory size is according to a pixel widthheight calculation of the number of bytes occupied, wide high did not change, will not change in the size of the memory footprint of nature, The principle of quality compression is to change the bit depth and transparency of an image to reduce the disk space occupied by the image. Therefore, it is not suitable for thumbnails, but can be used to reduce the disk space occupied by the image while maintaining the image quality. Also, since PNG is lossless compression, setting quality is invalid.

File sdFile = Environment.getExternalStorageDirectory();
   File originFile = new File(sdFile, "originImg.jpg");
   Bitmap originBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath());
   ByteArrayOutputStream bos = new ByteArrayOutputStream();
   originBitmap.compress(format, quality, bos);
   try {
       FileOutputStream fos = new FileOutputStream(new File(sdFile, "resultImg.jpg"));
       fos.write(bos.toByteArray());
       fos.flush();
       fos.close();
   } catch (FileNotFoundException e) {
       e.printStackTrace();
   } catch (IOException e) {
       e.printStackTrace();
   }

Copy the code
  • Sampling rate compression, which is scenario one

Sampling rate is compressed by setting the BitmapFactory. Options. InSampleSize, to reduce the resolution of the image, thus reducing the picture by the amount of disk space and memory size.

  • Scaling of compression

Reduce the disk space and memory size of an image by reducing the pixel size of the image, which can be used to cache thumbnails

public void compress(View v) { File sdFile = Environment.getExternalStorageDirectory(); File originFile = new File(sdFile, "originImg.jpg"); Bitmap bitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath()); // Set the zoom ratio int radio = 8; Bitmap result = Bitmap.createBitmap(bitmap.getWidth() / radio, bitmap.getHeight() / radio, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); RectF rectF = new RectF(0, 0, bitmap.getWidth() / radio, bitmap.getHeight() / radio); DrawBitmap (bitmap, null, rectF, null); drawBitmap(bitmap, null, rectF, null); ByteArrayOutputStream bos = new ByteArrayOutputStream(); result.compress(Bitmap.CompressFormat.JPEG, 100, bos); try { FileOutputStream fos = new FileOutputStream(new File(sdFile, "sizeCompress.jpg")); fos.write(bos.toByteArray()); fos.flush(); fos.close(); } catch (FileNotFoundException e) { e.printStackTrace(); } catch (IOException e) { e.printStackTrace(); }}Copy the code
  • Configure the bitmap. Config value to reduce the number of bytes per pixel

In fact, sampling rate compression and scaling compression is very similar, the difference is that the sampling rate compression ratio will be limited (is a power of 2), size compression will not, but the clarity of sampling rate compression will be better than the clarity of size compression. ListView reuse and optimization in detail 2.Android efficient loading large picture, multi-picture solution, effectively avoid the program OOM 3.Android photo wall application implementation 4. Performance Optimization – Several ways to compress and optimize Android images