Bitmap在Android中指的是一张图片,可以是png
,也可以是jpg
等其他图片格式。
2.1 Drawable是一种可以在Canvas上进行绘制的抽象的概念。Drawable 是一个可以调用Canvas来进行绘制的上层工具。Drawable.draw(canvas)
可以将Drawable设置的绘制内容绘制到Canvas中。
2.2 Drawable的内部存储的是绘制规则,这个规则可以是一个具体的Bitmap,也可以是一个纯粹的颜色,甚至可以是一个抽象。灵活的描述。Drawable可以不含有具体的像素信息,只要它含有的信息足以在draw(canvas)
方法中被调用时进行绘制就够了。也就是说,颜色、图片等都可以是一个Drawable
2.3 Drawable
可以通过XML定义,或者通过代码构建
2.4 Android 中 Drawable是一个抽象类,每个具体的Drawable
都是其子类。
2.5 由于Drawable
存储的只是绘制规则,因此他在draw()方法被调用前,需要先调用Drawable.setBounds()
来为它设置绘制边界。
Drawable
所占用空间小,能减小apk大小
getIntrinsicWidth/Height
能获取内部宽/高
图片占用内存的计算公式:图片高度 * 图片宽度 * 一个像素占用的内存大小
。所以,计算图片占用内存大小的时候,要考虑图片所在的目录跟设备密度,这两个因素其实影响的是图片的宽高,android会对图片进行拉升跟压缩
BitmapFactory
类提供了四类方法用来加载Bitmap
:
decodeFile
从文件系统加载 Intent
打开本地图片或照片
onActivityResult
中获取图片uri
uri
获取图片的路径
bitmap:Bitmap bm = BitmapFactory.decodeFile(sd_path)
decodeResource
以R.drawable.xxx
的形式从本地资源中加载
Bitmap bm = BitmapFactory.decodeResource(getResources(), R.drawable.aaa);
decodeStream
从输入流加载 InputStream
Bitmap bm = BitmapFactory.decodeStream(stream)
,这是一个耗时操作,要在子线程中执行
decodeByteArray
从字节数组中加载
InputStream
InputStream
转换成byte[]
Bitmap bm = BitmapFactory.decodeByteArray(myByte,0,myByte.length);
注意:decodeFile
和decodeResource
间接调用decodeStream
方法。
android 色彩模式说明:
ALPHA_8
:每个像素占用1byte
内存。
ARGB_4444
:每个像素占用2byte
内存
ARGB_8888
:每个像素占用4byte
内存
RGB_565
:每个像素占用2byte
内存
Android默认的色彩模式为ARGB_8888
,这个色彩模式色彩最细腻,显示质量最高。但同样的,占用的内存也最大。
BitmapFactory.Options
的inPreferredConfig
参数可以 指定decode
到内存中,手机中所采用的编码,可选值定义在Bitmap.Config
中。缺省值是ARGB_8888
。
假设一张1024*1024
,模式为ARGB_8888
的图片,那么它占有的内存就是:1024*1024*4 = 4MB
<br />inSampleSize的值必须大于1时才会有效果,且采样率同时作用于宽和高;
当inSampleSize=1时,采样后的图片为图片的原始大小
当inSampleSize=2时,采样后的图片的宽高均为原始图片宽高的1/2,这时像素为原始图片的1/4,占用内存也为原始图片的1/4;
inSampleSize的取值应该总为2的整数倍,否则会向下取整,取一个最接近2的整数倍,比如inSampleSize=3时,系统会取inSampleSize=2
假设一张1024*1024,模式为ARGB_8888的图片,inSampleSize=2,原始占用内存大小是4MB,采样后的图片占用内存大小就是(1024/2) * (1024/2 )* 4 = 1MB
<br />将BitmapFacpry.Options的inJustDecodeBounds参数设为true并加载图片当inJustDecodeBounds为true时,执行decodeXXX方法时,BitmapFactory只会解析图片的原始宽高信息,并不会真正的加载图片
从BitmapFacpry.Options取出图片的原始宽高(outWidth,outHeight)信息
选取合适的采样率
将BitmapFacpry.Options的inSampleSize参数设为false并重新加载图片
在Android中,Bitmap
的存储分为两部分,一部分是Bitmap的数据,一部分是Bitmap的引用。 在Android2.3
时代,Bitmap的引用是放在堆中的,而Bitmap的数据部分是放在栈中的,需要用户调用recycle
方法手动进行内存回收,而在Android2.3
之后,整个Bitmap,包括数据和引用,都放在了堆中,这样,整个Bitmap
的回收就全部交给GC
了,这个recycle
方法就再也不需要使用了。
bitmap recycler
引发的问题:当图像的旋转角度小余两个像素点之间的夹角时,图像即使旋转也无法显示,因此,系统完全可以认为图像没有发生变化。这时系统就直接引用同一个对象来进行操作,避免内存浪费。
<br />if (!bmp.isRecycle()) {
bmp.recycle(); //回收图片所占的内存
bitmap = null;
system.gc(); //提醒系统及时回收
}
一定要对OutOfMemory
异常进行捕获
<br /> Bitmap bitmap = null;
try {
// 实例化Bitmap
bitmap = BitmapFactory.decodeFile(path);
} catch (OutOfMemoryError e) {
}
if (bitmap == null) {
return defaultBitmapMap; // 如果实例化失败 返回默认的Bitmap对象
}
有时候,可能需要在一个Activity里多次用到同一张图片。比如一个Activity会展示一些用户的头像列表,而如果用户没有设置头像的话,则会显示一个默认头像,而这个头像是位于应用程序本身的资源文件中的。如果有类似上面的场景,就可以对同一Bitmap进行缓存。如果不进行缓存,尽管看到的是同一张图片文件,但是使用BitmapFactory类的方法来实例化出来的Bitmap,是不同的Bitmap对象。缓存可以避免新建多个Bitmap对象,避免内存的浪费。在Android应用开发过程中所说的缓存有两个级别,一个是硬盘缓存,一个是内存缓存
上述用inSampleSize压缩是尺寸压缩,Android中还有一种压缩方式叫质量压缩。质量压缩是在保持像素的前提下改变图片的位深及透明度等,来达到压缩图片的目的,经过它压缩的图片文件大小(kb)会有改变,但是导入成bitmap后占得内存是不变的,宽高也不会改变。因为要保持像素不变,所以它就无法无限压缩,到达一个值之后就不会继续变小了。
尽量不要使用setImageBitmap或setImageResource或BitmapFactory.decodeResource来设置一张大图,因为这些函数在完成decode后,最终都是通过java层的createBitmap来完成的,需要消耗更多内存,可以通过BitmapFactory.decodeStream方法,创建出一个bitmap,再将其设为ImageView的 source
使用BitmapFactory.Options对图片进行压缩(上述第二部分)
运用Java软引用,进行图片缓存,将需要经常加载的图片放进缓存里,避免反复加载
Drawable newBitmapDrawable = new BitmapDrawable(bitmap);
还可以从BitmapDrawable中获取Bitmap对象
Bitmap bitmap = new BitmapDrawable.getBitmap();
drawable
转换成Bitmap
<br />public Bitmap scaleMatrixImage(Bitmap oldbitmap, float scaleWidth, float scaleHeight) {
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth,scaleHeight);// 放大缩小比例
Bitmap ScaleBitmap = Bitmap.createBitmap(oldbitmap, 0, 0, oldbitmap.getWidth(), oldbitmap.getHeight(), matrix, true);
return ScaleBitmap;
}
<br />public Bitmap cutImage(Bitmap bitmap, int reqWidth, int reqHeight) {
Bitmap newBitmap = null;
if (bitmap.getWidth() > reqWidth && bitmap.getHeight() > reqHeight) {
bitmap = Bitmap.createBitmap(bitmap, 0, 0, reqWidth, reqHeight);
} else {
bitmap = Bitmap.createBitmap(bitmap, 0, 0, bitmap.getWidth(), bitmap.getHeight());
}
return bitmap;
}
<br /> public void savePic(Bitmap bitmap,String path) {
File file = new File(path);
FileOutputStream fileOutputStream = null;
try {
file.createNewFile();
fileOutputStream = new FileOutputStream(file);
//以质量为100%的方式压缩图像,但是图片大小不变
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);
fileOutputStream.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fileOutputStream != null) {
fileOutputStream.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
通过 BitmapFactory 中的 decodeResource 方法,将资源文件中的 R.drawable.ic_drawable 转化成Bitmap
<br />Resources res = getResources();
Bitmap bmp = BitmapFactory.decodeResource(res, R.drawable.ic_drawable);
将 Drable 对象先转化成 BitmapDrawable ,然后调用 getBitmap 方法 获取
<br />Resource res = gerResource();
Drawable drawable = res.getDrawable(R.drawable.ic_drawable);//获取drawable
BitmapDrawable bd = (BitmapDrawable) drawable;
Bitmap bm = bd.getBitmap();
根据已有的Drawable创建一个新的Bitmap
<br /> public static Bitmap drawableToBitmap(Drawable drawable) {
int w = drawable.getIntrinsicWidth();
int h = drawable.getIntrinsicHeight();
System.out.println("Drawable转Bitmap");
Bitmap.Config config =
drawable.getOpacity() != PixelFormat.OPAQUE ? Bitmap.Config.ARGB_8888
: Bitmap.Config.RGB_565;
Bitmap bitmap = Bitmap.createBitmap(w, h, config);
//注意,下面三行代码要用到,否则在View或者SurfaceView里的canvas.drawBitmap会看不到图
Canvas canvas = new Canvas(bitmap);
drawable.setBounds(0, 0, w, h);
drawable.draw(canvas);
return bitmap;
}
使用 BitmapDrawable 对 Bitmap 进行强制转换
Drawable drawable = new BitmapDrawable(bmp);
<br />public static byte[] getBytes(Bitmap bitmap){
ByteArrayOutputStream baos = new ByteArrayOutputStream();
bitmap.compress(Bitmap.CompressFormat.PNG, 100, baos);
return baos.toByteArray();
}
<br /> public static Bitmap Bytes2Bimap(byte[] b) {
if (b.length != 0) {
return BitmapFactory.decodeByteArray(b, 0, b.length);
} else {
return null;
}
}
<br />public static Bitmap tintBitmap(Bitmap inBitmap , int tintColor) {
if (inBitmap == null) {
return null;
}
Bitmap outBitmap = Bitmap.createBitmap (inBitmap.getWidth(), inBitmap.getHeight() , inBitmap.getConfig());
Canvas canvas = new Canvas(outBitmap);
Paint paint = new Paint();
paint.setColorFilter( new PorterDuffColorFilter(tintColor, PorterDuff.Mode.SRC_IN)) ;
canvas.drawBitmap(inBitmap , 0, 0, paint) ;
return outBitmap ;
}
高斯模糊实现原理
在Android平台上进行模糊渲染是一个相当耗CPU也相当耗时的操作,一旦处理不好,卡顿是在所难免的。考虑到效率,渲染一张图片最好的方法是使用OpenGL,其次是使用C++/C,使用Java代码是最慢的。但是Android推出RenderScript之后,我们就有了新的选择,测试表明,使用RenderScript的渲染效率和使用C/C++不相上下,但是使用RenderScript却比使用JNI简单地多!
原理步骤如下所示:
RenderScript
内核对象
RenderScript
的工具对象
实现思路:先将图片进行最大程度的模糊处理,再将原图放置在模糊后的图片上面,通过不断改变原图的透明度(Alpha值)来实现动态模糊效果。
2 高斯模糊实现的代码
2.1 设置高斯模糊代码
<br />/**
* 设置模糊背景
*/
private void setBlurBackground(int pos) {
//获取轮播图索引pos处的图片
Integer integer = pagerAdapter.getBitmapHashMap().get(pos);
Resources res = this.getResources();
Bitmap bitmap= BitmapFactory.decodeResource(res, integer);
//压缩图片
final Bitmap image = BitmapUtils.compressImage(bitmap);
if (bitmap != null) {
if (mBlurRunnable != null) {
mIvBlurBackground.removeCallbacks(mBlurRunnable);
}
mBlurRunnable = new Runnable() {
@Override
public void run() {
//压缩图片,宽高缩放
Bitmap blurBitmap = BlurBitmapUtils.getBlurBitmap(
mIvBlurBackground.getContext(), image, 15);
ViewSwitchUtils.startSwitchBackgroundAnim(mIvBlurBackground, blurBitmap);
}
};
mIvBlurBackground.postDelayed(mBlurRunnable, 100);
}
}
2.2 RenderScript
图片高斯模糊
<br />/**
* RenderScript图片高斯模糊
*/
public class BlurBitmapUtils {
/**
* 建议模糊度(在0.0到25.0之间)
*/
private static final int SCALED_WIDTH = 100;
private static final int SCALED_HEIGHT = 100;
/**
* 得到模糊后的bitmap
* @param context 上下文
* @param bitmap bitmap
* @param radius 半径
* @return
*/
public static Bitmap getBlurBitmap(Context context, Bitmap bitmap, int radius) {
// 将缩小后的图片做为预渲染的图片。
Bitmap inputBitmap = Bitmap.createScaledBitmap(bitmap, SCALED_WIDTH, SCALED_HEIGHT, false);
// 创建一张渲染后的输出图片。
Bitmap outputBitmap = Bitmap.createBitmap(inputBitmap);
// 创建RenderScript内核对象
RenderScript rs = RenderScript.create(context);
// 创建一个模糊效果的RenderScript的工具对象
ScriptIntrinsicBlur blurScript = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
// 由于RenderScript并没有使用VM来分配内存,所以需要使用Allocation类来创建和分配内存空间。
// 创建Allocation对象的时候其实内存是空的,需要使用copyTo()将数据填充进去。
Allocation tmpIn = Allocation.createFromBitmap(rs, inputBitmap);
Allocation tmpOut = Allocation.createFromBitmap(rs, outputBitmap);
// 设置渲染的模糊程度, 25f是最大模糊度
blurScript.setRadius(radius);
// 设置blurScript对象的输入内存
blurScript.setInput(tmpIn);
// 将输出数据保存到输出内存中
blurScript.forEach(tmpOut);
// 将数据填充到Allocation中
tmpOut.copyTo(outputBitmap);
return outputBitmap;
}
}
2.3 设置高斯模糊背景View动画过渡效果
<br />/**
* 图片背景切换动画帮助类,设置View动画
*/
public class ViewSwitchUtils {
static void startSwitchBackgroundAnim(ImageView view, Bitmap bitmap) {
Drawable oldDrawable = view.getDrawable();
Drawable oldBitmapDrawable ;
TransitionDrawable oldTransitionDrawable = null;
if (oldDrawable instanceof TransitionDrawable) {
oldTransitionDrawable = (TransitionDrawable) oldDrawable;
oldBitmapDrawable = oldTransitionDrawable.findDrawableByLayerId(oldTransitionDrawable.getId(1));
} else if (oldDrawable instanceof BitmapDrawable) {
oldBitmapDrawable = oldDrawable;
} else {
oldBitmapDrawable = new ColorDrawable(0xffc2c2c2);
}
if (oldTransitionDrawable == null) {
oldTransitionDrawable = new TransitionDrawable(new Drawable[]{oldBitmapDrawable, new BitmapDrawable(bitmap)});
oldTransitionDrawable.setId(0, 0);
oldTransitionDrawable.setId(1, 1);
oldTransitionDrawable.setCrossFadeEnabled(true);
view.setImageDrawable(oldTransitionDrawable);
} else {
oldTransitionDrawable.setDrawableByLayerId(oldTransitionDrawable.getId(0), oldBitmapDrawable);
oldTransitionDrawable.setDrawableByLayerId(oldTransitionDrawable.getId(1), new BitmapDrawable(bitmap));
}
oldTransitionDrawable.startTransition(1000);
}
}
3 高斯模糊可能会造成的崩溃
3.1 崩溃日志
开发回收bitmap引发Canvas: trying to use a recycled bitmap错误处理
3.2 抛该异常的原因分析
如果代码已经不再需要使用Bitmap对象了,就可以释放了。释放内存以后,就不能再使用该Bitmap对象了,如果再次使用,就会抛出异常。所以一定要保证不再使用的时候释放。
3.3 解决该问题的办法
使用缓存
4 高斯模糊参考案例
Android 图片高斯模糊解决方案:https://www.jianshu.com/p/02da487a2f43