preface
After a year, I found myself slack and didn't blog much. I also wanted to write the problems encountered in the project, but I didn't put them into action. Recently, I reconstructed some bad code of the project. When I'm free, it's also time to share the problems encountered in the project with you.Well, after nagging, today we will mainly talk about image compression. In Android development, we will inevitably deal with images. Among them, image compression is a common and thorny problem. In the process of processing, we need to pay attention to the problems of distortion and memory: when the image mosaic is completed, the business or test will come to the door; Android loads a large number of bitmaps, resulting in memory overflow. Now let's talk about some basic concepts of Bitmap.
Bitmap storage format
The object for Android to load pictures is the old-fashioned bitmap. Bitmap is a bitmap composed of pixels. How is it stored in memory? There are several storage methods for bitmap pixels, corresponding to bitmap Enumeration values in config:- ALPHA_8:
Save only transparency, not color. Each pixel is stored as a single translucent (alpha) channel. One pixel occupies one byte, which is not commonly used. After creating this type of diagram, you cannot draw colors on it. - RGB_565:
Only the color value is stored, and the transparency is not stored (alpha channel is not supported). By default, it is opaque. RGB occupies 5, 6 and 5 bits respectively, and one pixel occupies 16 bits and 2 bytes. This configuration is appropriate when using opaque bitmaps that do not require high color fidelity. - ARGB_4444:
ARGB is stored in 4 bits, and 16 bits of 1 pixel account for 2 bytes. This type of picture configuration leads to poor image quality. ARGB is recommended_ 8888. - ARGB_8888:
ARGB is stored in 8 bits, and 32 bits of one pixel account for 4 bytes. Each channel (RGB and alpha are translucent) is stored with 8-bit accuracy (256 possible values). The configuration is flexible and the picture is very clear. This method should be used as much as possible, which takes up more memory. - RGBA_F16:
Each channel (RGB and translucent alpha) is stored as a half precision floating-point value. Each pixel is stored on 8 bytes. It is very suitable for wide color gamut widescreen and HDR (high dynamic range pictures). It occupies the highest memory, so the display effect is also very good (it can only be used above API26). - HARDWARE:
Hardware bitmap, whose pixel data is stored in video memory, and optimizes the scene where the picture is only drawn on the screen. In short, it saves only one copy of the picture memory on the GPU without the copy of the application itself. In this way, theoretically, when loading a picture through hardware, the memory occupation can be half less than the original, because the pixel data is in the video memory, and exceptions will occur when accessing the pixel data in some scenes. See details for details Hardware bitmap.
Bitmap memory calculation method
We talked about several ways of Bitmap and the bytes occupied by pixels. What is the size of a picture Bitmap in memory? Here we take the jpg format picture with 500 * 313 pixels as an example:
Here we see that the file size is 34.1KB, so what is the size of loading this picture into memory? BitmapFactory of Android provides us with several ways to load pictures:
- BitmapFactory.decodeResource(): load the bitmap from the resource file by id
- BitmapFactory.decodeFile(): load the incoming file path, such as the file in sd card
- BitmapFactory.decodeStream(): loads pictures from the input stream
- BitmapFactory.decodeByteArray(): load pictures from byte array
500* 313* (32/8)B = 626000B = 0.6MB
Bitmap bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.test); Log.e(TAG, "Original size: " + SampleUtils.getBitmapSize(bitmap)); BitmapFactory.Options options1 = new BitmapFactory.Options(); options1.inPreferredConfig = Bitmap.Config.RGB_565; // options1.inDensity = 160; // options1.inScaled = false; Bitmap bitmap1 = BitmapFactory.decodeResource(getResources(), R.drawable.test, options1); Log.e(TAG, "RGB_565: " + SampleUtils.getBitmapSize(bitmap1) + " inTargetDensity=" + bitmap1.getDensity() + " width=" + bitmap1.getWidth() + " height=" + bitmap1.getHeight() + " totalSize=" + bitmap1.getWidth() * bitmap1.getHeight() * 2); BitmapFactory.Options options2 = new BitmapFactory.Options(); options2.inPreferredConfig = Bitmap.Config.ARGB_8888; // options2.inDensity = 160; // options2.inScaled = false; Bitmap bitmap2 = BitmapFactory.decodeResource(getResources(), R.drawable.test, options2); Log.e(TAG, "ARGB_8888: " + SampleUtils.getBitmapSize(bitmap2) + " inTargetDensity=" + bitmap2.getDensity() + " width=" + bitmap2.getWidth() + " height=" + bitmap2.getHeight() + " totalSize=" + bitmap2.getWidth() * bitmap2.getHeight() * 4);
Get Bitmap size:
The log printed above is as follows:/** * Get the size of the bitmap */ public static int getBitmapSize(Bitmap bitmap) { //API 19 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) { return bitmap.getAllocationByteCount(); } //API 12 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.HONEYCOMB_MR1) { return bitmap.getByteCount(); } //In the lower version, the byte x height of one line is used return bitmap.getRowBytes() * bitmap.getHeight(); }
Infer from the log
- BitmapFactory. Options. The default parameter of inpreferredconfig should be bitmap Config. ARGB_ 8888. Generally, this parameter is the format we set to store pixels, so we can set its parameters to reduce memory, but there will be cases that do not meet the configuration. See details for details Android inpreferredconfig parameter analysis;
- Generally, picture memory size = original width × Original height × Bytes of pixels
Sort out and compare the above two pictures, and load jpg format pictures with a resolution of 500 * 313 under the dpi of 480:
Storage format | drawable directory | width(px) | height(px) | Number of bytes per pixel | Memory size |
---|---|---|---|---|---|
RGB_565 | drawable | 1500 | 939 | 2 | 2817000(2.68MB) |
RGB_565 | drawable-xxhdpi | 500 | 313 | 2 | 313000(0.30MB) |
ARGB_8888 | drawable | 1500 | 939 | 4 | 5634000(5.37MB) |
ARGB_8888 | drawable-xxhdpi | 500 | 313 | 4 | 626000(0.60) |
Here, in the same storage format and different drawable directories, the resolution (width * height) of the pictures is different. When the pictures in the drawable directory are loaded into the memory, the width and height become three times of the original, and the resolution becomes nine times of the original, so the memory becomes nine times of the original. Therefore, we can guess that the size of the picture memory is related to the drawable directory in which the pictures are stored, By checking the source code of decodeResource(), if no Options parameter is passed in, the default will be generated. Finally, the method of decodeResourceStream() will be called:
/** * Decode a new Bitmap from an InputStream. This InputStream was obtained from * resources, which we pass to be able to scale the bitmap accordingly. */ public static Bitmap decodeResourceStream(Resources res, TypedValue value, InputStream is, Rect pad, Options opts) { if (opts == null) { opts = new Options(); } if (opts.inDensity == 0 && value != null) { //It can be understood that the picture is placed in the dpi corresponding to drawable final int density = value.density; if (density == TypedValue.DENSITY_DEFAULT) { opts.inDensity = DisplayMetrics.DENSITY_DEFAULT; } else if (density != TypedValue.DENSITY_NONE) { opts.inDensity = density; } } if (opts.inTargetDensity == 0 && res != null) { //dpi of mobile phone opts.inTargetDensity = res.getDisplayMetrics().densityDpi; } return decodeStream(is, pad, opts); }
It is explained here that decodeResourceStream() internally adapts the density of Bitmap, and then calls decodeStream(). The decoding process of Bitmap is actually completed in the native layer and tracked to bitmapfactory Cpp#nativedecodestream(), the relevant scaling codes are as follows:
if (env->GetBooleanField(options, gOptions_scaledFieldID)) { const int density = env->GetIntField(options, gOptions_densityFieldID); const int targetDensity = env->GetIntField(options, gOptions_targetDensityFieldID); const int screenDensity = env->GetIntField(options, gOptions_screenDensityFieldID); if (density != 0 && targetDensity != 0 && density != screenDensity) { scale = (float) targetDensity / density; } } ... int scaledWidth = decoded->width(); int scaledHeight = decoded->height(); if (willScale && mode != SkImageDecoder::kDecodeBounds_Mode) { scaledWidth = int(scaledWidth * scale + 0.5f); scaledHeight = int(scaledHeight * scale + 0.5f); } ... if (willScale) { const float sx = scaledWidth / float(decoded->width()); const float sy = scaledHeight / float(decoded->height()); bitmap->setConfig(decoded->getConfig(), scaledWidth, scaledHeight); bitmap->allocPixels(&javaAllocator, NULL); bitmap->eraseColor(0); SkPaint paint; paint.setFilterBitmap(true); SkCanvas canvas(*bitmap); canvas.scale(sx, sy); canvas.drawBitmap(*decoded, 0.0f, 0.0f, &paint); }
At a glance, the origin of the scaling value scale:
scale = (float) targetDensity / density;
The dpi values of devices corresponding to different drawable directories are listed here:
Different directories | drawable | drawable-ldpi | drawable-mdpi | drawable-hdpi | drawable-xhdpi | drawable-xxhdpi |
---|---|---|---|---|---|---|
dpi of corresponding equipment | 160 | 120 | 160 | 240 | 320 | 480 |
- Height of new drawing = height of original drawing * (dpi of equipment / dpi corresponding to directory)
- Width of new drawing = width of original drawing * (dpi of equipment / dpi corresponding to directory)
- drawable corresponds to 160dpi, height of new drawing: 1500 = 500 * (480 / 160); Width of new drawing: 939 = 313 * (480 / 160).
- Drawable xxhdpi corresponds to 480dpi, height of new drawing: 500 = 500 * (480 / 480); Width of new drawing: 313 = 313 * (480 / 480).
If you want to further verify this conclusion, you can use a test machine with a dpi of 240 to test the control variables. There is no comparison here, so we say that the memory size of the previous picture = original width × Original height × The number of bytes occupied by pixels is not quite right. It is also related to the dpi of the device and different resource directories. Specifically, the pictures located in different resource directories of res will undergo a resolution conversion before calculating the size when loaded into memory. Final calculation method:
Bitmap memory usage ≈ original width × Original height × (device dpi / resource directory corresponds to dpi)^2 × Bytes occupied by pixels
Since the calculation method of loading pictures into memory in res is analyzed, is it the same calculation method for loading pictures of other resources into memory? Now let's analyze the decodeFile() method. Its source code is as follows:
public static Bitmap decodeFile(String pathName, Options opts) { validate(opts); Bitmap bm = null; InputStream stream = null; try { stream = new FileInputStream(pathName); bm = decodeStream(stream, null, opts); } catch (Exception e) { Log.e("BitmapFactory", "Unable to decode stream: " + e); } finally { if (stream != null) { try { stream.close(); } catch (IOException e) { } } } return bm; }
Internally, a FileInputStream is created according to the file path. Finally, like the decodeResource() method, the decodeStream() method is called to decode the image. The difference is that there is no resolution conversion, so the calculation method of the image memory size should be our original formula: memory size = original width × Original height × The number of bytes occupied by pixels is tested below (apply for storage permission first):
File file = new File(Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS), "/TestPath/test.jpg"); BitmapFactory.Options options3 = new BitmapFactory.Options(); options3.inPreferredConfig = Bitmap.Config.ARGB_8888; Log.e(TAG, "ARGB_8888: " + SampleUtils.getBitmapSize(bitmap3) + " inTargetDensity=" + bitmap3.getDensity() + " width=" + bitmap3.getWidth() + " height=" + bitmap3.getHeight() + " totalSize=" + bitmap3.getWidth() * bitmap3.getHeight() * 4);
The results are as follows:
Here is only one experiment. You can also repeat this operation with another picture. The results are the same: memory size = original width × Original height × Number of bytes occupied by pixels. In addition, the calculation methods loaded into memory in other ways are the same, such as network resources (essentially downloaded to mobile phone storage), assert directory, SD card resources, etc. So here we come to the conclusion that only the image resources in res will convert the resolution and use the new resolution to calculate the memory size, while other resources use the original image resolution to calculate the memory size. The previous long speech is mainly to summarize these points:
- For picture resources in non res directory, such as local file pictures, network pictures, etc., Bitmap memory occupation is ≈ original width × Original height × × Bytes occupied by pixels
- For picture resources in different drawable directories under res directory, Bitmap memory occupation is ≈ original width × Original height × (device dpi / resource directory corresponds to dpi)^2 × Bytes occupied by pixels
Here, I have to say bitmapfactory Some parameters of options class and their meanings:
type | parameter | significance |
---|---|---|
boolean | inJustDecodeBounds | If true, the bitmap object will not be returned after decoding, but the bitmap width and height will be returned to options Outwidth and options Outheight; Otherwise, return. It is mainly used to obtain the size of the decoded bitmap without loading the bitmap into memory, wasting memory space. |
boolean | inMutable | If it is true, it means that the Bitmap of the variable attribute is returned; otherwise, it is immutable |
boolean | inPreferredConfig | Decode according to the specified config, such as bitmap Config. RGB_ 565 et al |
boolean | inSampleSize | If the value is greater than 1, the Bitmap that occupies less memory will be returned in proportion during decoding. For example, a value of 2 scales the width and height by half. This value is useful for most image compression. |
boolean | inScaled | If it is true, and neither indestinity nor inTargetDensity is 0, the image will be scaled according to inTargetDensityl during loading, and the drawing does not depend on the scaling attribute of the image itself. |
boolean | inDensity | The density of Bitmap itself, which defaults to the dpi corresponding to the drawable directory where the image is located |
boolean | inTargetDensity | The density used in the bitmap draw process adopts the dpi of the current device by default, the same as inscreen density |
Image file storage format
Above, we talked about the calculation method of image loading into memory. Now let's take a look at the storage size of image files. There are several common image file formats: JPEG (JPG), PNG and WEBP.We can understand them as containers of pictures. They convert the information of each pixel of the original image into another data format through the corresponding compression algorithm, so as to achieve the purpose of compression and reduce the size of picture files.
In a word, these formats are different compression algorithms, corresponding to bitmap Compressformat: the generated picture uses the specified picture storage format
- Bitmap.CompressFormat.JPEG:
JPEG compression algorithm is a lossy compression format, which will change the original image quality in the compression process. The worse the image quality, the greater the damage to the original image quality, but the resulting file is relatively small, and JPEG does not support transparency. When transparency pixels are encountered, it will be filled with black background. - Bitmap.CompressFormat.PNG:
Using PNG algorithm, it is a lossless compression format that supports transparency and has rich color display effects. Even in the case of compression, it can not reduce the image quality. - Bitmap.CompressFormat.WEBP:
WEBP is a picture file format that provides both lossy compression and lossless compression. When 14 < = API < = 17, WEBP is a lossy compression format and does not support transparency. After api18, WEBP is a lossless compression format and supports transparency. When lossy compression, under the same quality, the picture volume of WEBP format is 40% smaller than JPEG, But the encoding time is eight times longer than JPEG. In lossless compression, lossless WEBP images are 26% smaller than PNG compression, but the compression time of WEBP is 5 times that of PNG format.
Image compression method
Previously, we talked about how to calculate the size of bitmap in memory. For example, we need to download a 1920 * 1080 resolution image from the network and use ARGB_8888 mode shows the memory size of this picture:A picture should take up so much memory, and the memory of the mobile phone is limited. If we don't control it, what kind of scene will it be when loading dozens of pictures? The final result must be that OOM flash back, which is obviously unacceptable, so we should handle these pictures more carefully.1920*1080*4B = 7.91MB
According to the above and related calculation methods, the memory optimization method of Bitmap is mainly to store the pictures in the appropriate drawable directory, adopt the appropriate storage format to reduce the number of bytes occupied by pixels, reduce the resolution of pictures, and reuse and cache Bitmap, that is:
- Store the pictures in the appropriate drawable directory
- Reduce the size of each pixel
- Reduce resolution
- Reuse and caching
RGB_565 compression
This is achieved by setting the memory size occupied by pixels. ARGB is generally not recommended_ 4444, because the picture quality is really bold. If there is no requirement for transparency, it is suggested to change it to RGB_ Compared to ARGB, 565_ 8888 will save half of the memory overhead./** * RGB565 compress * * @param context context * @param id Picture resource id * @return Compressed image Bitmap */ public static Bitmap compressByRGB565(Context context, int id) { BitmapFactory.Options options = new BitmapFactory.Options(); options.inPreferredConfig = Bitmap.Config.RGB_565; return BitmapFactory.decodeResource(context.getResources(), id, options); }
Mass compression
Change the bit depth and transparency of the picture on the premise of maintaining the pixels, and reduce the quality and compress the file by erasing the pixels near the attachment of a certain point. The memory for loading Bitmap will not be reduced, and the file will become smaller. It is used to compress the image uploaded by the server or save the local image file./** * Mass compression * * @param bmp Picture bitmap * @param quality The quality parameter 0-100100 is uncompressed and PNG is lossless compression. This parameter is for bitmap CompressFormat. Png invalid * @param file Save the compressed picture file */ public static void qualityCompress(Bitmap bmp, int quality, File file) { ByteArrayOutputStream bos = new ByteArrayOutputStream(); // Store the compressed data in bos bmp.compress(Bitmap.CompressFormat.JPEG, quality, bos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(bos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
Size compression (zoom compression)
Change the size of the picture, that is, compress the pixels of the width and height of the picture. From the above calculation formula, we can know that this will reduce the memory occupation of the picture bitmap, thus reducing the probability of OOM to a certain extent. But here we need to note that if the compression ratio is too large, the picture will be seriously distorted due to the reduction of pixels, and finally the picture will change from HD to mosaic./** * Zoom compression * * @param bmp Picture bitmap * @param radio Scale: the larger the value, the smaller the picture size * @param file Save the compressed picture file */ public static void scaleCompress(Bitmap bmp, int radio, File file) { //Set zoom ratio Bitmap result = Bitmap.createBitmap(bmp.getWidth() / radio, bmp.getHeight() / radio, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(result); RectF rectF = new RectF(0, 0, bmp.getWidth() * 1.0f / radio, bmp.getHeight() * 1.0f / radio); //Place the original picture on the scaled rectangle canvas.drawBitmap(bmp, null, rectF, null); ByteArrayOutputStream bos = new ByteArrayOutputStream(); // Store the compressed data in bos bmp.compress(Bitmap.CompressFormat.JPEG, 100, bos); try { FileOutputStream fos = new FileOutputStream(file); fos.write(bos.toByteArray()); fos.flush(); fos.close(); } catch (Exception e) { e.printStackTrace(); } }
Sample rate compression
In fact, the principle of sample rate compression is the same as that of size compression. They both compress the pixels of picture width and height by reducing the picture size. Here, we make full use of the parameter settings in the Options class (refer to the table above):- inSampleSize: sampling rate, which is an integer and is the nth power of 2. n can be 0, that is, the sampling rate is 1. The size of the processed image is consistent with the original image. When the sampling rate is 2, that is, the width and height are 1 / 2 of the original, the pixel is 1 / 4 of the original, and its occupied memory is also 1 / 4 of the original. When the set sampling rate is less than 1, the effect is the same as 1. When the inSampleSize set is greater than 1 and not an index of 2, the system will take down the value of the index closest to 2.
- inJustDecodeBounds: when set to true, BitmapFactory will only parse the original width / height information of the picture and will not really load the picture. This setting is amazing.
The advantage of sampling rate compression is that it will not read large pictures into memory first, which greatly reduces the use of memory, and it is not necessary to consider the release of large pictures after reading them into memory. The disadvantage is that the inSampleSize parameter can not guarantee the quality of pictures because it is an integer./** * Sample rate compression * * @param context context * @param id Picture resource id * @param destW Target width size * @param destH Target size high * @return Compressed image Bitmap */ public static Bitmap sampleSizeCompress(Context context, int id, int destW, int destH) { Bitmap bm = null; int inSampleSize = 1; //First sampling BitmapFactory.Options options = new BitmapFactory.Options(); //Setting this property to true will only load the width, height and type information of the picture, and will not load the specific pixels of the picture options.inJustDecodeBounds = true; bm = BitmapFactory.decodeResource(context.getResources(), id, options); Log.e(TAG, "sampleSizeCompress--Width of picture before compression:" + options.outWidth + "--Height of picture before compression:" + options.outHeight + "--Picture size before compression:" + options.outWidth * options.outHeight * 4 / 1024 + "kb"); int iWidth = options.outWidth; int iHeight = options.outHeight; //Adjust the zoom scale until the width and height meet our requirements while (iWidth > destW || iHeight > destH) { //If the zoom ratio of either side of the width and height does not meet the requirements, continue to increase the zoom ratio //inSampleSize should be the nth power of 2. If the number set for inSampleSize is not the nth power of 2, the system will take it nearby //The width and height are both 1 / 2 of the width and height of the original drawing, and the memory is about 1 / 4 of the original inSampleSize = inSampleSize * 2; iWidth = iWidth / inSampleSize; iHeight = iHeight / inSampleSize; } //Start of secondary sampling //During secondary sampling, I need to load the picture completely and display it. The injustdecodeboundaries property should be set to false options.inJustDecodeBounds = false; options.inSampleSize = inSampleSize; //Pixel settings, default Config. ARGB_ eight thousand eight hundred and eighty-eight //bitmapFactoryOptions.inPreferredConfig = Bitmap.Config.RGB_565; bm = BitmapFactory.decodeResource(context.getResources(), id, options); Log.e(TAG, "sampleSizeCompress--Width of picture:" + bm.getWidth() + "--Height of picture:" + bm.getHeight() + "--Picture size:" + bm.getWidth() * bm.getHeight() * 4 / 1024 + "kb"); //Return compressed photos return bm; }
The process of image compression may be time-consuming. Please do not put it into the main thread. You can use the thread pool and then call back the result to the main thread. There is no code here. Of course, you can also use these compression methods in combination. Making full use of their respective advantages is a good way out. For example, quality compression will not reduce the memory consumption when converting pictures to bitmap. In order to avoid OOM, we suggest to compress the appropriate size first, and then further quality compression.
Now the well-known three-party compression library is known as the one closest to the image compression of wechat circle of friends Luban The core of it is the algorithm of sampling rate. Interested students can go and have a look.
Wechat sharing
After introducing the compression method, here is a case to share: the size of the picture shared by wechat. Why sometimes wechat sharing is unsuccessful? The official documents say that the sharing of pictures is limited to 10M. In fact, I can't share 1M pictures.First, I looked at it Wechat sharing documents , sort out some things: there are two kinds of wechat image sharing: one is the sharing of local image path (10M), and the other is the sharing of image byte stream. This is shared to wechat through the opening activity of intent. Since the intention is involved, Android developers should know that the data transmitted by intent is limited. The official document says that it can only transmit 1M data, However, considering various complex situations and mobile phone models, 512KB is a general value. The way we handle this is to compress the image to 512KB through cyclic quality compression. Here, 512KB refers to the file size, not the memory occupied by the previous Bitmap. Don't get confused.
The dependency introduced by thread pool here is/** * Cyclic quality compression is convenient to support wechat sharing * * Note: wechat restricts the sharing of pictures below 10M, but wechat sharing of pictures will involve starting a new activity, which involves the transmission of Intent values, and the transmission of Intent values has size restrictions (different models), * Therefore, in order to adapt to all models, the picture size here should be less than 512KB * PS:If you want to transfer a large image without compression, you can use imagePath. First save the image locally (involving storage permission), and then directly transfer the address of the image. * * @param bitmap Image Bitmap to be compressed * @param callBack Callback compressed picture byte array */ public static void asyncCompressImageForWXShare(final Bitmap bitmap, final OnBitmap2ByteCallBack callBack) { ThreadUtils.getCachedPool().execute(new Runnable() { @Override public void run() { ByteArrayOutputStream byteAOStream = new ByteArrayOutputStream(); // Quality compression method, where 100 means no compression, and the compressed data is stored in byteao stream bitmap.compress(Bitmap.CompressFormat.PNG, 100, byteAOStream); byte[] bytes = byteAOStream.toByteArray(); int options = 100; // Cycle to judge whether the compressed image is larger than 512KB and continue compression while (bytes.length / 1024 > 512 && options >= 10) { // 10% less each time options -= 10; if (options < 0) { options = 0; } // Reset byteAOStream, otherwise it will accumulate byteAOStream.reset(); // Store the compressed data in byteao stream bitmap.compress(Bitmap.CompressFormat.JPEG, options, byteAOStream); // Convert stream to byte array bytes = byteAOStream.toByteArray(); } Log.e(TAG, "Wechat shared picture byte array size:" + bytes.length); final byte[] finalBytes = bytes; ThreadUtils.runOnUiThread(new Runnable() { @Override public void run() { callBack.onBitmap2ByteCallBack(finalBytes); } }); } }); } /** * Callback for asynchronous conversion of picture byte stream */ public interface OnBitmap2ByteCallBack { void onBitmap2ByteCallBack(byte[] imageBytes); }
implementation 'com.blankj:utilcode:1.30.6'
summary
In this article, we mainly talk about some important concepts, compression methods and memory optimization of Bitmap. The above theories are based on the most original way of loading pictures, and do not involve three powerful picture processing libraries, such as Glide and fresco. These libraries must have been well optimized for picture loading and memory, We don't have to spend too much energy to deal with these problems, but it's necessary for us to understand the most basic things and have an overall understanding. It's best to see how excellent open source libraries deal with these problems, which will definitely improve our ability.Finally, what I want to say is that there are many strange businesses and problems. Different people will have different situations. We just need to find some clues from them and combine our own thinking. I believe the problem will be solved in the end. Because I have been dealing with image Bitmap recently, I consulted all kinds of materials, accumulated a lot of experience, and then combined with the problems I encountered, I wrote this article.
Well, if you are wrong or have any doubts, please leave a message in the comment area, and please don't be reluctant to give up your praise 👍 oh