Optimization of bitmap for Android performance tuning

background

In Android development, loading too many pictures or too large ones can easily cause an OutOfMemoryError exception, which is our common memory overflow. Because Android imposes a memory limit on a single application, the default allocated memory is only a few M (depending on different systems). If the loaded image is in a compression format such as JPG (JPG supports the highest level of compression, but the compression is lossy), expanding it in memory will take up a lot of memory space, and it is easy to cause memory overflow. So it is very important to load Bitmap efficiently. Bitmap refers to a picture in Android, and the format of the picture is .jpg .png .webp and other common formats.

How to choose a picture format

One principle: under the premise of ensuring that the image is not visually distorted, reduce the size as much as possible

Android currently commonly used picture formats are png, jpeg and webp

  • png: Lossless compressed image format, supports Alpha channel, Android cut image material mostly adopts this format
  • 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: It is a picture format that provides lossy compression and lossless compression at the same time. It is derived from the video encoding format VP8. According to Google's official website, lossless webp is 26% smaller than png on average, and lossy webp is 25% smaller than jpeg on average. ~34%, lossless webp supports Alpha channel, lossy webp also supports under certain conditions, lossy webp supports after Android4.0 (API 14), lossless and transparent supports after Android4.3 (API18)

Using 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. quality

    Quality compression will not change the size of the image in memory, it will only reduce the size of the disk space occupied by the image, because quality compression will not change the resolution of the image, and the size of the image in memory is based on one pixel of width height Calculated by the number of bytes occupied, the width and height have not changed, and the size occupied in the memory will naturally not change. The principle of quality compression is to reduce the disk space occupied by the picture by changing the bit depth and transparency of the picture, so It is not suitable as a thumbnail, but can be used to reduce the disk space occupied by the picture while maintaining the picture quality. In addition, since png is lossless compression, setting quality is invalid,

  /**
 * 质量压缩
 *
 * @param format  图片格式 jpeg,png,webp
 * @param quality 图片的质量,0-100,数值越小质量越差
 */
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();
    }
}
  1. Sampling Rate

    Sampling rate compression is to reduce the resolution of the image by setting BitmapFactory.Options.inSampleSize, thereby reducing the disk space and memory size occupied by the image.

    The set inSampleSize will cause the width and height of the compressed image to be 1/inSampleSize, and the overall size will become one-quarter of the inSampleSize of the original image. Of course, there are some points to note:

    • inSampleSize less than or equal to 1 will be processed as 1

    • inSampleSize can only be set to the square of 2. If it is not the square of 2, it will eventually be reduced to the nearest square of 2. For example, if it is set to 7, it will be compressed by 4, and if it is set to 15, it will be compressed by 8.

/**
 * 
 * @param inSampleSize  可以根据需求计算出合理的inSampleSize
 */
public static void compress(int inSampleSize) {
    
    
    File sdFile = Environment.getExternalStorageDirectory();
    File originFile = new File(sdFile, "originImg.jpg");
    BitmapFactory.Options options = new BitmapFactory.Options();
    //设置此参数是仅仅读取图片的宽高到options中,不会将整张图片读到内存中,防止oom
    options.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();
    }
}
  1. zoom

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

 /**
     *  缩放bitmap
     * @param context
     * @param id
     * @param maxW
     * @param maxH
     * @return
     */
    public static Bitmap resizeBitmap(Context context,int id,int maxW,int maxH,boolean hasAlpha,Bitmap reusable){
    
    
        Resources resources = context.getResources();
        BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        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;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;
        return BitmapFactory.decodeResource(resources,id,options);
    }

    /**
     * 计算缩放系数
     * @param w
     * @param h
     * @param maxW
     * @param maxH
     * @return 缩放的系数
     */
    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;
    }
}

Use the JPEG library to compress images using the Huffman algorithm at the jni layer

Android's image engine uses a castrated version of the skia engine, which removes the Huffman algorithm in image compression

void write_JPEG_file(uint8_t *data, int w, int h, jint q, const char *path) {
    
    
//    3.1、创建jpeg压缩对象
    jpeg_compress_struct jcs;
    //错误回调
    jpeg_error_mgr error;
    jcs.err = jpeg_std_error(&error);
    //创建压缩对象
    jpeg_create_compress(&jcs);
//    3.2、指定存储文件  write binary
    FILE *f = fopen(path, "wb");
    jpeg_stdio_dest(&jcs, f);
//    3.3、设置压缩参数
    jcs.image_width = w;
    jcs.image_height = h;
    //bgr
    jcs.input_components = 3;
    jcs.in_color_space = JCS_RGB;
    jpeg_set_defaults(&jcs);
    //开启哈夫曼功能
    jcs.optimize_coding = true;
    jpeg_set_quality(&jcs, q, 1);
//    3.4、开始压缩
    jpeg_start_compress(&jcs, 1);
//    3.5、循环写入每一行数据
    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、压缩完成
    jpeg_finish_compress(&jcs);
//    3.7、释放jpeg对象
    fclose(f);
    jpeg_destroy_compress(&jcs);
}

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

Setting pictures can be reused

Image multiplexing mainly refers to the multiplexing of memory blocks. There is no need to apply for a new piece of memory for this bitmap, which avoids a memory allocation and recovery, thereby improving operating efficiency.

It should be noted that inBitmap can only be used after 3.0. On 2.3, the bitmap data is stored in the native memory area, not on the Dalvik memory heap.

Using inBitmap, before 4.4, you can only reuse bitmap memory areas of the same size, but after 4.4 you can reuse any bitmap memory area, as long as this memory is larger than the bitmap that will allocate memory. The best way here is to use LRUCache to cache the bitmap. Later, a new bitmap comes. You can find the most suitable bitmap for reuse from the cache according to the API version to reuse its memory area.

   BitmapFactory.Options options = new BitmapFactory.Options();
        // 只解码出 outxxx参数 比如 宽、高
        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;
        //设置成能复用
        options.inMutable=true;
        options.inBitmap=reusable;

Use image cache

There is a LruCache in android, which is a thread-safe data cache class implemented based on the most remembered and least used algorithm. When the set cache capacity is exceeded, the least recently used data will be eliminated first. The LRU cache strategy of LruCache is realized by using LinkedHashMap. And control the cache size and eliminate elements by encapsulating related methods such as get/put, but do not support null key and value. We can use an open source library provided by JakeWharton github.com/JakeWharton…to implement the logic of our image cache

The memory and disk sections are omitted.

In order to help everyone better grasp the performance optimization in a comprehensive and clear manner, we have prepared relevant core notes (returning to the underlying logic):https://qr18.cn/FVlo89

Performance optimization core notes:https://qr18.cn/FVlo89

Startup optimization

Memory optimization

UI

optimization Network optimization

Bitmap optimization and image compression optimization : Multi-thread concurrency optimization and data transmission efficiency optimization Volume package optimizationhttps://qr18.cn/FVlo89




"Android Performance Monitoring Framework":https://qr18.cn/FVlo89

"Android Framework Study Manual":https://qr18.cn/AQpN4J

  1. Boot Init process
  2. Start the Zygote process at boot
  3. Start the SystemServer process at boot
  4. Binder driver
  5. AMS startup process
  6. The startup process of the PMS
  7. Launcher's startup process
  8. The four major components of Android
  9. Android system service - distribution process of Input event
  10. Android underlying rendering-screen refresh mechanism source code analysis
  11. Android source code analysis in practice

Guess you like

Origin blog.csdn.net/weixin_61845324/article/details/132093024