最近研究了一些Android
的截屏方法,做一个总结。
View.getDrawingCache()
得到Bitmap
。非常简单但是只能截图本应用的图片,并且没办法控制截图的范围。Bitmap
进行截屏。可以方便的操作截取大小,但是需要提前截取整个屏幕,然后再处理生成的Bitmap
。截取屏幕流程:打开一个新的Window
全屏展示,上面包含一个CropView
->操作CropView
选择区域->根据选择区域截取Bitmap
。一般需求用来从相册选择或者从相册发送图片。
(1).选择图片。打开系统选择图片的Activity
并设置相应的参数。
// 打开选择的Activity.
private void selectPic(){
Intent intent = new Intent("android.intent.action.PICK");
intent.setDataAndType(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, "image/*");
startActivityForResult(intent, REQUEST_PICK_IMG);
}
(2).截切图片。选择图片成功(onActivityResult
)以后,设置相应参数开始裁剪图片。
// 截取图片
public void cutImage(Uri uri){
try {
Intent intent = new Intent("com.android.camera.action.CROP");
intent.setDataAndType(uri, "image/*");
intent.putExtra("crop", "true");
intent.putExtra("aspectX", 1);
intent.putExtra("aspectY", 1);
intent.putExtra("outputX", width);
intent.putExtra("outputY", height);
// 不返回bitmap,全使用uri来传递,防止图片使用内存太大
intent.putExtra("return-data", false);
intent.putExtra("output", outputUri);
intent.putExtra("outputFormat", Bitmap.CompressFormat.JPEG.toString());
intent.putExtra("scale", true);
intent.putExtra("scaleUpIfNeeded", true);
intent.putExtra("noFaceDetection", true);
startActivityForResult(intent, REQUEST_CUT_IMG);
}
catch (Exception e) {
Log.e("Test", "com.android.camera.action.CROP error");
}
}
(3).onActivityResult
处理裁剪图片成功以后回调。
api25
)系统从相册打开图片,然后截取图片。源码在com.android.gallery3d
包下,com.android.gallery3d
是一个单独的应用(进程)。
(1).打开相册com.android.gallery3d/.app.GalleryActivity
,选择图片,略过。
(2).打开截图界面com.android.gallery3d/.filtershow.crop.CropActivity
。
(3).执行BitmapIOTask.doInBackground
,对sourceUri
进行操作,保存在dstUri
中。
(4).执行CropActivity.getCroppedImage
进行截图。
protected static Bitmap getCroppedImage(Bitmap image, RectF cropBounds, RectF photoBounds) {
// cropBounds:截取大小。photoBounds:图片大小。
RectF imageBounds = new RectF(0, 0, image.getWidth(), image.getHeight());
RectF crop = CropMath.getScaledCropBounds(cropBounds, photoBounds, imageBounds);
if (crop == null) {
return null;
}
Rect intCrop = new Rect();
crop.roundOut(intCrop);
// 相当于直接对Bitmap 进行截取。
return Bitmap.createBitmap(image, intCrop.left, intCrop.top, intCrop.width(),
intCrop.height());
}
api21
以上使用MediaProjectionManager
,MediaProjection
,VirtualDisplay
,ImageReader
。(1).申请权限
private void requestCapturePermission() {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager)
getSystemService(Context.MEDIA_PROJECTION_SERVICE);
startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(),
REQUEST_MEDIA_PROJECTION);
}
获取权限以后,获取MediaProjection
对象。
MediaProjection mMediaProjection;
protected void onActivityResult(int requestCode, int resultCode, Intent data) { switch (requestCode) { case REQUEST_MEDIA_PROJECTION: mMediaProjection = ((MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE)) .getMediaProjection(Activity.RESULT_OK, data); }
}
(2).获取屏幕内容
// 获取屏幕内容
VirtualDisplay mVirtualDisplay;
private void virtualDisplay() {
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
mScreenWidth, mScreenHeight, mScreenDensity,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null);
}
(3).截图,android.media.Image
可以拿到相应的Bitmap
信息。
ImageReader.OnImageAvailableListener() {
// 每当生成一个新的Image 的时候都会回调onImageAvailable
@Override
public void onImageAvailable(ImageReader reader) {
if (reader == null) {
return;
}
// 最终会调用native方法
Image image = reader.acquireLatestImage();
if (image == null) {
startScreenShot();
} else {
if (mCropView != null) {
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 + (pixelStride == 0 ? 0 : rowPadding / pixelStride), height, Bitmap.Config.ARGB_8888);
// 最终生成了Bitmap保存了图片
bitmap.copyPixelsFromBuffer(buffer);
}
}
if (image != null) {
// 注意需要调用close
image.close();
}
// 防止多次回调onImageAvailable
reader.close();
}
}, mMainHandler);
ddmlib
(DDMS的ddmlib.jar
)截屏。(1).创建Java
工程,并导入ddmlib.jar
,ddms.jar
和ddmuilib.jar
。在Android SDK
的tools/lib
目录下面。
(2).直接完整代码了,注意getDevice
中的createBridge
需要改成自己电脑adb
的路径。takeScreenshot
中保存的路径需要设置成自己的路径。main
中调用的getDevice
需要改成自己手机的id。
public class MyClass {
private static void waitDeviceList(AndroidDebugBridge bridge) {
int count = 0;
while (bridge.hasInitialDeviceList() == false) {
try {
Thread.sleep(100); // 如果没有获得设备列表,则等待
count++;
} catch (InterruptedException e) {
}
if (count > 300) {
// 设定时间超过300×100 ms的时候为连接超时
System.err.print("Time out");
break;
}
}
}
/** * 连接device */
private static IDevice getDevice(String id) {
AndroidDebugBridge.init(false); // 需要初始化 false
AndroidDebugBridge bridge = AndroidDebugBridge
.createBridge("/Users/Egos/Library/Android/sdk/platform-tools/adb", false);
waitDeviceList(bridge);
IDevice devices[] = bridge.getDevices();
for (IDevice onlinedeivce : devices) {
if (onlinedeivce.getSerialNumber().equals(id))
return onlinedeivce;
}
return null;
}
/** * 截取屏幕 */
private static void takeScreenshot(IDevice device) {
try {
RawImage rawScreen = device.getScreenshot();
if (rawScreen != null) {
int width = rawScreen.width;
int height = rawScreen.height;
BufferedImage image = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
int index = 0;
int indexInc = rawScreen.bpp >> 3;
for (int y = 0; y < rawScreen.height; y++) {
for (int x = 0; x < rawScreen.width; x++, index += indexInc) {
int value = rawScreen.getARGB(index);
image.setRGB(x, y, value);
}
}
ImageIO.write(image, "PNG", new File("/Users/Egos/Downloads/test.png"));
}
} catch (Exception e) {
e.printStackTrace();
}
}
public static void main(String[] args) {
IDevice device = getDevice("03f37b9af0c746f4");
takeScreenshot(device);
}
}
adb shell screencap -p filepath
。使用adb截图保存在手机上面,没有具体分析调用流程。adb shell input keyevent 120
,相当于调用的系统截屏(手机组合键截图)。api25
)(1).按下组合键以后,执行到PhoneWindowManager.interceptKeyBeforeDispatching()
// PhoneWindowManager.java
public long interceptKeyBeforeDispatching(WindowState win, KeyEvent event, int policyFlags) {
// 省略代码
else if (keyCode == KeyEvent.KEYCODE_S && event.isMetaPressed()
&& event.isCtrlPressed()) {
if (down && repeatCount == 0) {
int type = event.isShiftPressed() ? TAKE_SCREENSHOT_SELECTED_REGION
: TAKE_SCREENSHOT_FULLSCREEN;
mScreenshotRunnable.setScreenshotType(type);
mHandler.post(mScreenshotRunnable);
return -1;
}
}
// 省略代码
}
(2).执行到PhoneWindowManager.ScreenshotRunnable.run()
// PhoneWindowManager.java
private class ScreenshotRunnable implements Runnable {
private int mScreenshotType = TAKE_SCREENSHOT_FULLSCREEN;
public void setScreenshotType(int screenshotType) {
mScreenshotType = screenshotType;
}
@Override
public void run() {
takeScreenshot(mScreenshotType);
}
}
(3).执行到takeScreenshot
// PhoneWindowManager.java
private void takeScreenshot(final int screenshotType) {
// SYSUI_SCREENSHOT_SERVICE = "com.android.systemui.screenshot.TakeScreenshotService"
final ComponentName serviceComponent = new ComponentName(SYSUI_PACKAGE,
SYSUI_SCREENSHOT_SERVICE);
// 省略代码
final Intent serviceIntent = new Intent();
serviceIntent.setComponent(serviceComponent);
ServiceConnection conn = new ServiceConnection() {
// 省略代码
}
if (mContext.bindServiceAsUser(serviceIntent, conn,
Context.BIND_AUTO_CREATE | Context.BIND_FOREGROUND_SERVICE_WHILE_AWAKE,
UserHandle.CURRENT)) {
mScreenshotConnection = conn;
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
}
(4).com.android.systemui.screenshot.TakeScreenshotService
private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
final Messenger callback = msg.replyTo;
// 省略代码
if (mScreenshot == null) {
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
}
switch (msg.what) {
// 截取全屏
case WindowManager.TAKE_SCREENSHOT_FULLSCREEN:
mScreenshot.takeScreenshot(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
// 截取指定区域
case WindowManager.TAKE_SCREENSHOT_SELECTED_REGION:
mScreenshot.takeScreenshotPartial(finisher, msg.arg1 > 0, msg.arg2 > 0);
break;
}
}
};
(5).截取全屏或者指定区域最后都是执行到了同一个逻辑。分析全屏截取。执行mScreenshot.takeScreenshot
。
// GlobalScreenshot.java
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
mDisplay.getRealMetrics(mDisplayMetrics);
// 全屏就是 (0,0) -> (width,height)
takeScreenshot(finisher, statusBarVisible, navBarVisible, 0, 0, mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels);
}
(6).GlobalScreenshot.takeScreenshot
,截取选择区域也是这个方法。
// GlobalScreenshot.java
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible,
int x, int y, int width, int height) {
// 省略代码
// 截取代码获取了Bitmap。
mScreenBitmap = SurfaceControl.screenshot((int) dims[0], (int) dims[1]);
// 截屏失败
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager,
R.string.screenshot_failed_to_capture_text);
finisher.run();
return;
}
// 省略代码
// Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
}
(7).SurfaceControl.screenshot
,SurfaceControl
是hide
的。
// SurfaceControl.java
public static Bitmap screenshot(int width, int height) {
IBinder displayToken = SurfaceControl.getBuiltInDisplay(
SurfaceControl.BUILT_IN_DISPLAY_ID_MAIN);
// 调用native代码截图
return nativeScreenshot(displayToken, new Rect(), width, height, 0, 0, true,
false, Surface.ROTATION_0);
}
截屏功能native
层通过framebuffer
来实现的(这里可能不是很准确)。帧缓冲(framebuffer
)是Linux
为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行读写操作。Android
系统是基于Linux
内核的,所以也存在framebuffer
这个设备,我们要实现截屏的话只要能获取到framebuffer
中的数据,然后把数据转换成图片就可以了,Android
中的framebuffer
数据是存放在 /dev/graphics/fb0
文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。
在自身应用中截取一个View
比较简单。但是去截取任意界面任意大小,就需要比较大的权限,可能需要root
手机。直接使用android.view.SurfaceControl
反射处理。需要使用到系统的uid``android:sharedUserId="android.uid.systemui"
。api21
以后系统开放了截图的接口,可以直接使用(自己的使用的时候碰到过问题)。
为什么 Android 截屏需要 root 权限
截屏方法总结
ddmlib使用入门