在日常开发中,可以说和Bitmap低头不见抬头见,基本上每个应用都会直接或间接的用到,而这里面又涉及到大量的相关知识。
所以这里把Bitmap的常用知识做个梳理,限于经验和能力,不做太深入的分析。
1. 区别decodeResource()和decodeFile()
这里的区别不是指方法名和参数的区别,而是对于解码后图片尺寸在处理上的区别:
decodeFile()用于读取SD卡上的图,得到的是图片的原始尺寸
decodeResource()用于读取Res、Raw等资源,得到的是图片的原始尺寸 * 缩放系数
可以看的出来,decodeResource()比decodeFile()多了一个缩放系数,缩放系数的计算依赖于屏幕密度,当然这个参数也是可以调整的:
1
2
3
4
5
6
7
8
|
// 通过BitmapFactory.Options的这几个参数可以调整缩放系数 public class BitmapFactory {
public static class Options {
public boolean inScaled; // 默认true
public int inDensity; // 无dpi的文件夹下默认160
public int inTargetDensity; // 取决具体屏幕
}
} |
我们分具体情况来看,现在有一张720×720的图片:
inScaled属性
如果inScaled设置为false,则不进行缩放,解码后图片大小为720×720; 否则请往下看。
如果inScaled设置为true或者不设置,则根据inDensity和inTargetDensity计算缩放系数。
默认情况
把这张图片放到drawable目录下, 默认:
以720p的红米3为例子,缩放系数 = inTargetDensity(具体320 / inDensity(默认160)= 2 = density,解码后图片大小为1440×1440。
以1080p的MX4为例子,缩放系数 = inTargetDensity(具体480 / inDensity(默认160)= 3 = density, 解码后图片大小为2160×2160。
*dpi文件夹的影响
把图片放到drawable或者draw这样不带dpi的文件夹,会按照上面的算法计算。
如果放到xhdpi会怎样呢? 在MX4上,放到xhdpi,解码后图片大小为1080 x 1080。
因为放到有dpi的文件夹,会影响到inDensity的默认值,放到xhdpi为160 x 2 = 320; 所以缩放系数 = 480(屏幕) / 320 (xhdpi) = 1.5; 所以得到的图片大小为1080 x 1080。
手动设置缩放系数
如果你不想依赖于这个系统本身的density,你可以手动设置inDensity和inTargetDensity来控制缩放系数:
1
2
3
4
5
6
7
8
9
10
|
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = false ;
options.inSampleSize = 1 ;
options.inDensity = 160 ;
options.inTargetDensity = 160 ;
bitmap = BitmapFactory.decodeResource(getResources(), R.drawable.origin, options); // MX4上,虽然density = 3 // 但是通过设置inTargetDensity / inDensity = 160 / 160 = 1 // 解码后图片大小为720x720 System.out.println( "w:" + bitmap.getWidth() + ", h:" + bitmap.getHeight());
|
2. recycle()方法
官方说法
首先,Android对Bitmap内存(像素数据)的分配区域在不同版本上是有区分的:
As of Android 3.0 (API level 11), the pixel data is stored on the Dalvik heap along with the associated bitmap.
从3.0开始,Bitmap像素数据和Bitmap对象一起存放在Dalvik堆中,而在3.0之前,Bitmap像素数据存放在Native内存中。
所以,在3.0之前,Bitmap像素数据在Nativie内存的释放是不确定的,容易内存溢出而Crash,官方强烈建议调用recycle()(当然是在确定不需要的时候);而在3.0之后,则无此要求。
一点讨论
3.0之后官方无recycle()建议,是不是就真的不需要recycle()了呢?
在医生的这篇文章:Bitmap.recycle引发的血案 最后指出:“在不兼容Android2.3的情况下,别在使用recycle方法来管理Bitmap了,那是GC的事!”。文章开头指出了原因在于recycle()方法的注释说明:
1
2
3
4
5
6
|
/** * ... This is an advanced call, and normally need not be called,
* since the normal GC process will free up this memory when
* there are no more references to this bitmap.
*/
public void recycle() {}
|
事实上这个说法是不准确的,是不能作为recycle()方法不调用的依据的。
因为从commit history中看,这行注释早在08年初始化代码的就有了,但是早期的代码并没有因此不需要recycle()方法了。
如果3.0之后真的完全不需要主动recycle(),最新的AOSP源码应该有相应体现,我查了SystemUI和Gallery2的代码,并没有取缔Bitmap的recycle()方法。
所以,我个人认为,如果Bitmap真的不用了,recycle一下又有何妨?
PS:至于医生说的那个bug,显然是一种优化策略,APP开发中加个两个bitmap不相等的判断条件即可。
3. Bitmap到底占多大内存
这个已经有一篇bugly出品的绝好文章讲的很清楚:
Android 开发绕不过的坑:你的 Bitmap 究竟占多大内存?
4. inBitmap
BitmapFactory.Options.inBitmap是Android3.0新增的一个属性,如果设置了这个属性则会重用这个Bitmap的内存从而提升性能。
但是这个重用是有条件的,在Android4.4之前只能重用相同大小的Bitmap,Android4.4+则只要比重用Bitmap小即可。
在官方网站有详细介绍,这里列举示例代码的两个方法了解一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
|
private static void addInBitmapOptions(BitmapFactory.Options options, ImageCache cache) {
// inBitmap only works with mutable bitmaps, so force the decoder to
// return mutable bitmaps.
options.inMutable = true ;
if (cache != null ) {
// Try to find a bitmap to use for inBitmap.
Bitmap inBitmap = cache.getBitmapFromReusableSet(options);
if (inBitmap != null ) {
// If a suitable bitmap has been found,
// set it as the value of inBitmap.
options.inBitmap = inBitmap;
}
}
} static boolean canUseForInBitmap(Bitmap candidate, BitmapFactory.Options targetOptions) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.KITKAT) {
// From Android 4.4 (KitKat) onward we can re-use
// if the byte size of the new bitmap is smaller than
// the reusable bitmap candidate
// allocation byte count.
int width = targetOptions.outWidth / targetOptions.inSampleSize;
int height = targetOptions.outHeight / targetOptions.inSampleSize;
int byteCount = width * height * getBytesPerPixel(candidate.getConfig());
return byteCount <= candidate.getAllocationByteCount();
}
// On earlier versions,
// the dimensions must match exactly and the inSampleSize must be 1
return candidate.getWidth() == targetOptions.outWidth && candidate.getHeight() == targetOptions.outHeight
&& targetOptions.inSampleSize == 1 ;
} |
参考链接:
5. LRU缓存算法
LRU,Least Recently Used,Discards the least recently used items first。
在最近使用的数据中,丢弃使用最少的数据。与之相反的还有一个MRU,丢弃使用最多的数据。
这就是著名的局部性原理。
实现思路
1.新数据插入到链表头部;
2.每当缓存命中(即缓存数据被访问),则将数据移到链表头部;
3.当链表满的时候,将链表尾部的数据丢弃。
LruCache
在Android3.1和support v4中均提供了Lru算法的实现类LruCache。
内部使用LinkedHashMap实现。
DiskLruCache
LruCache的所有对象和数据都是在内存中(或者说LinkedHashMap中),而DiskLruCache是磁盘缓存,不过它的实现要稍微复杂一点。
使用DiskLruCache后就不用担心文件或者图片太多占用过多磁盘空间,它能把那些不常用的图片自动清理掉。
DiskLruCache系统中并没有正式提供,需要另外下载: DiskLruCache
6. 计算inSampleSize
使用Bitmap节省内存最重要的技巧就是加载合适大小的Bitmap,因为以现在相机像素,很多照片都巨无霸的大,这些大图直接加载到内存,最容易OOM。
加载合适的Bitmap需要先读取Bitmap的原始大小,按缩小了合适的倍数的大小进行加载。
那么,这个缩小的倍数的计算就是inSampleSize的计算。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
|
// 根据maxWidth, maxHeight计算最合适的inSampleSize public static int $sampleSize(BitmapFactory.Options options, int maxWidth, int maxHeight) {
// raw height and width of image
int rawWidth = options.outWidth;
int rawHeight = options.outHeight;
// calculate best sample size
int inSampleSize = 0 ;
if (rawHeight > maxHeight || rawWidth > maxWidth) {
float ratioWidth = ( float ) rawWidth / maxWidth;
float ratioHeight = ( float ) rawHeight / maxHeight;
inSampleSize = ( int ) Math.min(ratioHeight, ratioWidth);
}
inSampleSize = Math.max( 1 , inSampleSize);
return inSampleSize;
} |
关于inSampleSize需要注意,它只能是2的次方,否则它会取最接近2的次方的值。
7. 缩略图
为了节省内存,需要先设置BitmapFactory.Options的inJustDecodeBounds为true,这样的Bitmap可以借助decodeFile方法把高和宽存放到Bitmap.Options中,但是内存占用为空(不会真正的加载图片)。
有了具备高宽信息的Options,结合上面的inSampleSize算法算出缩小的倍数,我们就能加载本地大图的某个合适大小的缩略图了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
|
/** * 获取缩略图
* 支持自动旋转
* 某些型号的手机相机图片是反的,可以根据exif信息实现自动纠正
* @return
*/
public static Bitmap $thumbnail(String path,
int maxWidth, int maxHeight, boolean autoRotate) {
int angle = 0 ;
if (autoRotate) {
angle = ImageLess.$exifRotateAngle(path);
}
BitmapFactory.Options options = new BitmapFactory.Options();
options.inJustDecodeBounds = true ;
// 获取这个图片的宽和高信息到options中, 此时返回bm为空
Bitmap bitmap = BitmapFactory.decodeFile(path, options);
options.inJustDecodeBounds = false ;
// 计算缩放比
int sampleSize = $sampleSize(options, maxWidth, maxHeight);
options.inSampleSize = sampleSize;
options.inPreferredConfig = Bitmap.Config.RGB_565;
options.inPurgeable = true ;
options.inInputShareable = true ;
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
bitmap = BitmapFactory.decodeFile(path, options);
if (autoRotate && angle != 0 ) {
bitmap = $rotate(bitmap, angle);
}
return bitmap;
} |
系统内置了一个ThumbnailUtils也能生成缩略图,细节上不一样但原理是相同的。
8. Matrix变形
学过线性代数或者图像处理的同学们一定深知Matrix的强大,很多常见的图像变换一个Matrix就能搞定,甚至更复杂的也是如此。
// Matrix matrix = new Matrix();
// 每一种变化都包括set,pre,post三种,分别为设置、矩阵先乘、矩阵后乘。
平移:matrix.setTranslate()
缩放:matrix.setScale()
旋转:matrix.setRotate()
斜切:matrix.setSkew()
下面我举两个例子说明一下。
旋转
借助Matrix的postRotate方法旋转一定角度。
1
2
3
4
5
6
7
8
9
10
|
Matrix matrix = new Matrix();
// angle为旋转的角度 matrix.postRotate(angle); Bitmap rotatedBitmap = Bitmap.createBitmap(originBitmap, 0 ,
0 ,
originBitmap.getWidth(),
originBitmap.getHeight(),
matrix,
true );
|
缩放
借助Matrix的postScale方法旋转一定角度。
1
2
3
4
5
6
7
8
9
10
|
Matrix matrix = new Matrix();
// scaleX,scaleY分别为为水平和垂直方向上缩放的比例 matrix.postScale(scaleX, scaleY); Bitmap scaledBitmap = Bitmap.createBitmap(originBitmap, 0 ,
0 ,
originBitmap.getWidth(),
originBitmap.getHeight(),
matrix,
true );
|
Bitmap本身也带了一个缩放方法,不过是把bitmap缩放到目标大小,原理也是用Matrix,我们封装一下:
1
2
|
// 水平和宽度缩放到指定大小,注意,这种情况下图片很容易变形 Bitmap scaledBitmap = Bitmap.createScaledBitmap(originBitmap, dstWidth, dstHeight, true );
|
通过组合可以实现更多效果。
9. 裁剪
图片的裁剪的应用场景还是很多的:头像剪切,照片裁剪,圆角,圆形等等。
矩形
矩阵形状的裁剪比较简单,直接用createBitmap方法即可:
1
2
3
4
|
Canvas canvas = new Canvas(originBitmap);
draw(canvas); // 确定裁剪的位置和裁剪的大小 Bitmap clipBitmap = Bitmap.createBitmap(originBitmap, left, top, clipWidth, clipHeight); |
圆角
对于圆角我们需要借助Xfermode和PorterDuffXfermode,把圆角矩阵套在原Bitmap上取交集得到圆角Bitmap。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
|
// 准备画笔 Paint paint = new Paint();
paint.setAntiAlias( true );
// 准备裁剪的矩阵 Rect rect = new Rect( 0 , 0 , originBitmap.getWidth(), originBitmap.getHeight());
RectF rectF = new RectF( new Rect( 0 , 0 , originBitmap.getWidth(), originBitmap.getHeight()));
Bitmap roundBitmap = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(roundBitmap);
// 圆角矩阵,radius为圆角大小 canvas.drawRoundRect(rectF, radius, radius, paint); // 关键代码,关于Xfermode和SRC_IN请自行查阅 paint.setXfermode( new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(originBitmap, rect, rect, paint); |
圆形
和上面的圆角裁剪原理相同,不过画的是圆形套在上面。
为了从中间裁剪出圆形,我们需要计算绘制原始Bitmap的left和top值。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
|
int min = originBitmap.getWidth() > originBitmap.getHeight() ?
originBitmap.getHeight() : originBitmap.getWidth(); Paint paint = new Paint();
paint.setAntiAlias( true );
Bitmap circleBitmap = Bitmap.createBitmap(min, min, Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(circleBitmap);
// 圆形 canvas.drawCircle(min / 2 , min / 2 , min / 2 , paint);
paint.setXfermode( new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
// 居中显示 int left = - (originBitmap.getWidth() - min) / 2 ;
int top = - (originBitmap.getHeight() - min) / 2 ;
canvas.drawBitmap(originBitmap, left, top, paint); |
从圆角、圆形的处理上我们应该能看的出来绘制任意多边形都是可以的。
10. 保存Bitmap
很多图片应用都支持裁剪功能,滤镜功能等等,最终还是需要把处理后的Bitmap保存到本地,不然就是再强大的功能也是白忙活了。
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
|
public static String $save(Bitmap bitmap, Bitmap.CompressFormat format, int quality, File destFile) {
try {
FileOutputStream out = new FileOutputStream(destFile);
if (bitmap.compress(format, quality, out)) {
out.flush();
out.close();
}
if (bitmap != null && !bitmap.isRecycled()) {
bitmap.recycle();
}
return destFile.getAbsolutePath();
} catch (FileNotFoundException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
}
return null ;
} |
如果想更稳定或者更简单的保存到SDCard的包名路径下,可以再封装一下:
1
2
3
4
5
6
7
8
9
10
11
12
13
|
// 保存到本地,默认路径/mnt/sdcard/[package]/save/,用随机UUID命名文件 public static String $save(Bitmap bitmap, Bitmap.CompressFormat format, int quality, Context context) {
if (!Environment.getExternalStorageState().equals(Environment.MEDIA_MOUNTED)) {
return null ;
}
File dir = new File(Environment.getExternalStorageDirectory() + "/" + context.getPackageName() + "/save/" );
if (!dir.exists()) {
dir.mkdirs();
}
File destFile = new File(dir, UUID.randomUUID().toString());
return $save(bitmap, format, quality, destFile);
} |
11. 巨图加载
巨图加载,当然不能使用常规方法,必OOM。
原理比较简单,系统中有一个类BitmapRegionDecoder:
1
2
3
4
5
6
7
8
9
10
11
12
|
public static BitmapRegionDecoder newInstance( byte [] data, int offset,
int length, boolean isShareable) throws IOException {
} public static BitmapRegionDecoder newInstance(
FileDescriptor fd, boolean isShareable) throws IOException {
} public static BitmapRegionDecoder newInstance(InputStream is,
boolean isShareable) throws IOException {
} public static BitmapRegionDecoder newInstance(String pathName,
boolean isShareable) throws IOException {
} |
可以按区域加载:
1
2
|
public Bitmap decodeRegion(Rect rect, BitmapFactory.Options options) {
} |
微博的大图浏览也是通过这个BitmapRegionDecoder实现的,具体可自行查阅。
12. 颜色矩阵ColorMatrix
图像处理其实是一门很深奥的学科,所幸Android提供了颜色矩阵ColorMatrix类,可实现很多简单的特效,以灰阶效果为例子:
1
2
3
4
5
6
7
8
9
|
Bitmap grayBitmap = Bitmap.createBitmap(originBitmap.getWidth(), originBitmap.getHeight(), Bitmap.Config.RGB_565); Canvas canvas = new Canvas(grayBitmap);
Paint paint = new Paint();
ColorMatrix colorMatrix = new ColorMatrix();
// 设置饱和度为0,实现了灰阶效果 colorMatrix.setSaturation( 0 );
ColorMatrixColorFilter colorMatrixColorFilter = new ColorMatrixColorFilter(colorMatrix);
paint.setColorFilter(colorMatrixColorFilter); canvas.drawBitmap(originBitmap, 0 , 0 , paint);
|
除了饱和度,我们还能调整对比度,色相变化等等。
13. ThumbnailUtils剖析
ThumbnailUtils是系统提供的一个专门生成缩略图的方法,我专门写了一篇文章分析,内容较多,请移步:理解ThumbnailUtils
相关推荐
Android Bitmap.getPixels的正确理解演示源码,参考文章《Android Bitmap入门:getPixels的正确理解》
本篇文章将深入探讨如何在Android中对Bitmap图片进行处理,使其能够以任意角度显示为圆角,以及涉及到的相关技术。 首先,我们要理解Android中的ImageView组件,它是用来显示图像的视图,可以显示Bitmap或者从资源...
Android Bitmap 相关知识介绍 Android 中的 Bitmap 是一种特殊的类,它不能被直接创建,而只能通过 BitmapFactory 来获取。BitmapFactory 提供了多种方法来从不同的图像来源中获取 Bitmap,下面是其中的一些: 1. ...
本文将深入探讨如何使用Bitmap实现各种特效处理,包括黑白特效、底片特效、浮雕特效、模糊特效、锐化特效以及怀旧特效。 首先,我们来看**黑白特效**。这种特效通常是通过将彩色图像转换为灰度图像来实现的。...
- 使用工具如MAT (Memory Analyzer Tool) 或 Android Studio 的内存分析器检查内存泄漏,确保Bitmap在不再使用后被正确回收。 通过上述策略,开发者可以在保证用户体验的同时,有效避免因Bitmap处理不当导致的性能...
在Android开发中,Bitmap是处理图像的基本类,用于在内存中表示位图图像。当我们需要对图片进行裁剪、缩放或进行其他操作时,Bitmap提供了丰富的功能。本篇文章将详细探讨如何在Android环境下利用Bitmap来切割图片。...
这个教程将深入探讨如何在Android中利用Matrix对Bitmap进行处理。 首先,我们需要了解Bitmap的基本操作。Bitmap可以通过多种方式加载,例如从资源文件、内存或文件系统中获取。加载后,我们可以获取其宽度、高度和...
本文将深入探讨Android Bitmap导致的`OutOfMemoryError`问题,以及如何有效地解决这个问题。 首先,我们需要了解`OutOfMemoryError`是如何产生的。在Java(包括Android)中,每个应用都有一定的内存限制。当应用...
在Android平台上,Bitmap是用于表示图像数据的基本类,它提供了对像素颜色的直接访问。24位深度的Bitmap文件,通常指的是每个像素包含红、绿、蓝三个通道,每个通道用8位表示,总共24位。这种格式的Bitmap色彩丰富,...
在Android开发中,Bitmap对象是处理图像的主要方式,但它们可能会消耗大量内存,尤其是在处理大图或高分辨率图片时。为了优化性能并防止因内存不足引发的“OutOfMemoryError”,开发者通常需要对Bitmap进行压缩。...
深入理解 Android Bitmap Android 中的 Bitmap 是图像处理的最重要类之一。它可以获取图像文件信息,进行图像剪切、旋转、缩放等操作,并可以指定格式保存图像文件。本文主要介绍了理解 Android Bitmap 需要的朋友...
Android不支持将Bitmap转换成单色的Bmp图片,所以参考Bmp格式说明,自己写了一个转换类。亲测有效!!!
本篇文章将深入探讨如何在Android中进行Bitmap与String之间的转换,以及这些转换在实际应用中的场景。 首先,我们要理解Bitmap与String转换的原理。Bitmap是Android系统中用来表示图像的类,它包含了图像的所有像素...
位图(Bitmap)是Android开发中不可或缺的部分,它用于表示图像数据。Bitmap类是Android系统提供的用于处理图像的主要类,但需要注意的是,Bitmap对象本身并不直接创建,而是通过BitmapFactory类来解析并创建。...
Bitmap是Android平台中用于处理图像的核心类,它用于表示位图图像数据。下面是对Bitmap用法的详细总结: 1. **Drawable转换为Bitmap**: 当我们需要将一个Drawable对象(如从XML布局文件中加载的图像)转换为...
Android 实现把bitmap图片的某一部分的颜色改成其他颜色 在 Android 开发中,bitmap 图片处理是一个非常重要的方面,特别是在图片颜色修改方面,今天我们将介绍如何实现把 bitmap 图片的某一部分的颜色改成其他颜色...
在Android开发中,Bitmap是用于处理图像的基本类,它提供了对像素级别的操作。BitmapUtils工具类是为了方便开发者在处理图片时进行各种操作,比如转换、压缩、存储等。本篇文章将详细探讨`Android bitmap工具类`,...
在Android开发中,有时我们需要将Bitmap对象转换成不同的图片格式,比如BMP。BMP(Bitmap File Format)是一种常见的位图文件格式,但它并不像JPEG或PNG那样被Android SDK直接支持。本文将详细介绍如何在Android中将...
本文将深入解析`drawBitmap()`方法的参数及其用法,并通过实例来说明如何使用该方法。 `drawBitmap(Bitmap bitmap, Rect src, Rect dst, Paint paint)`是`drawBitmap()`方法的一个重载版本,其参数含义如下: 1. *...
在Android开发中,Bitmap对象是用于处理图像的主要工具,但如果不妥善管理,它可能会引发一个常见的问题:Out of Memory(OOM)错误。特别是在处理大尺寸图片时,由于内存限制,系统可能无法分配足够的内存,从而...