河北正规网站建设比较,制作网站需要哪些知识,工业互联网建设,乐昌门户网站图片优化是内存优化中很重要的一部分#xff0c;加载Bitmap时往往需要消耗大量的内存#xff0c;稍不注意就容易导致内存溢出#xff08;OOM#xff09;。
一、图片OOM问题产生
1、 一个页面一次加载过多图片#xff1b;
2、加载大图片没有进行压缩(尺寸#xff0c;质…图片优化是内存优化中很重要的一部分加载Bitmap时往往需要消耗大量的内存稍不注意就容易导致内存溢出OOM。
一、图片OOM问题产生
1、 一个页面一次加载过多图片
2、加载大图片没有进行压缩(尺寸质量)
3、列表页面加载大量bitmap没有使用缓存。
了解图片产生OOM问题的原因接下来我们将要通过这几个方面对图片进行优化在此之前我们还需要知道加载一张图片到APP中需要消耗多大的内存是什么计算的
二、获取Bitmap的大小
1、getByteCount()
getByteCount()方法是在API12加入的代表存储Bitmap的色素需要的最少内存。API19开始 getAllocationByteCount()方法代替了getByteCount()。
2、getAllocationByteCount()
API19之后Bitmap加了一个ApigetAllocationByteCount()代表在内存中为Bitmap分配的内存大小。
public final int getAllocationByteCount() {if (mBuffer null) {//mBuffer代表存储Bitmap像素数据的字节数组。return getByteCount();}return mBuffer.length;
}
3、getByteCount()与getAllocationByteCount()的区别
一般情况下两者是相等的通过复用Bitmap来解码图片如果被复用的Bitmap的内存比待分配内存的Bitmap大,那么getByteCount()表示新解码图片占用内存的大小并非实际内存大小实际大小是复用的那个Bitmap的大小getAllocationByteCount()表示被复用Bitmap真实占用的内存大小即mBuffer的长度。
三、Bitmap占用内存大小计算
Bitmap作为位图需要读入一张图片每一个像素点的数据其主要占用内存的地方也正是这些像素数据。对于像素数据总大小我们可以猜想为像素总数量 × 每个像素的字节大小而像素总数量在矩形屏幕表现下应该是横向像素数量 × 纵向像素数量
结合得到
Bitmap内存占用 ≈ 像素数据总大小 横向像素数量 × 纵向像素数量 × 每个像素的字节大小
注意上面的计算方法适用于网络和本地等图片计算不适用于加载APP项目的drawable和mipmap文件的图片。
在android源码中加载drawable和mipmap图片跟density有关而density和图片存放的资源文件的目录有关同一张图片放置在不同目录下会有不同的值 可以验证几个结论
1. 图片放在drawable中等同于放在drawable-mdpi中原因为drawable目录不具有屏幕密度特 性所以采用基准值即mdpi
2. 图片放在某个特定drawable中比如drawable-hdpi如果设备的屏幕密度高于当前drawable目 录所代表的密度则图片会被放大否则会被缩小放大或缩小比例 设备屏幕密度 / drawable目录所代表的屏幕密度因此关于Bitmap占用内存大小的公式
从之前
Bitmap内存占用 ≈ 像素数据总大小 横向像素数量 × 纵向像素数量 × 每个像素的字节大小
可以更细化为
Bitmap内存占用 ≈ 像素数据总大小 图片宽 × 图片高× (设备分辨率/资源目录分辨率)^2 × 每个像 素的字节大小
知道图片内存大小的计算后我们便可以从图片宽、高和每个像素点占用的字节数等方面对图片进行压缩优化处理。
四、图片存储优化
Android系统加载Bitmap给我们提供了很多API常用的BitmapFactory工厂类
Option 参数类
public boolean inJustDecodeBounds
如果设置为 true 在不获取图片不分配内存时可以返回图片的高度宽度信息。即设置为 true 在解码的时将不会返回 bitmap 只返回这个 bitmap 的尺寸。
public int inSampleSize
图片缩放的倍数 这个值是一个 int 当它小于1的时候将会被当做1处理如果大于1那么就会按照比例 1 / inSampleSize 缩小 bitmap 的宽和高、降低分辨率inSampleSize只能设置为2的倍数。
public int outWidth
获取图片的宽度值
public int outHeight
获取图片的高度值 表示这个 Bitmap 的宽和高一般和inJustDecodeBounds 一起使用来获得 Bitmap 的宽高但是不加载到内存。
public Bitmap.Config inPreferredConfig
设置解码器这个值是设置色彩模式默认值是 ARGB_8888 在这个模式下一个像素点占用4bytes空间一般对透明度不做要求的话一般采用 RGB_565 模式这个模式下一个像素点占用2bytes。
Bitmap类
bitmap.compress(Bitmap.CompressFormat.JPEG, 30, baos);
30 是压缩率表示压缩70%; 如果不压缩是100表示压缩率为0。
BitmapRegionDecoder类
decoder.decodeRegion(rect, null);
按坐标分部加载需要显示的部分图像。
1、尺寸采样率压缩
我们在加载Bitmap显示到ImageView的时候往往是不需要加载原图的当ImageView宽高小于Bitmap时我们可以将Bitmap宽高压缩到mageView宽高相似大小再加载到内存中。
代码实现
public static Bitmap pathToBitmap(String srcPath) {BitmapFactory.Options newOpts new BitmapFactory.Options();// 开始读入图片此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds true;Bitmap bitmap BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空int w newOpts.outWidth;int h newOpts.outHeight;// 假设这里ImageView的宽高为400*400这里可以根据ImageView动态计算float hh 400f;float ww 400f;// 缩放比。由于是固定比例缩放只用高或者宽其中一个数据进行计算即可int be 1;// be1表示不缩放if (w h w ww) {// 如果宽度大的话根据宽度固定大小缩放be (int) (newOpts.outWidth / ww);} else if (w h h hh) {// 如果高度高的话根据宽度固定大小缩放be (int) (newOpts.outHeight / hh);}if (be 0)be 1;newOpts.inSampleSize be;// 设置缩放比例newOpts.inJustDecodeBounds false;// 重新读入图片注意此时已经把options.inJustDecodeBounds 设回false了bitmap BitmapFactory.decodeFile(srcPath, newOpts);return bitmap;}
2、解码率压缩
Bitmap解码器默认是 ARGB_8888 我们可以将解码器设置为RGB_565再减少一倍的内存。
public static Bitmap pathToBitmap(String srcPath) {BitmapFactory.Options newOpts new BitmapFactory.Options();// 开始读入图片此时把options.inJustDecodeBounds 设回true了newOpts.inJustDecodeBounds true;Bitmap bitmap BitmapFactory.decodeFile(srcPath, newOpts);// 此时返回bm为空int w newOpts.outWidth;int h newOpts.outHeight;// 假设这里ImageView的宽高为400*400这里可以根据ImageView动态计算float hh 400f;float ww 400f;// 缩放比。由于是固定比例缩放只用高或者宽其中一个数据进行计算即可int be 1;// be1表示不缩放if (w h w ww) {// 如果宽度大的话根据宽度固定大小缩放be (int) (newOpts.outWidth / ww);} else if (w h h hh) {// 如果高度高的话根据宽度固定大小缩放be (int) (newOpts.outHeight / hh);}if (be 0)be 1;newOpts.inSampleSize be;// 设置缩放比例newOpts.inPreferredConfig Config.RGB_565; // 降低图片从ARGB888到RGB565newOpts.inJustDecodeBounds false;// 重新读入图片注意此时已经把options.inJustDecodeBounds 设回false了bitmap BitmapFactory.decodeFile(srcPath, newOpts);return bitmap;}
3、质量压缩
质量压缩法不减少图片本身的像素它在保持像素的前提下该变图片的位深以及透明度来达到压缩图片的目的压缩后的文件大小会有所改变但是导入成 bitmap后所占内存是不会变化的。
public static Bitmap zipBitmap(Bitmap image, int size) {try {ByteArrayOutputStream baos new ByteArrayOutputStream();image.compress(CompressFormat.JPEG, 100, baos);int options 100;System.out.println(options options ,baos baos.toByteArray().length / 1024);while (baos.toByteArray().length / 1024 size) { //循环判断如果压缩后图片是否大于size kb,大于继续压缩baos.reset();//重置baos即清空baosoptions - 10;//每次都减少10image.compress(CompressFormat.JPEG, options, baos);//这里压缩options%把压缩后的数据存放到baos中}ByteArrayInputStream isBm new ByteArrayInputStream(baos.toByteArray());Bitmap bitmap BitmapFactory.decodeStream(isBm, null, null);return bitmap;//压缩好比例大小后再进行质量压缩} catch (Exception e) {e.printStackTrace();}return image;}
注意第一个参数不能为CompressFormat.PNGPNG格式是无损的它无法再进行质量压缩quality这个参数就没有作用了会被忽略所以最后图片保存成的文件大小不会有变化 可以设置为CompressFormat.JPEG和CompressFormat.WEBP质量压缩不会改变Bitmap本身的内存大小改变的是压缩后保存成文件的大小。
4、分部加载超大图
在特殊场景我们需要清晰的显示一张超大图的时候我们可以使用自定义View通过滑动去分部加载超大图。避免一次性将整张大图加载到内存中而导致OOM问题。
InputStream inputStream ...; // 输入流可以是网络流或本地文件流
BitmapRegionDecoder decoder BitmapRegionDecoder.newInstance(inputStream, false); // 创建BitmapRegionDecoder对象
int width decoder.getWidth(); // 获取完整图像的宽度
int height decoder.getHeight(); // 获取完整图像的高度
Rect rect new Rect(0, 0, width / 2, height / 2); // 需要显示的部分图像的矩形范围
Bitmap bitmap decoder.decodeRegion(rect, null); // 加载需要显示的部分图像
imageView.setImageBitmap(bitmap); // 显示加载的部分图像
5、多级缓存
使用图片缓存通过使用图片缓存可以避免重复加载图片从而提高应用程序的性能。可以使用LruCache或DiskLruCache来实现图片缓存。
1LruCache
LruCache是Android中的缓存类用于缓存对象并在缓存满时自动移除最近最少使用的对象。LruCache使用了LRULeast Recently Used算法来维护缓存的对象即将最近最少使用的对象移除以便为新对象腾出空间。
LruCache的实现代码
int maxMemory (int) (Runtime.getRuntime().maxMemory() / 1024); // 获取应用程序最大可用内存
int cacheSize maxMemory / 8; // 设置缓存大小为最大可用内存的1/8
LruCacheString, Bitmap memoryCache new LruCacheString, Bitmap(cacheSize) {Overrideprotected int sizeOf(String key, Bitmap bitmap) {return bitmap.getByteCount() / 1024; // 返回图片占用的内存大小单位KB}
}; // 创建LruCache对象String url http://www.example.com/image.jpg;
Bitmap bitmap memoryCache.get(url); // 从缓存中获取图片
if (bitmap null) {// 如果缓存中不存在该图片则从网络加载该图片bitmap loadImageFromNetwork(url);// 将加载的图片添加到缓存中memoryCache.put(url, bitmap);
}
imageView.setImageBitmap(bitmap); // 显示图片
2DiskLruCache
DiskLruCache是一个非Google官方编写但获得官方认证的三方库用于缓存数据到磁盘上并在缓存满时自动移除最近最少使用的数据。与LruCache类似DiskLruCache也使用了LRULeast Recently Used算法来维护缓存的数据。
DiskLruCache的使用代码
File cacheDir getExternalCacheDir(); // 缓存目录
int cacheVersion 1; // 缓存版本号
long cacheSize 10 * 1024 * 1024; // 缓存大小10MBDiskLruCache diskCache DiskLruCache.open(cacheDir, cacheVersion, 1, cacheSize); // 创建DiskLruCache对象String key example;
String value Hello, world!;// 将数据添加到缓存中
DiskLruCache.Editor editor diskCache.edit(key);
OutputStream os editor.newOutputStream(0);
os.write(value.getBytes());
editor.commit();// 从缓存中获取数据
DiskLruCache.Snapshot snapshot diskCache.get(key);
if (snapshot ! null) {String result snapshot.getString(0);Log.d(DiskLruCache, result);
}
3三方图片加载库
除了使用上述的图片优化方法在项目开发中我们可以使用常用的三方图片加载库包括Glide和Picasso等来帮助应用程序更高效地加载图片并自动处理图片的优化和缓存。