Android 截屏、图片模糊及bitmap相关
作者:大师傅姑爷
转载地址:https://juejin.cn/post/7116432182448488479
简介
任务:window弹窗的背景模糊
实现原理:在window弹窗内容显示出来之前先截取手机屏幕,然后模糊,等弹窗出来之后根据弹窗大小自适应截图,设置为背景,从而实现弹窗背景模糊的效果
难点:
- 由于我们的应用没有activity常驻,而且是系统应用,所以和普通应用的截屏不同;
- 使用反射的方法调用系统api进行截屏时,获取屏幕高度有个坑,需要特别注意;
- 直接使用原生api进行模糊,radius设置为最大25并不能满足业务要求,所以还另外需要对图片进行处理。
截屏
截屏的方法有很多种,这里介绍常用的几种:
-
adb命令截屏
adb命令截屏需要root权限,而且由于涉及io,所以比较耗时
private Bitmap getScreenByAdb(String localImgPath) {
// adb命令获取屏幕截图,与实际屏幕尺寸相同,但是涉及IO所以耗时
Process process = null;
try {
process = Runtime.getRuntime().exec("screencap " + localImgPath);
process.waitFor();//比较耗时,尤其屏幕色彩比较复杂时,耗时甚至会到3s
} catch (Exception e) {
e.printStackTrace();
} finally {
if (process != null) {
process.destroy();
}
}
FileInputStream fis = null;
try {
fis = new FileInputStream(localImgPath);
} catch (FileNotFoundException e) {
e.printStackTrace();
}
Bitmap originalBgBitmap = BitmapFactory.decodeStream(fis);
return originalBgBitmap;
}
```
2. 反射调用系统api截屏
使用该方法截屏时需要是系统应用,我最后也是采用的这种方式。
```
private Bitmap getScreenBySysMethod() {
DisplayMetrics dm = getContext().getResources().getDisplayMetrics();
Bitmap originalBgBitmap = null;
try {
Class<?> mClassType = Class.forName("android.view.SurfaceControl");
Method nativeScreenshotMethod = mClassType.getDeclaredMethod("screenshot", Rect.class, int.class, int.class, int.class);
nativeScreenshotMethod.setAccessible(true);
Bitmap sBitmap = (Bitmap) nativeScreenshotMethod.invoke(mClassType, new Rect(), dm.widthPixels,
dm.heightPixels, Surface.ROTATION_0);
originalBgBitmap = sBitmap.copy(Bitmap.Config.ARGB_8888, true);
} catch (NoSuchMethodException e) {
e.printStackTrace();
} catch (ClassNotFoundException e) {
e.printStackTrace();
} catch (InvocationTargetException e) {
e.printStackTrace();
} catch (IllegalAccessException e) {
e.printStackTrace();
}
return originalBgBitmap;
}
在使用这个方法截屏时有个坑,当时折腾了我挺长时间,就是在部分全面屏手机上直接使用dm.heightPixels获取的屏幕高度不包含状态栏,看dm.heightPixels的源码,注释写的是The absolute height of the available display size in pixels,所以不包含状态栏也对。这时通过另外一种方式可以获取屏幕高度:
/**
* 在activity中获取屏幕的真实高度,由于在部分全面屏手机上,
* 直接使用DisplayMetrics heightPixels获取屏幕高度没有包含状态栏或者虚拟按键
* 所以会比实际的小几十个像素
* @param context
* @return
*/
public static int getScreenHeight(Activity context) {
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
context.getWindowManager().getDefaultDisplay().getRealMetrics(displayMetrics);
return displayMetrics.heightPixels;
}
-
普通应用截屏
这个方法一般用于activity或者fragment中调用,因为需要调用getWindow().getDecorView()获取根布局。所以,如果在需要截屏的时候,如果没有activity活动,是无法使用这个方法的。
private Bitmap getScreenshotView() {
View view = getWindow().getDecorView();
view.setDrawingCacheEnabled(true); // 设置缓存,可用于实时截图
Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
view.draw(canvas);
view.setDrawingCacheEnabled(false); // 清空缓存,可用于实时截图
return bitmap;
}
同时,由于这个方法在获取截屏时是通过view进行的,所以也可以只对某个view进行局部截图,只要将方法中的view对象换成需要截图的view就可以了。
Bitmap模糊
GitHub上有现成的模糊控件,可以满足很多场景,比如github.com/mmin18/Real…等。可惜不满足我的需求,我这边只需要对拿到的bitmap进行模糊,直接使用Android原生的就可以,代码如下:
/**
*
* @param context 上下文
* @param src 原图
* @param radius 模糊半径,有效范围0-25
* @return
*/
private Bitmap blur(Context context, Bitmap src, float radius) {
RenderScript rs = RenderScript.create(context);
final Allocation input = Allocation.createFromBitmap(rs, src);
final Allocation output = Allocation.createTyped(rs, input.getType());
final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(rs, Element.U8_4(rs));
script.setRadius(radius);
script.setInput(input);
script.forEach(output);
output.copyTo(src);
return src;
}
如果这个方法模糊度不够的话,还可以这样来做:先将bitmap缩小,再进行模糊,然后再放大,这样得到的模糊度一般是可以满足需求的。
缩小bitmap
BitmapTransformUtil.scaleBitmap(originalBgBitmap, 0.2f, 0.2f, true);
放大bitmap
BitmapTransformUtil.scaleBitmap(blurredBgBitmap, 5.0f, 5.0f, true);
其中scaleBitmap方法如下所示:
/**
* 根据指定的宽度比例值和高度比例值进行缩放
*
* @param srcBitmap
* @param scaleWidth
* @param scaleHeight
* @param recycleSrc 是否回收Bitmap
* @return
*/
public static Bitmap scaleBitmap(Bitmap srcBitmap, float scaleWidth, float scaleHeight, boolean recycleSrc) {
int width = srcBitmap.getWidth();
int height = srcBitmap.getHeight();
Matrix matrix = new Matrix();
matrix.postScale(scaleWidth, scaleHeight);
Bitmap bitmap = Bitmap.createBitmap(srcBitmap, 0, 0, width, height, matrix, true);
if (bitmap != null) {
/**回收*/
if (recycleSrc && srcBitmap != null && !srcBitmap.equals(bitmap) && !srcBitmap.isRecycled()) {
GlideBitmapPool.putBitmap(srcBitmap);
}
return bitmap;
} else {
return srcBitmap;
}
}
Bitmap截取
拿到整个手机屏幕的截图后,需要借去部分作为背景,截取bitmap方法如下:
/**
* 裁剪一定高度保留上半部分
*
* @param srcBitmap 原图
* @param x 起始坐标x
* @param y 起始坐标y
* @param width 目标宽度
* @param height 目标高度
* @param recycleSrc 是否回收原图
* @return
*/
public static Bitmap cropBitmapTop(Bitmap srcBitmap, int x, int y, int width, int height, boolean recycleSrc) {
/**裁剪关键步骤*/
Bitmap cropBitmap = Bitmap.createBitmap(srcBitmap, x, y, width, height);
/**回收之前的Bitmap*/
if (recycleSrc && srcBitmap != null && !srcBitmap.equals(cropBitmap) && !srcBitmap.isRecycled()) {
GlideBitmapPool.putBitmap(srcBitmap);
}
return cropBitmap;
}
Bitmap圆角设置
/**
* 设置圆角
*
* @param srcBitmap
* @param recycleSrc 是否回收原图
* @return
*/
public static Bitmap getRoundedCornerBitmap(Bitmap srcBitmap, float radius, boolean recycleSrc) {
Bitmap output = Bitmap.createBitmap(srcBitmap.getWidth(), srcBitmap.getHeight(), Bitmap.Config.ARGB_8888);
Paint paint = new Paint();
Rect rect = new Rect(0, 0, srcBitmap.getWidth(), srcBitmap.getHeight());
RectF rectF = new RectF(rect);
paint.setAntiAlias(true);
Canvas canvas = new Canvas(output);
canvas.save();
canvas.drawARGB(0, 0, 0, 0);
canvas.drawRoundRect(rectF, radius, radius, paint);
paint.setXfermode(new PorterDuffXfermode(PorterDuff.Mode.SRC_IN));
canvas.drawBitmap(srcBitmap, rect, rect, paint);
canvas.restore();
/**回收之前的Bitmap*/
if (recycleSrc && srcBitmap != null && !srcBitmap.equals(output) && !srcBitmap.isRecycled()) {
GlideBitmapPool.putBitmap(srcBitmap);
}
return output;
}
Bitmap保存
/**
* 保存bitmap到本地
* @param bitmap
* @param filePath
*/
private void saveBitmap(Bitmap bitmap, String filePath){
FileOutputStream fos = null;
try {
fos = new FileOutputStream(filePath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos);
fos.flush();
} catch (IOException e) {
e.printStackTrace();
} finally {
try {
if (fos != null) {
fos.close();
}
} catch (IOException e) {
e.printStackTrace();
}
}
}
推荐一个bitmap复用池:github.com/amitshekhar…,毕竟bitmap是内存消耗大户,合理使用bitmap可以避免内存抖动和OOM。具体使用方法直接点进去看吧。
最后贴一个实现效果图吧