截圖介紹
Android的調試工具DDMS提供有截屏功能,很多軟件也會有截屏功能,在做支付等安全類應用的時候,為了保證用戶的資產和系統安全,往往會禁止應用內截屏,禁止之后,在此應用處於前台的情況下,截屏功能將不能使用,如下圖所示
截圖的原理
DDMS的實現方式
DDMS是通過adb調用設備端的adbd(ADB daemon)提供的framebuffer service進行截屏(源碼在system/core/adb/framebuffer_service.c),在較早版本的Android中,framebuffer service通過直接讀framebuffer 設備(/dev/graphics/fb0)來截屏,但是讀framebuffer設備(/dev/graphics/fb0)的方式在某些使用硬件overlay顯示的設備上可能無法截取到某些畫面(例如video playback和camera preview畫面)。
在較新版本的Android中,framebuffer service則調用截屏工具screencap來截屏。
screencap工具
screencap是Android原生自帶的工具,是一個C寫的可執行文件,在設備上的/system/bin/下面可以找到它,screencap截屏后可保存為PNG格式文件或RGB RAW文件。screencap的源碼frameworks/base/cmds/screencap/
,它調用SurfaceFlinger提供的截屏接口ScreenshotClient,其源碼在frameworks/native/libs/gui/SurfaceComposerClient.cpp
(該路徑在不同版本的Android源碼中可能略有差別),ScreenshotClient通過進程間通信調用SurfaceFlinger service的截屏功能,源碼在frameworks/native/services/surfaceflinger/SurfaceFlinger.cpp
中的函數SurfaceFlinger::captureScreen
。
SurfaceFlinger提供的上述截屏接口則可以完美截取任何屏幕畫面,因此相對來說是Android上最正規最完善的截屏方法,使用起來也非常簡單。
關於SurfaceFlinger和framebuffer的內容不再做贅述,因為描述起來非常復雜,不是本文的重點,有興趣的同學可以參考相關資料學習。
截圖的實現
shell命令實現
screencap 命令 用法如下
screencap [-hp] [FILENAME]
-h: this message
-p: save the file as a png.
If FILENAME ends with .png it will be saved as a png.
If FILENAME is not given, the results will be printed to stdout.
如果文件名以.png結尾時,它將保存為png文件
如果文件名沒有給出,則結果被會被輸出到stdout
例如 以下命令會將屏幕截圖保存在sd卡路徑下,文件名為screen.png
$ adb shell screencap -p /sdcard/screen.png
代碼實現
截圖工具類ScreenUtils
public class ScreenUtils { private ScreenUtils() { /* cannot be instantiated */ throw new UnsupportedOperationException("cannot be instantiated"); } /** * 獲得屏幕高度 * * @param context * @return */ public static int getScreenWidth(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.widthPixels; } /** * 獲得屏幕寬度 * * @param context * @return */ public static int getScreenHeight(Context context) { WindowManager wm = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics outMetrics = new DisplayMetrics(); wm.getDefaultDisplay().getMetrics(outMetrics); return outMetrics.heightPixels; } /** * 獲得狀態欄的高度 * * @param context * @return */ public static int getStatusHeight(Context context) { int statusHeight = -1; try { Class<?> clazz = Class.forName("com.android.internal.R$dimen"); Object object = clazz.newInstance(); int height = Integer.parseInt(clazz.getField("status_bar_height") .get(object).toString()); statusHeight = context.getResources().getDimensionPixelSize(height); } catch (Exception e) { e.printStackTrace(); } return statusHeight; } /** * 獲取當前屏幕截圖,包含狀態欄 * * @param activity * @return */ public static Bitmap snapShotWithStatusBar(Activity activity) { View view = activity.getWindow().getDecorView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); int width = getScreenWidth(activity); int height = getScreenHeight(activity); Bitmap bp = null; bp = Bitmap.createBitmap(bmp, 0, 0, width, height); view.destroyDrawingCache(); return bp; } /** * 獲取當前屏幕截圖,不包含狀態欄 * * @param activity * @return */ public static Bitmap snapShotWithoutStatusBar(Activity activity) { View view = activity.getWindow().getDecorView(); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bmp = view.getDrawingCache(); Rect frame = new Rect(); activity.getWindow().getDecorView().getWindowVisibleDisplayFrame(frame); int statusBarHeight = frame.top; int width = getScreenWidth(activity); int height = getScreenHeight(activity); Bitmap bp = null; bp = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height - statusBarHeight); view.destroyDrawingCache(); return bp; } }
- 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
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
- 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
- 68
- 69
- 70
- 71
- 72
- 73
- 74
- 75
- 76
- 77
- 78
- 79
- 80
- 81
- 82
- 83
- 84
- 85
- 86
- 87
- 88
- 89
- 90
- 91
- 92
- 93
- 94
- 95
- 96
- 97
- 98
- 99
- 100
- 101
- 102
- 103
- 104
通過調用以上工具類的截圖方法就可以拿到圖片的bitmap然后就可以隨心所欲的進行進一步操作了。
禁止截圖的實現
禁止截圖通過對window對象加標志位FLAG_SECURE實現,此標識位的注釋如下,
/** Window flag: treat the content of the window as secure, preventing
* it from appearing in screenshots or from being viewed on non-secure
* displays.
*
* <p>See {@link android.view.Display#FLAG_SECURE} for more details about
* secure surfaces and secure displays.
*/
public static final int FLAG_SECURE = 0x00002000;
使用方式如下:
@Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE); }
private void initializeScreenshotSecurity() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.ICE_CREAM_SANDWICH &&
TextSecurePreferences.isScreenSecurityEnabled(this))
{
getWindow().addFlags(WindowManager.LayoutParams.FLAG_SECURE);
} else {
getWindow().clearFlags(WindowManager.LayoutParams.FLAG_SECURE);
}
}