Android进阶之路 - 花样百出的截屏需求
Posted Modu_MrLiu
tags:
篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android进阶之路 - 花样百出的截屏需求相关的知识,希望对你有一定的参考价值。
最近又遇到了截图相关的需求,联合之前的截图需求后,抽时间整理了一下截屏的常见需求,特此记录 ~
关联篇
针对每个人不同的需求,可采用不同的方法 - - ~
截取当前屏幕界面(activity)
一般调用对应方法时出现调用不到的场景时,我们需要根据提示进行修改,这里应该需要通过activity进行方法调用 ~
别人的方法
public Bitmap screen() {
View dView = getWindow().getDecorView();
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
return Bitmap.createBitmap(dView.getDrawingCache());
}
我的方法 - activity截图
//缺点:无法截取WebView页面,截屏后是白屏!
public static Bitmap capture(Activity activity) {
activity.getWindow().getDecorView().setDrawingCacheEnabled(true);
Bitmap bmp = activity.getWindow().getDecorView().getDrawingCache();
return bmp;
}
截图并保存
public void screen() {
View dView = getWindow().getDecorView();
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
if(bitmap !=null ) {
try {
// 获取内置SD卡路径
String sdCardPath = Environment.getExternalStorageDirectory().getPath();
// 图片文件路径(自行定义)
String filePath = sdCardPath + File.separator +"screenshot.png";
File file =new File(filePath);
FileOutputStream os = new FileOutputStream(file);
bitmap.compress(Bitmap.CompressFormat.PNG,100, os);
os.flush();
os.close();
} catch(Exception e) {
}
}
}
截取某个控件或区域(view)
可作用于根view或某个控件view
- 简洁版
/**
* 截取指定View为图片
*/
public static Bitmap captureView(View view) throws Throwable {
Bitmap bm = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
view.draw(new Canvas(bm));
return bm;
}
截图并保存
public void captureView(View view) {
//图片存储路径,可以不关注
String imgPath = "/sdcard/test.png";
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
//图片存储部分可以不关注
if (bitmap != null) {
try {
FileOutputStream out = new FileOutputStream(imgPath);
bitmap.compress(Bitmap.CompressFormat.PNG, 100,
out);
} catch (Exception e) {
e.printStackTrace();
}
}
}
- 繁琐版
public void screen(View view){
View dView = view;
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
//以下俩种方式,一种是取图片缓存,一种是重新绘制,可自行调节尝试
//Bitmap bitmap = Bitmap.createBitmap(dView.getDrawingCache());
Bitmap bitmap = Bitmap.createBitmap(dView.getWidth(), dView.getHeight(), Bitmap.Config.ARGB_8888);
//使用Canvas,调用自定义view控件的onDraw方法,绘制图片
Canvas canvas =new Canvas(bitmap);
dView.draw(canvas);
}
- view截图(有人说截取view转bitmap后为null,可用这种方式清理缓存,
未尝试
)
public static Bitmap getViewBp(View v) {
if (null == v) {
return null;
}
v.setDrawingCacheEnabled(true);
v.buildDrawingCache();
if (Build.VERSION.SDK_INT >= 11) {
v.measure(View.MeasureSpec.makeMeasureSpec(v.getWidth(),
View.MeasureSpec.EXACTLY), View.MeasureSpec.makeMeasureSpec(
v.getHeight(), View.MeasureSpec.EXACTLY));
v.layout((int) v.getX(), (int) v.getY(),
(int) v.getX() + v.getMeasuredWidth(),
(int) v.getY() + v.getMeasuredHeight());
} else {
v.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED),
View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
v.layout(0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
}
Bitmap b = Bitmap.createBitmap(v.getDrawingCache(), 0, 0, v.getMeasuredWidth(), v.getMeasuredHeight());
v.setDrawingCacheEnabled(false);
v.destroyDrawingCache();
return b;
}
- view截图(有人说截取view转bitmap后为null,可用这种方式清理缓存,
未尝试
)
public static Bitmap convertViewToBitmap(View view) {
view.setDrawingCacheEnabled(true);
view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED));
view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight());
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
view.setDrawingCacheEnabled(false);
return bitmap;
}
截取长屏
截取长屏其实原理就是截取整个ScrollView或者ListView的视图,因此实现原理跟上面中提到的截取某个控件的View基本一致。
ScrollView 实现截屏
/*
* 截取scrollview的屏幕
**/
public static Bitmap getScrollViewBitmap(ScrollView scrollView) {
int h = 0;
Bitmap bitmap;
for ( int i = 0 ; i < scrollView.getChildCount(); i++) {
h += scrollView.getChildAt(i).getHeight();
}
// 创建对应大小的bitmap
bitmap = Bitmap.createBitmap(scrollView.getWidth(), h,Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
scrollView.draw(canvas);
return bitmap;
}
ListView实现截屏
/**
* 截图listview
**/
public static Bitmap getListViewBitmap(ListView listView) {
int h = 0;
Bitmap bitmap;
// 获取listView实际高度
for (int i = 0 ; i < listView.getChildCount(); i++) {
h += listView.getChildAt(i).getHeight();
}
Log.d(TAG, "实际高度:" + h);
Log.d(TAG, "list 高度:" + listView.getHeight());
// 创建对应大小的bitmap
bitmap = Bitmap.createBitmap(listView.getWidth(), h,
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
listView.draw(canvas);
return bitmap;
}
WebView实现截屏
//这是webview的,利用了webview的api
private static Bitmap captureWebView(WebView webView) {
Picture snapShot = webView.capturePicture();
Bitmap bmp = Bitmap.createBitmap(snapShot.getWidth(),
snapShot.getHeight(), Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bmp);
snapShot.draw(canvas);
return bmp;
}
局部、全局实时截屏
关于应用内全局截屏的需求,我在 如何优雅的实时获取用户操作界面 中详细的解释了使用方式
在日常开发中,一般截屏后的图片,我们都会进行压缩、保存,这里一并进行记录
图片压缩
/**
* 压缩图片
*
* @param bgimage
* @param newWidth
* @param newHeight
* @return
*/
public static Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
// 获取这个图片的宽和高
float width = bgimage.getWidth();
float height = bgimage.getHeight();
// 创建操作图片用的matrix对象
Matrix matrix = new Matrix();
// 计算宽高缩放率
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
// 缩放图片动作
//matrix.postScale(scaleWidth, scaleHeight);//TODO 因为宽高不确定的因素,所以不缩放
Bitmap bitmap = Bitmap.createBitmap(bgimage, 0, 0, (int) width,
(int) height, matrix, true);
return bitmap;
}
截图、压缩
Bitmap bitmap = null;
try {
bitmap = captureView(mShareBackgroundSign);
} catch (Throwable throwable) {
throwable.printStackTrace();
}
//图片压缩,加快使用速度~
zoomImage(bitmap, 720, 1280);
保存图片到本地
注意:切记适配6.0、7.0
public static void savePhotoToSDCard(Bitmap photoBitmap, String path, String photoName) {
if (checkSDCardAvailable()) {
File dir = new File(path);
if (!dir.exists()) {
dir.mkdirs();
}
File photoFile = new File(path, photoName + ".png");
FileOutputStream fileOutputStream = null;
try {
fileOutputStream = new FileOutputStream(photoFile);
if (photoBitmap != null) {
if (photoBitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream)) {
fileOutputStream.flush();
}
}
} catch (FileNotFoundException e) {
photoFile.delete();
e.printStackTrace();
} catch (IOException e) {
photoFile.delete();
e.printStackTrace();
} finally {
try {
fileOutputStream.close();
} catch (IOException e) {
e.printStackTrace();
}
}
}
}
截屏扩展
以下部分其实我并未在有限的开发中使用到,仅当做个人兴趣的知识延伸
截取带导航栏的整个屏幕
采用adb命令方式截屏的操作,屈指可数,可当做知识扩展,没有必要深读
- 优点:只需在代码中执行截屏的命令即可,可根据需求封装一个方法,传入保存的路径和文件名即可;
- 缺点:手机需要获取ROOT权限,调用命令之前需要先请求su获取ROOT权限;
adb 命令:这里指的不是连接电脑进行adb操控,而是在App内部实现adb命令的操控
在APK中调用 "adb shell screencap -p filepath" 命令
注意:该命令读取系统的framebuffer,需要获得系统权限
- 在androidManifest.xml文件中添加
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
- 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
LOCAL_CERTIFICATE:= platform
public void takeScreenShot () {
String mSavedPath = Environment.getExternalStorageDirectory() + File.separator + "screenshot.png";
try {
Runtime.getRuntime().exec("screencap -p " + mSavedPath);
} catch (Exception e) {
e.printStackTrace();
}
}
利用系统的隐藏API,实现Screenshot,这部分代码是系统隐藏的,需要在源码下编译。
- 修改Android.mk, 添加系统权限
LOCAL_CERTIFICATE := platform
- 修改AndroidManifest.xml 文件,添加权限
<uses-permission android:name="android.permission.READ_FRAME_BUFFER" />
Android本地编程(Native Programming)读取framebuffer命令行,框架的截屏功能是通过framebuffer来实现的
framebuffer介绍
帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行 读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。
帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
android截屏实现思路
Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。
现在我们的测试代码运行时候是通过RC(remote controller)方式来运行被测应用的,那就需要在PC机上来访问模拟器或者真机上的framebuffer数据,这个的话可以通过android的ADB命令来实现。
各大手机自带的按键组合进行截屏,Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\\frameworks\\base\\policy\\src\\com\\android\\internal\\policy\\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。
截取非含当前应用的屏幕部分(最佳官方方案)
Android 在5.0 之后支持了实时录屏的功能;通过实时录屏我们可以拿到截屏的图像。同时可以通过在Service中处理实现后台的录屏(具体的类讲解大家自行网上查阅)
类似使用手机的系统截屏(音量下键+电源键),针对的并非一个view,而是整个屏幕,在调用这个服务的时候,会弹出一个权限确认的弹框;同时需注意,这一方法只能在Android 5.0的系统设备上使用。
详细步骤
- 初始化一个MediaProjectionManager
MediaProjectionManager mMediaProjectionManager = (MediaProjectionManager)getApplication().getSystemService(Context.MEDIA_PROJECTION_SERVICE);
- 创建intent,并启动Intent(注意:这里是startActivityForResult) 以上是关于Android进阶之路 - 花样百出的截屏需求的主要内容,如果未能解决你的问题,请参考以下文章