Performance Optimization 04-Commonly used image optimization techniques

Performance optimization 04-image optimization

1. Image compression

Pictures usually occupy a lot of memory in APP, so picture compression is often required.

Common image compression methods: size compression, quality compression, format conversion.

1. Size compression

When loading a large image into memory, you need to compress the size first, otherwise it will easily lead to oom.

Size compression affects both the storage size of the image and the memory size of the image.

Size compression uses BitmapFactory.Options, loaded 2 times:

  1. For the first time, only the border is loaded and the image size is obtained;
  2. Set the compression ratio and load the full image.
/**
 * 尺寸压缩
 * @param resources 资源
 * @param id        资源id
 * @param newWidth  压缩后的宽度
 * @param newHeight 压缩后的高度
 * @return 压缩后的Btmap
 */
public static Bitmap sizeCompress(Resources resources, int id, float newWidth, float newHeight) {
    BitmapFactory.Options options = new BitmapFactory.Options();
    //开始读入图片,此时把options.inJustDecodeBounds 设回true了
    options.inJustDecodeBounds = true;

    Bitmap bitmap = BitmapFactory.decodeResource(resources, id, options);
    options.inJustDecodeBounds = false;
    int w = options.outWidth;
    int h = options.outHeight;
    //缩放比。由于是固定比例缩放,只用高或者宽其中一个数据进行计算即可
    int sacle = (int) (w / newWidth > h / newHeight ? w / newWidth : h / newHeight);
    sacle = sacle <= 0 ? 1 : sacle;
    options.inSampleSize = sacle;//设置缩放比例
    //重新读入图片,注意此时已经把options.inJustDecodeBounds 设回false了
    bitmap = BitmapFactory.decodeResource(resources, id, options);
    return bitmap;
}

Note: The compression ratio can be set to any value, but the actual compression ratio must be 2 to the nth power.

2. Quality compression

Quality compression only affects the storage size of the image, not the memory size of the image. In addition, when the quality compressed image is converted into binary data for transmission, the data is also smaller. Such as WeChat sharing pictures.

Bitmap.compress() is a quality compression tool provided by the system. Bitmap.compress() uses the incomplete Skia library, which is based on libjpeg for jpeg and libpng for png. When libjpeg library compresses images, Huffman encoding can be set. However, due to the CPU, it was not turned on before 7.0, and it was turned on after 7.0.

/**
 * 质量压缩
 * @param bitmap         原图
 * @param compressFormat 图片类型:JPEG,PNG,WEBP;
 * @param quality        质量
 * @return 压缩后的Bitmap
 */
public static Bitmap qualityCompress(Bitmap bitmap, Bitmap.CompressFormat compressFormat, int quality) {
    ByteArrayOutputStream baos = new ByteArrayOutputStream();
    bitmap.compress(compressFormat, quality, baos);
    byte[] bytes = baos.toByteArray();
    Bitmap bm = BitmapFactory.decodeByteArray(bytes, 0, bytes.length);
    return bm;
}

In addition, introduce a third-party quality compression framework LibJpeg-turbo. Download address: https://libjpeg-turbo.org/ .

3. Format conversion

Commonly used image formats for Android are JPEG, PNG and WEBP.

JPEG is a widely used compression standard method for photos and videos.

  • Commonly used .jpg files are lossy compression
  • Background transparency is not supported
  • Suitable for large image compression with rich colors such as photos
  • Not suitable for logos, line drawings

PNG (Portable Network Graphics, PNG) is a lossless compressed bitmap graphics format that supports index, grayscale, RGB three color schemes, and alpha channel characteristics. Most of the cutting materials in Android development are in .png format.

  • Supports 256-color palette technology to produce small size files
  • Up to 48-bit true color images and 16-bit grayscale images are supported.
  • Support for transparency/translucency properties of alpha channel.
  • Gamma calibration information to support image brightness.
  • Support for storing additional text information to preserve information such as image name, author, copyright, creation time, annotations, etc.
  • Use lossless compression.
  • Asymptotic display and streaming read and write, suitable for quickly displaying the preview effect during network transmission and then displaying the whole picture.
  • Use CRC to prevent file errors.
  • The latest PNG standard allows multiple images to be stored in one file.

