一个bug引出了一系列需要修改和注意的问题,让人去思考前期的设计与相关问题的思考,对后期功能增加和问题修复的重要性。
问题描述:软件的图片浏览功能中查看图片时,发现图片模糊,放大查看时更明显
查找原因:分析代码发现,在浏览及查看图片时显示的是缩略图,没有显示原图;而且设计如此,但不满足当前需求,需要修改
初步修改:将获取图片缩略图的地方,替换成获取原图
思路简单清晰,但忽略了此前代码的设计,随后导致与图片浏览显示相关的其它问题:
(1)缩放比例问题:显示原图了,但当原图过大时,允许的缩放比例边界值不够,致使在首次显示时,图片不能完整显示
(2)缓存问题:内存缓存,原设计采用双缓存机制,固定缓存3张,动态缓存采用软引用方式;在显示缩略图时内存占用并不高。换成显示原图后,浏览和显示一般图片没有任何问题,但当浏览大图时(数码相机图片),1张图片的内存占用就在70M,快速浏览几次很容易OOM
(3)适配问题:低内存手机,直接显示原图后,有的无法支持缓存了,甚至显示一张大的原图都会OOM
当然还有其它相关的问题,如快速浏览卡顿,图片旋转等,需要熟悉下原设计思路,重新考虑修改的思路。在整体考虑修改思路之前,先整理下相关知识。
1.图片内存占用
通常的误解是图片文件的大小,直接影响其内存占用大小。图片在内存中占用的大小是由图片分辨率和单位像素占用的字节数决定的。什么是单位像素字节数,即表示一个像素所需要的字节数。在Bitmap.Config中就定义了几种类型,
如ARGB_8888 ,就是一个像素点用4字节表示,具体为透明度用1字节表示,颜色值用3字节RGB表示。那么一张1080*720的图片的内存占用为1080*720*4=3110400字节,约3M;当宽高都乘5时,内存约75M
但是如果为RGB_565,即单位像素只占用2字节,内存占用缩小一半,但大图的话仍然是个不小的数字
2.大图OOM处理
在了解图片的内存占用后,也就能理解强引用几张大图后,必然会导致OOM(out of memory);而且在内存很小的手机上,直接显示单张大图,都会造成OOM。因此,得根据使用的场景不同对图片进行处理。
(1)首先单纯希望通过捕获OutOfMemoryError的做法,在Android6.0上可能不行,因为调用BitmapFactory.decodeStream时,可能在native层就报OOM了,但仍然是种防御手段。
(2)减少表示一个像素所需要的字节数:前面提到加载图片时,默认采用ARGB_8888,如果对透明度和色值范围要求不高的情况下,可以采用RGB_565,或者Bitmap.Config提供的其它选项,但这种减少毕竟有限;
(3)降低图片分辨率:BitmapFactory加载图片时,BitmapFactory.Options设置inSampleSize可以显著减少图片占用的内存大小,当然也是降低了图片的分辨率。inSampleSize为2时,其实内存占用是原来的四分之一
另外为了配合计算inSampleSize的取值,可以在不加载图片的情况下,解析出图片的宽高信息,给个示例代码如下:
根据屏幕大小,降低图片分辨率
3.其它问题
(1)图片缩放、平移的矩阵变换
对Bitmap的旋转、缩放、平移,都可以通过Matrix来实现,详细的解读可参考
Pro Android学习笔记(一零九):2D动画(4):view的Matrix(http://blog.youkuaiyun.com/flowingflying/article/details/38304057)
图片的旋转代码如:
(2)图片旋转
上面示例代码通过Matrix使图片旋转,但是具体旋转多少角度,可以通过图片的长宽比例来计算,也可以通过
(3)bitmap.recycle,现在主流的4.X以上手机这样调用没有用处,还会出现崩溃
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@41cb98e8
整体修改思路
在整理完一些必要的知识后,再整体思考设计,目标是可快速浏览,在查看图片时能显示原图,因此,依然保持双缓存,都保存缩略图;当停留在某张图片时,才加载该图的原图,也就是大家常见的先显示模糊图,之后图再变清晰。具体流程上是,浏览的缓存中存储较多的缩略图(占用内存较小),方便快速浏览滑动;当停留在某个图上超过一定时间后(通过handler.removeCallbacks和handler.postDelayed控制),在线程池中加载原图,并替换当前显示的缩略图
问题描述:软件的图片浏览功能中查看图片时,发现图片模糊,放大查看时更明显
查找原因:分析代码发现,在浏览及查看图片时显示的是缩略图,没有显示原图;而且设计如此,但不满足当前需求,需要修改
初步修改:将获取图片缩略图的地方,替换成获取原图
思路简单清晰,但忽略了此前代码的设计,随后导致与图片浏览显示相关的其它问题:
(1)缩放比例问题:显示原图了,但当原图过大时,允许的缩放比例边界值不够,致使在首次显示时,图片不能完整显示
(2)缓存问题:内存缓存,原设计采用双缓存机制,固定缓存3张,动态缓存采用软引用方式;在显示缩略图时内存占用并不高。换成显示原图后,浏览和显示一般图片没有任何问题,但当浏览大图时(数码相机图片),1张图片的内存占用就在70M,快速浏览几次很容易OOM
(3)适配问题:低内存手机,直接显示原图后,有的无法支持缓存了,甚至显示一张大的原图都会OOM
当然还有其它相关的问题,如快速浏览卡顿,图片旋转等,需要熟悉下原设计思路,重新考虑修改的思路。在整体考虑修改思路之前,先整理下相关知识。
1.图片内存占用
通常的误解是图片文件的大小,直接影响其内存占用大小。图片在内存中占用的大小是由图片分辨率和单位像素占用的字节数决定的。什么是单位像素字节数,即表示一个像素所需要的字节数。在Bitmap.Config中就定义了几种类型,
如ARGB_8888 ,就是一个像素点用4字节表示,具体为透明度用1字节表示,颜色值用3字节RGB表示。那么一张1080*720的图片的内存占用为1080*720*4=3110400字节,约3M;当宽高都乘5时,内存约75M
但是如果为RGB_565,即单位像素只占用2字节,内存占用缩小一半,但大图的话仍然是个不小的数字
2.大图OOM处理
在了解图片的内存占用后,也就能理解强引用几张大图后,必然会导致OOM(out of memory);而且在内存很小的手机上,直接显示单张大图,都会造成OOM。因此,得根据使用的场景不同对图片进行处理。
(1)首先单纯希望通过捕获OutOfMemoryError的做法,在Android6.0上可能不行,因为调用BitmapFactory.decodeStream时,可能在native层就报OOM了,但仍然是种防御手段。
(2)减少表示一个像素所需要的字节数:前面提到加载图片时,默认采用ARGB_8888,如果对透明度和色值范围要求不高的情况下,可以采用RGB_565,或者Bitmap.Config提供的其它选项,但这种减少毕竟有限;
(3)降低图片分辨率:BitmapFactory加载图片时,BitmapFactory.Options设置inSampleSize可以显著减少图片占用的内存大小,当然也是降低了图片的分辨率。inSampleSize为2时,其实内存占用是原来的四分之一
另外为了配合计算inSampleSize的取值,可以在不加载图片的情况下,解析出图片的宽高信息,给个示例代码如下:
根据屏幕大小,降低图片分辨率
public static Bitmap getBitmapFromFile(String imgPath, int screenWidth, int screenHeight) {
File imgfile = new File(imgPath);
if (!imgfile.exists()) {
return null;
}
Bitmap bmp = null;
FileInputStream fis = null;
try {
Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true;
BitmapFactory.decodeStream(new FileInputStream(imgPath), null, options);
int sample = 1;
int imgWidth = options.outWidth;
int imgHeight = options.outHeight;
while ((imgWidth / sample > screenWidth * 2) || (imgHeight / sample > screenHeight * 2)) {
sample *= 2;
}
fis = new FileInputStream(imgPath);
if (sample > 1) {
options = new BitmapFactory.Options();
options.inPreferredConfig = Config.ARGB_8888;
options.inSampleSize = sample;
bmp = BitmapFactory.decodeStream(fis, null, options);
} else {
bmp = BitmapFactory.decodeStream(fis);
}
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (OutOfMemoryError e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
} finally {
try {
if (fis != null)
fis.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return bmp;
}
3.其它问题
(1)图片缩放、平移的矩阵变换
对Bitmap的旋转、缩放、平移,都可以通过Matrix来实现,详细的解读可参考
Pro Android学习笔记(一零九):2D动画(4):view的Matrix(http://blog.youkuaiyun.com/flowingflying/article/details/38304057)
图片的旋转代码如:
Matrix matrix = new Matrix();
matrix.setRotate(90);
Bitmap rotateBitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight(), matrix,
false);
(2)图片旋转
上面示例代码通过Matrix使图片旋转,但是具体旋转多少角度,可以通过图片的长宽比例来计算,也可以通过
ExifInterface.TAG_ORIENTATION的值来计算,示例代码如:
ExifInterface exif = null;
try {
exif = new ExifInterface(filePath);
} catch (IOException e) {
e.printStackTrace();
}
String sOrientation = exif.getAttribute(ExifInterface.TAG_ORIENTATION);
当得到ORIENTATION_ROTATE_90,将图片旋转90度(3)bitmap.recycle,现在主流的4.X以上手机这样调用没有用处,还会出现崩溃
java.lang.RuntimeException: Canvas: trying to use a recycled bitmap android.graphics.Bitmap@41cb98e8
整体修改思路
在整理完一些必要的知识后,再整体思考设计,目标是可快速浏览,在查看图片时能显示原图,因此,依然保持双缓存,都保存缩略图;当停留在某张图片时,才加载该图的原图,也就是大家常见的先显示模糊图,之后图再变清晰。具体流程上是,浏览的缓存中存储较多的缩略图(占用内存较小),方便快速浏览滑动;当停留在某个图上超过一定时间后(通过handler.removeCallbacks和handler.postDelayed控制),在线程池中加载原图,并替换当前显示的缩略图
暂无完整源代码提供,如有需要请留言,我再整理发布出来
转载请注明出处:http://blog.youkuaiyun.com/w7849516230,欢迎关注微信公众号“编程阳光”