最近又遇到了截图相关的需求,联合之前的截图需求后,抽时间整理了一下截屏的常见需求,特此记录 ~
关联篇
针对每个人不同的需求,可采用不同的方法 - - ~
截取当前屏幕界面(activity)
一般调用对应方法时出现调用不到的场景时,我们需要根据提示进行修改,这里应该需要通过activity进行方法调用 ~
别人的方法
public Bitmap screen() {
View dView = getWindow().getDecorView();
dView.setDrawingCacheEnabled(true);
dView.buildDrawingCache();
return Bitmap.createBitmap(dView.getDrawingCache());
}
我的方法 - activity截图
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 {
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
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.getWidth(), dView.getHeight(), Bitmap.Config.ARGB_8888);
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 实现截屏
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.createBitmap(scrollView.getWidth(), h,Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
scrollView.draw(canvas);
return bitmap;
}
ListView实现截屏
public static Bitmap getListViewBitmap(ListView listView) {
int h = 0;
Bitmap bitmap;
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.createBitmap(listView.getWidth(), h,
Bitmap.Config.ARGB_8888);
final Canvas canvas = new Canvas(bitmap);
listView.draw(canvas);
return bitmap;
}
WebView实现截屏
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;
}
局部、全局实时截屏
关于应用内全局截屏的需求,我在 如何优雅的实时获取用户操作界面 中详细的解释了使用方式
在日常开发中,一般截屏后的图片,我们都会进行压缩、保存,这里一并进行记录
图片压缩
public static Bitmap zoomImage(Bitmap bgimage, double newWidth, double newHeight) {
float width = bgimage.getWidth();
float height = bgimage.getHeight();
Matrix matrix = new Matrix();
float scaleWidth = ((float) newWidth) / width;
float scaleHeight = ((float) newHeight) / height;
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)
startActivityForResult(mMediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
- 在onActivityResult中拿到Mediaprojection
mResultCode = resultCode;
mResultData = data;
mMediaProjection = mMediaProjectionManager.getMediaProjection(mResultCode, mResultData);
- 设置VirtualDisplay 将图像和展示的View关联起来;一般来说我们会将图像展示到SurfaceView,这里为了为了便于拿到截图,我们使用ImageReader,他内置有SurfaceView
mImageReader = ImageReader.newInstance(windowWidth, windowHeight, 0x1 ,2);
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror" ,windowWidth, windowHeight, mScreenDensity,DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null , null);
- 通过ImageReader拿到截图
strDate = dateFormat.format(new java.util.Date());
nameImage = pathImage+strDate+ ".png";
Image image = mImageReader.acquireLatestImage();
int width = image.getWidth();
int height = image.getHeight();
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * width;
Bitmap bitmap = Bitmap.createBitmap(width+rowPadding/pixelStride, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
bitmap = Bitmap.createBitmap(bitmap, 0, 0 ,width, height);
image.close();
- 注意截屏之后要及时关闭VirtualDisplay ,因为VirtualDisplay 是十分消耗内存和电量的
if(mVirtualDisplay == null) {
return ;
}
mVirtualDisplay.release();
mVirtualDisplay = null;
常用步骤
- 在Activity中开启截屏服务
if (Build.VERSION.SDK_INT >= 21) {
startActivityForResult(((MediaProjectionManager) getSystemService("media_projection")).createScreenCaptureIntent(), 1);
} else {
Log.e("TAG", "版本过低,无法截屏");
}
final MediaProjectionManager projectionManager = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
Intent intent = projectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE);
- 重写onActivityResult方法
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == 1 && data != null) {
parseData(data);
}
}
private void parseData(Intent data){
MediaProjection mMediaProjection = (MediaProjectionManager).getSystemService(
Context.MEDIA_PROJECTION_SERVICE).getMediaProjection(Activity.RESULT_OK,data);
ImageReader mImageReader = ImageReader.newInstance(
getScreenWidth(),
getScreenHeight(),
PixelFormat.RGBA_8888,1);
VirtualDisplay mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
getScreenWidth(),
getScreenHeight(),
Resources.getSystem().getDisplayMetrics().densityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
Handler handler = new Handler();
handler.postDelayed(new Runnable() {
@Override
public void run() {
Image image = mImageReader.acquireLatestImage();
}
}, 300);
mVirtualDisplay.release();
mVirtualDisplay = null;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
handleScreenShotIntent(resultCode, data);
}
private void handleScreenShotIntent(int resultCode, Intent data) {
onScreenshotTaskBegan();
final MediaProjectionManager projectionManager = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
final MediaProjection mProjection = projectionManager.getMediaProjection(resultCode, data);
Point size = Utils.getScreenSize(this);
final int mWidth = size.x;
final int mHeight = size.y;
final ImageReader mImageReader = ImageReader.newInstance(mWidth, mHeight, PixelFormat
.RGBA_8888, 2);
final VirtualDisplay display = mProjection.createVirtualDisplay("screen-mirror", mWidth,
mHeight, DisplayMetrics.DENSITY_MEDIUM,
DisplayManager.VIRTUAL_DISPLAY_FLAG_PRESENTATION, mImageReader.getSurface(),
null, null);
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader mImageReader) {
Image image = null;
try {
image = mImageReader.acquireLatestImage();
if (image != null) {
final Image.Plane[] planes = image.getPlanes();
if (planes.length > 0) {
final ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride();
int rowStride = planes[0].getRowStride();
int rowPadding = rowStride - pixelStride * mWidth;
Bitmap bmp = Bitmap.createBitmap(mWidth + rowPadding / pixelStride,
mHeight, Bitmap.Config.ARGB_8888);
bmp.copyPixelsFromBuffer(buffer);
Bitmap croppedBitmap = Bitmap.createBitmap(bmp, 0, 0, mWidth, mHeight);
saveBitmap(croppedBitmap);
if (croppedBitmap != null) {
croppedBitmap.recycle();
}
if (bmp != null) {
bmp.recycle();
}
}
}
} catch (Exception e) {
e.printStackTrace();
} finally {
if (image != null) {
image.close();
}
if (mImageReader != null) {
mImageReader.close();
}
if (display != null) {
display.release();
}
mImageReader.setOnImageAvailableListener(null, null);
mProjection.stop();
onScreenshotTaskOver();
}
}
}, getBackgroundHandler());
}
|