WEBP is a picture file format that provides both lossy and lossless compression. It is derived from the video encoding format VP8. It was developed by Google after purchasing On2 Technologies and released under the terms of the BSD license. Android 4.0+ supports WebP by default, and Android 4.2.1+ starts to support lossless WebP and WebP with alpha channel.

  • It has a better image data compression algorithm, which can bring a smaller image volume, and has image quality that is indistinguishable to the naked eye.
  • Lossless WebP images are 26% smaller than PNGs, and lossy WebP images are 25-34% smaller than JPEGs
  • Compared with encoding JPEG files, encoding WebP files of the same quality also requires more computing resources
  • Features lossless and lossy compression modes, alpha transparency, and animation
  • Excellent, stable and uniform conversion on both JPEG and PNG
  • Lossless WebP supports transparency and alpha channels, and lossy also supports under certain conditions

If compatibility is not considered, use lossy WebP images to save memory; use lossless WebP images to achieve the best performance.

In addition, to convert PNG and JPEG to WEBP format, you can select the image on Android Studio, right-click and select Converting Images to Webpto convert.

If you consider compatibility, then use JPG, which saves memory; use PNG, the best effect. There are many tools for PNG and JPEG conversion, so I won't mention them here.

2. Color Mode

The size of the image in memory, determined by the pixel and color mode.

Color mode can be said to be the number of bytes occupied by each pixel, which determines the fineness of the picture. Common color modes: RGB_565, ARGB_4444, ARGB_8888.

The meaning of ARGB: A stands for alpha channel, that is, transparency; R stands for Red, red; G stands for Green, green; B stands for Blue, blue.

RGB_565: R occupies 5 bits, G occupies 6 bits, B occupies 5 bits, a total of 16 bits, that is, 2 bytes;

ARGB_4444: A occupies 4 bits, R occupies 4 bits, G occupies 4 bits, and B occupies 4 bits, a total of 16 bits, that is, 2 bytes;

ARGB_8888: A occupies 8 bits, R occupies 8 bits, G occupies 8 bits, B occupies 8 bits, a total of 32 bits, that is, 4 bytes;

The memory occupied by RGB_565 and ARGB_4444 is smaller than that of ARGB_8888, and the fineness is also lower than that of ARGB_8888. However, with the naked eye, it is difficult to see the difference between the three.

/**
 * 设置图片色彩模式
 *
 * @param bitmap 位图
 * @param config 色彩模式,常用的:RGB_565,ARGB_4444ARGB_8888。
 * @return 返回图片大小
 */
public static long setConfig(Bitmap bitmap, Bitmap.Config config) {
    if (bitmap == null) {
        return 0;
    }
    Bitmap newBitmap = bitmap.copy(config, true);
    long size = newBitmap.getByteCount();
    return size;
}

3. Image caching

When displaying an image, it is often necessary to reload the image. For images that are loaded repeatedly, performance can be improved if caching is used.

1. Memory cache

When the interface refreshes, the images are reloaded. If you recreate a Bitmap object, it will consume a lot of performance. At this time, you can use LruCache to cache the Bitmap object.

