Android截屏方法总结
最近研究了一些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);
}
1
2
3
4
5
6
(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");
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
(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());
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
api21以上使用MediaProjectionManager,MediaProjection,VirtualDisplay,ImageReader。
代碼調用
(1).申請權限
private void requestCapturePermission() {
? ? MediaProjectionManager mediaProjectionManager = (MediaProjectionManager)
? ? ? ? ? ? getSystemService(Context.MEDIA_PROJECTION_SERVICE);
? ? startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(),
? ? ? ? ? ? REQUEST_MEDIA_PROJECTION);
}
1
2
3
4
5
6
獲取權限以后,獲取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);
? ? }
}
1
2
3
4
5
6
7
8
(2).獲取屏幕內容
// 獲取屏幕內容
VirtualDisplay mVirtualDisplay;
private void virtualDisplay() {
? ? mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror",
? ? ? ? ? ? mScreenWidth, mScreenHeight, mScreenDensity,
? ? ? ? ? ? DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
? ? ? ? ? ? mImageReader.getSurface(), null, null);
}
1
2
3
4
5
6
7
8
(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);
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
利用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);
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
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;
? ? ? ? }
? ? }
? ? // 省略代碼
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
(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);
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
(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);
? ? }
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
(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;
? ? ? ? }
? ? }
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
(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);
}
1
2
3
4
5
6
7
(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);
}
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
(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);
}
1
2
3
4
5
6
7
8
擴展
截屏功能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使用入門
---------------------?
作者:CoolEgos?
來源:CSDN?
原文:https://blog.csdn.net/A38017032/article/details/70482514?
版權聲明:本文為博主原創文章,轉載請附上博文鏈接!
總結
以上是生活随笔為你收集整理的Android截屏方法总结的全部內容,希望文章能夠幫你解決所遇到的問題。
- 上一篇: AndroidAsync
- 下一篇: Android adb无线调试脚本