1, Introduce
As a large memory consumer, pictures have always been the key object that developers try to optimize. Bitmap's memory is located in native before 3.0, then changed to jvm, and then changed back to native after 8.0. Each jvm process has a memory limit, while native has no limit (not without impact, at least not oom). Therefore, moving bitmap, a large memory user, to native may be a dream of many people, but the management and implementation of native is obviously more complex than that of jvm. Unless there is a ready-made implementation, few people move this piece.
2, Getting to know Bitmap
Bitmap is a final class, so it cannot be inherited. Bitmap has only one construction method, and the construction method is not modified by any access modifier, that is, the construction method is friendly, but Google says that the construction method of bitmap is private (private), which feels a little lax. Anyway, in general, we can't directly create a bitmap object through the construction method.
Bitmap is one of the most important classes in image processing in Android system. Bitmap can obtain image file information, cut, rotate, scale and compress the image, and save the image file in the specified format.
3, Create a Bitmap object
Since you can't create a Bitmap directly through the construction method, how can you create a Bitmap object. Generally, we can use the static method createBitmap() of Bitmap and the static method decode series of BitmapFactory to create Bitmap objects.
- Static method of Bitmap createBitmap()
- BitmapFactory's decode series static methods
4, Color configuration information and compression mode information of Bitmap
There are two internal enumeration classes in Bitmap: config and CompressFormat. Config is used to set color configuration information and CompressFormat is used to set compression mode.
Config resolution:
- Bitmap.Config.ALPHA_8: Color information only consists of transparency, accounting for 8 bits.
- Bitmap.Config.ARGB_4444: color information consists of transparency and R (Red), G (Green) and B (Blue). Each part occupies 4 bits, accounting for 16 bits in total.
- Bitmap.Config.ARGB_8888: color information consists of transparency and R (Red), G (Green) and B (Blue). Each part occupies 8 bits, accounting for 32 bits in total. It is the default color configuration information of bitmap, and it also takes up the most space.
- Bitmap.Config.RGB_565: color information consists of R (Red), G (Green) and B (Blue). R accounts for 5 bits, G for 6 bits and B for 5 bits, accounting for 16 bits in total.
Generally, when we optimize Bitmap, we usually use Bitmap when we need to do performance optimization or prevent OOM (Out Of Memory) Config. RGB_ 565 this configuration because Bitmap Config. ALPHA_ 8 only transparency, displaying general pictures is meaningless, Bitmap Config. ARGB_ 4444 the picture displayed is not clear, Bitmap Config. ARGB_ 8888 takes up the most memory.
CompressFormat resolution:
- Bitmap.CompressFormat.JPEG: refers to image compression with jpeg compression algorithm. The compressed format can be ". jpg" or ". jpeg", which is a lossy compression.
- Bitmap.CompressFormat.PNG: refers to image compression with png compression algorithm. The compressed format can be ". png", which is a lossless compression.
- Bitmap.CompressFormat.WEBP: refers to image compression with webp compression algorithm. The compressed format can be ". webp", which is a lossy compression. Under the condition of the same quality, the volume of webp format image is 40% smaller than that of JPEG format image. The drawback is that the encoding time of webp format image is "8 times longer than JPEG format image".
5, Bitmap picture performance optimization
Method 1: use inSampleSize sampling rate compression, size compression:
BitmapFactory is through BitmapFactory Options operates on the image, and then generates a Bitmap object from the operated image, or saves the operated image with an existing Bitmap. When it cannot be saved, null will be returned.
BitmapFactory. Common fields in options are:
Use options Injustdecodebounds to get the original size, and then use options as needed Insamplesize to sample the image close to the view size.
BitmapFactory can bring an Options when decoding pictures. It has some useful functions, such as:
- inTargetDensity: pixel density drawn on the target Bitmap.
- inSampleSize: compress the image. This value is an int. when it is less than 1, it will be treated as 1. If it is greater than 1, the width and height of the bitmap will be reduced and the resolution will be reduced according to the proportion (1 / inSampleSize). When it is greater than 1, this value will be treated as an integer power of 2 or an integer power close to 2. For example, if width=100, height=100, inSampleSize = 2 (set to 2), the bitmap will be processed as width=50, height=50, width and height will be reduced to 1 / 2, and the number of pixels will be reduced to 1 / 4
- inJustDecodeBounds: if set to true, it means that the Bitmap object information is obtained, but its pixels are not loaded into memory. Sometimes you can use this just to get the size of the picture without directly loading the whole picture.
- inPreferredConfig: the color configuration information of the Bitmap object. By default, the Bitmap.config is used Config. ARGB_ 8888. In this mode, a pixel will occupy 4 bytes (8 + 8 + 8 + 8 + 8 = 32 bits (4 bytes)). For some pictures that do not require transparency or low picture quality, RGB can be used_ 565, a pixel will only occupy 2 bytes (5 + 6 + 5 = 16 bits (2 bytes)), which can save 50% of memory.
- Inpargeable and inputshareable need to be used together, bitmapfactory There are comments in the Java source code, which roughly means whether the bitmap can be recycled when the system memory is insufficient. It is a bit like a soft reference, but these two attributes have been ignored since 5.0, because the system thinks that decoding after recycling may actually lead to performance problems
- inBitmap: an officially recommended parameter, which means to reuse picture memory and reduce memory allocation. Before 4.4, only picture memory areas of the same size can be reused. After 4.4, as long as the original picture is larger than the picture to be decoded, it can be reused.
- inDensity: the density set for the Bitmap object. If inScaled is true (this is the default), and if the inDensity does not match the inTargetDensity, it will be scaled to match the inTargetDensity before the Bitmap object returns.
- inDither: whether to dither the image. The default value is false.
- inScaled: sets whether to scale.
- outHeight: the height of the Bitmap object.
- outWidth: the width of the Bitmap object.
Method 2: reasonably select the pixel format of Bitmap
The picture in ARGB8888 format occupies 4 bytes (8 + 8 + 8 + 8 + 8 + 8 = 32 bits (4 bytes)) per pixel, while RGB565 is 2 bytes (5 + 6 + 5 = 16 bits (2 bytes)). If it is ARGB8888, it is 4 bytes per pixel. If it is RGB565, it is 2 bytes.
format | describe |
---|---|
ALPHA_8 | There is only one alpha channel, and each pixel is stored in one byte (8 bits) |
ARGB_4444 | This is not recommended from API 13 because the quality is too poor |
ARGB_8888 | ARGB has four channels, and each pixel is stored in four bytes (32 bits) |
RGB_565 | Each pixel occupies 2 bytes, of which red occupies 5bit, green occupies 6bit and blue occupies 5bit |
ALPHA8 doesn't need to be used, because we can do it with any color.
Although ARGB4444 occupies only half of the memory of ARGB8888, it has been officially rejected.
ARGB8888, RGB565: ARGB will be used by default_ 8888. In this mode, a pixel will occupy 4 bytes, and RGB can be used for some pictures without transparency requirements or low picture quality requirements_ 565, a pixel will only occupy 2 bytes, which can save 50% of memory.
Method 3: put the pictures in the appropriate folder:
If the same picture is placed in different directories, it will generate bitmap s of different sizes. Because its width and height are scaled, the picture resources should be placed in high-density resource folders as much as possible, so as to save the memory cost of the picture. It is generally recommended to put it under xxhdpi. At present, the dpi is used in mainstream mobile phones, And when UI provides us with cut-off images, it should also be provided for high-density screen devices as far as possible
jpg is a lossy compressed image storage format, while png is a lossless compressed image storage format. Obviously, jpg will be smaller than png
The memory occupied by Bitmap actually depends on:
-
As we mentioned earlier, if it is ARGB8888, it means 4 bytes for a pixel (8 + 8 + 8 + 8 + 8 + 8 = 32 bits (4 bytes)); if it is RGB565, it means 2 bytes (5 + 6 + 5 = 16 bits (2 bytes));
-
The resource directory where the original files are stored (hdpi or xhdpi or xxhdpi can't be foolishly distinguished);
-
The density of the target screen (so under the same conditions, the memory consumed by Hongmi in terms of resources must be less than that of Samsung S6);
Mode 4: design a reasonable cache mechanism (memory cache, reuse pool, hard disk cache)
Memory cache: we can use the official LurCache that has been implemented for us;
Reuse pool: used to load the pictures discarded in the memory cache LRU but may be used immediately. We cache them in the reuse pool to prevent them from being recycled by the GC immediately;
Hard disk cache: implemented by Jake Wharton DiskLruCache , download address: https://github.com/JakeWharton/DiskLruCache
Why set a reuse pool?
Because in the official 8.0Android system, pictures are processed in the native layer, such as the recycling mechanism. We cannot intervene in the java layer. In order not to immediately hand over the pictures to the native layer for processing, we use the weak references in the reuse pool to temporarily save them in the java layer, so that we need to use the pictures just abandoned by the memory cache LRU, Instead of repeating the decode pictures and putting them into the LRU queue, the performance consumption is reduced.
6, Bitmap picture performance optimization code
Picture optimization requires three things:
- Pixel compression (format conversion)
- Memory reuse
- L3 cache design (memory, disk, network)
Pixel compression
// Used to optimize picture (pixel compression (format conversion)) public class ImageResize { // int maxW,int maxH -- defines the width and height of pixels public static Bitmap resizeBitmap(Context context, int id, int maxW, int maxH, boolean hasAlpha) { Resources resources = context.getResources(); BitmapFactory.Options options = new BitmapFactory.Options(); // Bitmap bitmap = BitmapFactory.decodeResource(resources,R.drawable.logo); // Only the relevant parameter information (width, height, etc.) is decoded, and the picture will not be generated options.inJustDecodeBounds = true; // Decoding action switch (on) BitmapFactory.decodeResource(resources, id, options); int w = options.outWidth; // The width of the true output of the picture int h = options.outHeight; // The true output of the picture is high // Set the zoom factor (see picture) options.inSampleSize = calcuteInSampleSize(w, h, maxW, maxH); if (!hasAlpha) { // Transparency is not required and RGB is used_ 565 (5 + 6 + 5 = 16 bits) options.inPreferredConfig = Bitmap.Config.RGB_565; } options.inJustDecodeBounds = false; // Close after processing // Return to the real picture after setting return BitmapFactory.decodeResource(resources, id, options); } // Calculate scaling factor // w. H: width and height of the original drawing // maxW,maxH: width and height after scaling 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; } }
Pixel compression + memory reuse
public class ImageResize { // int maxW,int maxH -- defines the width and height of pixels // reusable: memory reuse location 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(); // Bitmap bitmap = BitmapFactory.decodeResource(resources,R.drawable.logo); // Only the relevant parameter information (width, height, etc.) is decoded, and the picture will not be generated options.inJustDecodeBounds = true; // Decoding action switch (on) BitmapFactory.decodeResource(resources, id, options); int w = options.outWidth; // The width of the true output of the picture int h = options.outHeight; // The true output of the picture is high // Set the zoom factor (see picture) options.inSampleSize = calcuteInSampleSize(w, h, maxW, maxH); if (!hasAlpha) { // Transparency is not required and RGB is used_ 565 (5 + 6 + 5 = 16 bits) options.inPreferredConfig = Bitmap.Config.RGB_565; } options.inJustDecodeBounds = false; // Close after processing // Set to be reusable options.inMutable=true; // Set the memory location required for reuse options.inBitmap = reusable; // Return to the real picture after setting return BitmapFactory.decodeResource(resources, id, options); } // Calculate scaling factor // w. H: width and height of the original drawing // maxW,maxH: width and height after scaling 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; } }
The bitmap object applies for a piece of memory in memory. When bitmap2 needs to use it, it directly reuses the memory applied by bitmap (or directly overwrites the contents of bitmap)
Three level cache mechanism: memory cache, reuse pool and disk cache (memory, disk and network)
public class ImageCache { public static final String TAG = "ImageCache"; private static ImageCache instance; private Context context; public static ImageCache getInstance() { if (null == instance) { synchronized (ImageCache.class) { if (null == instance) { instance = new ImageCache(); } } } return instance; } // Memory cache private LruCache<String, Bitmap> memoryCache; // Disk cache private DiskLruCache diskLruCache; BitmapFactory.Options options = new BitmapFactory.Options(); // Reuse pool public static Set<WeakReference<Bitmap>> reuseablePool; // The parameter dir is the path of the last stored disk cache public void init(Context context, String dir) { this.context = context.getApplicationContext(); // synchronizedSet: a set with locks reuseablePool = Collections.synchronizedSet(new HashSet<WeakReference<Bitmap>>()); ActivityManager am = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE); int memoryClass = am.getMemoryClass();// Available memory provided by virtual machine (memory available by APP) memoryCache = new LruCache<String, Bitmap>(memoryClass / 8 * 1024 * 1024) {// 1 / 8 of the available memory is used for image caching (1 / 8 of the available memory of APP) /* * return value Occupied memory size */ @Override // The sizeOf method is used to calculate the picture size protected int sizeOf(String key, Bitmap value) { // For compatibility with Android before 3.0 (before and after 19) if (Build.VERSION.SDK_INT > Build.VERSION_CODES.KITKAT) { // Greater than 19 (can be compressed and multiplexed) return value.getAllocationByteCount(); } // 19 before reusing memory, only pictures of the same size can be used to reuse memory return value.getByteCount(); } @Override // After LruCache is full, some data will be extruded (extruded in oldValue) // newValue: the object put by the queue head // oldValue: the object extruded at the end of the queue protected void entryRemoved(boolean evicted, String key, Bitmap oldValue, Bitmap newValue) { // oldValue needs to be put into the reuse pool if (oldValue.isMutable()) { // Use WeakReference to associate with reference queue reuseablePool.add(new WeakReference<Bitmap>(oldValue, getReferenceQueue())); } else { oldValue.recycle(); } } }; try { // Open a directory on your phone // Parameter 3: 1 file diskLruCache = DiskLruCache.open(new File(dir), 1, 1, 10 * 1024 * 1024); } catch (IOException e) { e.printStackTrace(); } } // Reference queue ReferenceQueue referenceQueue; Thread clearReferenceQueue; boolean shutDown; // API for actively monitoring GC to speed up recycling (compatible with different Android versions) private ReferenceQueue<Bitmap> getReferenceQueue() { if (null == referenceQueue){ referenceQueue = new ReferenceQueue<Bitmap>(); // Open a single thread to scan the content scanned by GC in the reference queue and hand it over to the native layer for release clearReferenceQueue = new Thread(new Runnable() { @Override public void run() { while (!shutDown){ try { // remove with blocking function Reference<Bitmap> reference = referenceQueue.remove(); Bitmap bitmap = reference.get(); if (null!=bitmap && !bitmap.isRecycled()){ bitmap.recycle(); // Improve and speed up the recycling action (go to the native layer for recycling) } } catch (InterruptedException e) { e.printStackTrace(); } } } }); clearReferenceQueue.start(); } return referenceQueue; } /** * Add memory cache */ public void putBitmapToMemeory(String key,Bitmap bitmap){ memoryCache.put(key,bitmap); } public Bitmap getBitmapFromMemory(String key){ return memoryCache.get(key); } public void clearMemoryCache(){ memoryCache.evictAll(); } // Get content in reuse pool public Bitmap getReuseable(int w,int h,int inSampleSize){ // Ignore below 3.0 if (Build.VERSION.SDK_INT<Build.VERSION_CODES.HONEYCOMB){ return null; } Bitmap reuseable = null; // Through iterator Iterator<WeakReference<Bitmap>> iterator = reuseablePool.iterator(); while (iterator.hasNext()){ Bitmap bitmap = iterator.next().get(); if (null!=bitmap){ // Reusable if (checkInBitmap(bitmap,w,h,inSampleSize)){ reuseable = bitmap; iterator.remove(); Log.i(TAG, "Found in reuse pool"); break; }else { iterator.remove(); } } } return reuseable; } // Detect whether it can be reused private boolean checkInBitmap(Bitmap bitmap, int w, int h, int inSampleSize) { if (Build.VERSION.SDK_INT<Build.VERSION_CODES.KITKAT){ return bitmap.getWidth()==w && bitmap.getHeight()==h && inSampleSize == 1; } // If the scaling factor is greater than 1, it can be reused if (inSampleSize>=1){ w/=inSampleSize; h/=inSampleSize; } int byteCount = w*h*getPixelsCount(bitmap.getConfig()); return byteCount<=bitmap.getAllocationByteCount(); } /* * The number of bytes required to obtain different formats of pixels */ private int getPixelsCount(Bitmap.Config config) { if (config == Bitmap.Config.ARGB_8888){ return 4; } return 2; } // Processing of disk cache /* * Add disk cache */ public void putBitMapToDisk(String key,Bitmap bitmap){ DiskLruCache.Snapshot snapshot = null; OutputStream os = null; try { snapshot = diskLruCache.get(key); // If this file already exists in the cache, ignore it if (null==snapshot){ // If there is no such file, it will be generated DiskLruCache.Editor editor = diskLruCache.edit(key); if (null!=editor){ os = editor.newOutputStream(0); bitmap.compress(Bitmap.CompressFormat.JPEG,50,os); editor.commit(); } } } catch (IOException e) { e.printStackTrace(); }finally { if (null!=snapshot){ snapshot.close(); } if (null!=os){ try { os.close(); } catch (IOException e) { e.printStackTrace(); } } } } /* * Fetch from disk cache */ public Bitmap getBitmapFromDisk(String key,Bitmap reuseable){ DiskLruCache.Snapshot snapshot = null; Bitmap bitmap = null; try { snapshot = diskLruCache.get(key); if (null==snapshot){ return null; } // Get the file input stream and read the bitmap InputStream is = snapshot.getInputStream(0); // Decode pictures and write options.inMutable = true; options.inBitmap = reuseable; bitmap = BitmapFactory.decodeStream(is,null,options); if (null != bitmap){ memoryCache.put(key,bitmap); } } catch (IOException e) { e.printStackTrace(); }finally { if (null!=snapshot){ snapshot.close(); } } return bitmap; } }
Complete code
reference resources
Performance optimization topic 3 - memory optimization (picture L3 cache)
Play Android Bitmap