LruCache<String, Bitmap> lruCache;
public void test(View view) {
    long maxMemory = Runtime.getRuntime().maxMemory();
    int cacheSize = (int) (maxMemory / 8);
    if (lruCache == null) {
        lruCache = new LruCache<String, Bitmap>(cacheSize){  
        //必须重写此方法,来测量Bitmap的大小  
        @Override  
        protected int sizeOf(String key, Bitmap value) {  
            return value.getRowBytes() * value.getHeight();  
        };         
    }
    Bitmap bitmap = lruCache.get("T");
    if (bitmap == null) {
        bitmap = ToolBitmap.sizeCompress(getResources(), R.mipmap.wangyiyun_icon, 400, 400);
        lruCache.put("T", bitmap);
    }
    ImageView img = findViewById(R.id.img);
    img.setImageBitmap(bitmap);
}

LruCache uses the Lru algorithm (the most recently used has the highest priority, the least used has the lowest priority), and uses LinkedHashMap to access objects. After each data is fetched, it is placed at the end of the linked list; each time data is added, it is also placed At the end of the linked list, check the cache size at the same time, if it exceeds the threshold, remove the data at the top of the linked list.

2. File cache

When the Activity is restarted, it may be necessary to display the previous pictures. At this time, file caching is required. We use the third-party DiskLruCache.

Github address: https://github.com/JakeWharton/DiskLruCache

Use reference: https://www.jianshu.com/p/0c56dc217917

3. Bitmap multiplexing

Bitmap reuse: Bitamps that are no longer in use can be allocated to other images.

Through bitmap multiplexing, performance problems caused by frequent memory requests are reduced.

BitmapFactory.Options options = new BitmapFactory.Options();
options.inMutable = true;//必须设为true,否则不能被复用
//被复用的bitmap
Bitmap oldbitmap = BitmapFactory.decodeResource(getResources(),R.drawable.lance,options);

//复用oldbitmap
options.inBitmap = oldbitmap;
Bitmap newbitmap = BitmapFactory.decodeResource(getResources(),R.drawable.lance,options);

Before 4.4, the Bitmap format must be jpg, png, and inSampleSize is 1 for multiplexing, and the multiplexed bitmap has the same width and height as the multiplexed bitmap.

After 4.4: The memory of the reused Bitmap must be greater than or equal to the memory of the reused bitmap.

4. Oversized image loading

The core of very large image loading is BitmapRegionDecoder. BitmapRegionDecoder is mainly used to display a rectangular area of ​​the picture.

The use of BitmapRegionDecoder is simple:

//isShareable为false会复制一张图片,为true会共用
BitmapRegionDecoder Decoder = BitmapRegionDecodeBitmapRegionDecoder r.newInstance(is, false);
//获取指定区域的bitmap。mRect为区域的矩阵,mOptions位图片的配置
Bitmap mBitmap = mDecoder.decodeRegion(mRect, mOptions);

We usually load very large images using custom controls. There are two keys to custom controls:

  1. Use BitmapRegionDecoder to load images;
  2. Rewrite event monitoring to realize picture dragging;

The key code is posted below:

/**
 * 设置图片
 *
 * @param is 图片的输入流
 */
public void setImage(InputStream is) {
    mOptions.inJustDecodeBounds = true;//加载边框
    BitmapFactory.decodeStream(is, null, mOptions);//获取图片宽高
    mImageWidth = mOptions.outWidth;
    mImageHeight = mOptions.outHeight;
    mOptions.inMutable = true;//复用Bitmap TODO
    mOptions.inPreferredConfig = Bitmap.Config.RGB_565;//设置像素格式
    mOptions.inJustDecodeBounds = false;//加载图片
    try {
        mDecoder = BitmapRegionDecoder.newInstance(is, false);//isShareable为false会复制一张图片,为true会共用
    } catch (IOException e) {
        e.printStackTrace();
    }
    requestLayout();//重新布局
}

@Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
    super.onMeasure(widthMeasureSpec, heightMeasureSpec);
    if (mDecoder == null) {
        return;
    }

    mViewWidth = getMeasuredWidth();//测量控件的宽
    mViewHeight = getMeasuredHeight();//测量控件的高
    mSacle = (float) mViewWidth / mImageWidth;

    //获取加载区域
    mRect.left = 0;
    mRect.top = 0;
    mRect.right = mImageWidth;
    mRect.bottom = (int) (mViewHeight / mSacle);
}

@Override
protected void onDraw(Canvas canvas) {
    super.onDraw(canvas);
    if (mDecoder == null) {
        return;
    }
    mOptions.inBitmap = mBitmap;//复用mBitmap
    mBitmap = mDecoder.decodeRegion(mRect, mOptions);
    Matrix matrix = new Matrix();//通过矩阵缩放
    matrix.setScale(mSacle, mSacle);//设置水平和垂直方向的缩放
    canvas.drawBitmap(mBitmap, matrix, null);
}

/**
 * 重写onScroll,调整图片的矩阵显示区域
 */
@Override
public boolean onScroll(MotionEvent e1, MotionEvent e2, float distanceX, float distanceY) {
    mRect.offset(0, (int) distanceY);//垂直滑动图片
    if (mRect.bottom > mImageHeight) {
        mRect.top = (int) (mImageHeight - mViewHeight / mSacle);
        mRect.bottom = mImageHeight;
    }
    if (mRect.top < 0) {
        mRect.top = 0;
        mRect.bottom = (int) (mViewHeight / mSacle);
    }
    invalidate();//重绘
    return false;
}

The demo has been put on gitee, and interested students can download it!

Previous: Performance Optimization 03-Memory Optimization

Next: Performance Optimization 05-Power Saving Optimization

Guess you like

Origin http://43.154.161.224:23101/article/api/json?id=325559396&siteId=291194637