“This is the second day of my participation in the Gwen Challenge in November. See details: The Last Gwen Challenge in 2021”

background

In Android development, loading too many or too many images can easily cause OutOfMemoryError, a common memory overflow. Because Android imposes a memory limit on a single application, the default allocation is only a few megabytes (depending on the system). Images loaded in compressed formats such as JPG (which supports the highest level of compression, but is lossy) take up a lot of memory and are prone to overflow. It is important to load bitmaps efficiently. On Android, a Bitmap refers to an image in common formats such as.jpg.png. Webp.

How to choose the image format

One rule: Reduce the size of the image as much as possible without distorting it

The most popular image formats on Android are PNG, JPEG and WebP

  • 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 is supported after Android4.0 (API 14), lossless and transparent is supported after Android4.3 (API18)

Webp can effectively reduce the disk space occupied by the picture while maintaining the clarity of the picture

Image compression

Image compression can be considered from three aspects:

  1. The quality of

    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.

/** * @param format jpeg, PNG,webp * @param quality the quality of the image,0-100, the smaller the quality of the image, the poorer the quality */ public static void compress(Bitmap.CompressFormat format, int quality) { 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
  1. Sampling rate

    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.

    Setting inSampleSize will cause the compressed image to be 1/inSampleSize in width and height, and the overall size to be 1/2 of the inSampleSize square of the original image.

    • If inSampleSize is less than or equal to 1, 1 is used

    • InSampleSize can only be set to the square of 2. If inSampleSize is not set to the square of 2, inSampleSize is eventually reduced to the nearest square of 2. If inSampleSize is set to 7, inSampleSize is compressed by 4, inSampleSize is compressed by 8.

/** ** @param inSampleSize public static void compress(int inSampleSize) {File sdFile = Environment.getExternalStorageDirectory(); File originFile = new File(sdFile, "originImg.jpg"); BitmapFactory.Options options = new BitmapFactory.Options(); / / set this parameter is only reads the images of wide high into the options, will not be read into memory, the whole picture options. Prevent oom inJustDecodeBounds = true; Bitmap emptyBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options); options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; Bitmap resultBitmap = BitmapFactory.decodeFile(originFile.getAbsolutePath(), options); ByteArrayOutputStream bos = new ByteArrayOutputStream(); resultBitmap.compress(Bitmap.CompressFormat.JPEG, 100, 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
  1. The zoom

    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 static bitmap * @param context * @param id * @param maxW * @param maxH * @return */ public static bitmap * @param context * @param id * @param maxH * @return */ resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){ Resources resources = context.getResources(); BitmapFactory.Options options = new BitmapFactory.Options(); / / decoded only outxxx parameters Such as width and height of the options. InJustDecodeBounds = true; BitmapFactory.decodeResource(resources,id,options); Int w = options.outWidth; int h = options.outHeight; Options. inSampleSize = calcuteInSampleSize(w,h,maxW,maxH); if (! hasAlpha){ options.inPreferredConfig = Bitmap.Config.RGB_565; } options.inJustDecodeBounds = false; // Set options.inMutable=true; options.inBitmap=reusable; return BitmapFactory.decodeResource(resources,id,options); } /** * calculate the scaling coefficient * @param w * @param h * @param maxW * @param maxH * @param return the scaling coefficient */ private static int calcuteInSampleSize(int w,int h,int maxW,int maxH) { int inSampleSize = 1; if (w > maxW && h > maxH){ inSampleSize = 2; While (w /inSampleSize > maxW &&h /inSampleSize > maxH){inSampleSize *= 2; } } return inSampleSize; }}Copy the code

Using the JPEG library, the JNI layer uses Huffman algorithm to compress the image

Android’s image engine uses a neutered version of the Skia engine, removing the Huffman algorithm from image compression

void write_JPEG_file(uint8_t *data, int w, int h, jint q, Const char *path) {// 3.1, create a jpeg compression object jpeg_compress_struct JCS; // Error callback jpeg_error_mgr error; jcs.err = jpeg_std_error(&error); // Create a compression object jpeg_create_compress(& JCS); Write binary FILE *f = fopen(path, "wb"); jpeg_stdio_dest(&jcs, f); Jcs. image_width = w; jcs.image_height = h; //bgr jcs.input_components = 3; jcs.in_color_space = JCS_RGB; jpeg_set_defaults(&jcs); // Enable Huffman function jcs.optimize_coding = true; jpeg_set_quality(&jcs, q, 1); Jpeg_start_compress (&jcs, 1); Int row_stride = w * 3; JSAMPROW row[1]; While (jcs.next_scanline < jcs.image_height) {uint8_t *pixels = data + jcs.next_scanline * row_stride; row[0]=pixels; jpeg_write_scanlines(&jcs,row,1); } // 3.6, compress jpeg_finish_compress(&jcs); // release the JPEG object fclose(f); jpeg_destroy_compress(&jcs); }Copy the code

Because it involves THE JNI part, I only post the code used for the time being, and I will write some JNI part blogs to share with you later.

Set images to be reusable

Image multiplexing mainly refers to the multiplexing of memory blocks, so there is no need to apply for a new piece of memory for the bitmap, avoiding a memory allocation and recycling, thus improving the operating efficiency. It is important to note that inBitmap can only be used after 3.0. In 2.3, bitmap data is stored in native memory region, not in Dalvik’s memory heap. With inBitmap, prior to 4.4, you can only reuse the memory area of a bitmap of the same size. After 4.4, you can reuse the memory area of any bitmap as long as the memory area is larger than the bitmap to be allocated. The best way to do this is to use LRUCache to cache the bitmap, and then the new bitmap comes in. You can find the bitmap from the cache according to the API version that is most suitable for reuse, and reuse its memory area.

BitmapFactory.Options options = new BitmapFactory.Options(); / / decoded only outxxx parameters Such as width and height of the options. InJustDecodeBounds = true; BitmapFactory.decodeResource(resources,id,options); Int w = options.outWidth; int h = options.outHeight; Options. inSampleSize = calcuteInSampleSize(w,h,maxW,maxH); if (! hasAlpha){ options.inPreferredConfig = Bitmap.Config.RGB_565; } options.inJustDecodeBounds = false; // Set options.inMutable=true; options.inBitmap=reusable;Copy the code

Using image Caching

Android has a LruCache is a thread-safe data cache class based on the least-used algorithm. When the set cache capacity is exceeded, the LruCache LRU cache policy is implemented by using LinkedHashMap. In addition, the cache size is controlled and elements are eliminated by encapsulating related methods such as GET and PUT, but null keys and values are not supported. We can use an open source library provided by JakeWharton github.com/JakeWharton… To implement our image caching logic

The memory and disk parts are omitted.