禁止截图的实现

禁止截图通过对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);}

之前QQ有个qq赞赏照片的功能,也防止截图,这个功能调用的是安卓和IOS底层的一个功能消息接口,拦截这个消息就能禁止截图。

截图工具类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;}
}

通过调用以上工具类的截图方法就可以拿到图片的bitmap然后就可以随心所欲的进行进一步操作了。

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