Skip to content
On this page

2022-08-android应用禁止截屏

1. 需求场景

使用银行APP的时候发现不能截屏。想看下他的实现原理。

2. 截屏实现

2.1 应用内截屏

用view或者activity的容器缓存生成图片,实现截屏

jsx
// 获取需要截取快照的控件
View view = findViewById(R.id.my_view);

// 设置控件允许绘制缓存
view.setDrawingCacheEnabled(true);
// 获取控件的绘制缓存(快照)
Bitmap bitmap = view.getDrawingCache();

// 保存截取的快照
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);

jsx
/*
 * 每个Activity都有一个铺满全屏的顶层View,只需要获取这个View的绘制快照,
 * 就相当于截取了整个Activity的屏幕,这种方式截取部分包括通知栏,但不包括
 * 通知烂上的内容,因为其内容不属于当前的Activity。
 */

// 获取Activity整个窗口最顶层的View
View view = activity.getWindow().getDecorView();

// 设置控件允许绘制缓存
view.setDrawingCacheEnabled(true);
// 获取控件的绘制缓存(快照)
Bitmap bitmap = view.getDrawingCache();

// 保存截取的快照
bitmap.compress(Bitmap.CompressFormat.PNG, 100, fileOutputStream);

2.2 adb命令截屏

adb命令截屏需要root

jsx
adb shell screencap -p /sdcard/sreenshot1.png

2.3 anroid5.0以后通过api截屏

jsx
if (Build.VERSION.SDK_INT >= 21) {
    startActivityForResult(
            ((MediaProjectionManager) getSystemService("media_projection")).createScreenCaptureIntent(),1);
} else {
    Log.e("TAG", "版本过低,无法截屏");
}
jsx
@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();
           // TODO 将image保存到本地即可
       }
    }, 300);
    mVirtualDisplay.release();
    mVirtualDisplay = null;
}

3. 禁止截屏实现

jsx
public class FlagSecureTestActivity extends Activity {
  @Override
  public void onCreate(Bundle savedInstanceState) {
    super.onCreate(savedInstanceState);

    getWindow().setFlags(LayoutParams.FLAG_SECURE, LayoutParams.FLAG_SECURE);

    setContentView(R.layout.main);
  }
}

4. 截屏监听

Android系统并没有提供截屏通知相关的API,需要我们自己利用系统能提供的相关特性变通实现。Android系统有一个媒体数据库,每拍一张照片,或使用系统截屏截取一张图片,都会把这张图片的详细信息加入到这个媒体数据库,并发出内容改变通知,我们可以利用内容观察者(ContentObserver)监听媒体数据库的变化,当数据库有变化时,获取最后插入的一条图片数据,如果该图片符合特定的规则,则认为被截屏了。 需要ContentObserver监听的资源URI:

  • MediaStore.Images.Media.INTERNAL_CONTENT_URI
  • MediaStore.Images.Media.EXTERNAL_CONTENT_URI

读取外部存储器资源,需要添加权限: android.permission.READ_EXTERNAL_STORAGE 当ContentObserver监听到媒体数据库的数据改变, 在有数据改变时 获取最后插入数据库的一条图片数据, 如果符合以下规则, 则认为截屏了:

  • 1、时间判断,图片的生成时间在开始监听之后, 并与当前时间相隔10秒内:开始监听后生成的图片才有意义,相隔10秒内说明是刚刚生成的;
  • 2、尺寸判断,图片的尺寸没有超过屏幕的尺寸:图片尺寸超过屏幕尺寸,一般都不是截屏图片(最近发现某些手机ROM支持滑动截屏,如果需要监听的APP界面支持滑动,需要做适当的调整和处理);
  • 3、路径判断,图片路径符合包含特定的关键词:这一点是关键,截屏图片的保存路径通常包含“screenshot”。

PS: 这些判断是为了增加截屏检测结果的可靠性,防止误报,防止遗漏。其中截屏图片的路径正常Android系统保存的路径格式为:“外部存储器/Pictures/Screenshots/Screenshot_20161001-164643.png”,但Android系统碎片化严重,加上其他第三方截屏APP等,所以路径关键字除了检查是否包含“screenshot”外,还可以适当增加其他关键字,详见最后的监听器完整代码。这种监听截屏的方法也不是100%准确,例如某些被root的机器使用第三方截屏APP自定义保存路径,还比如通过ADB命令在电脑上获取手机屏幕快照均不能监听到,但这也是目前可行性最高的方法,对于绝大多数用户都比较靠谱。

Released under the MIT License.