Android监听用户的截屏、投屏、录屏行为

Android监听用户的截屏、投屏、录屏行为

一.截屏

方案一:使用系统广播监听截屏操作

​ 从Android Q(10.0)开始,Intent.ACTION_SCREEN_CAPTURED_CHANGED字段不再被支持。这是因为Google在安卓10 中引入了一个新的隐私限制,即限制应用在用户开启了屏幕录制功能或截屏功能时获取相应的广播。

  1. 创建一个BroadcastReceiver类来接收截屏广播:
java 复制代码
public class ScreenCaptureReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_SCREEN_CAPTURES_CHANGED.equals(action)) {
            // 截屏操作
            // 在这里执行你的逻辑操作
        }
    }
}
  1. 在AndroidManifest.xml文件中声明截屏广播接收器:
xml 复制代码
<receiver
    android:name=".ScreenCaptureReceiver"
    android:enabled="true">
    <intent-filter>
        <action android:name="android.intent.action.SCREEN_CAPTURE_CHANGED" />
    </intent-filter>
</receiver>
  1. 注册截屏广播接收器,开始监听截屏操作:
java 复制代码
ScreenCaptureReceiver receiver = new ScreenCaptureReceiver();
IntentFilter filter = new IntentFilter(Intent.ACTION_SCREEN_CAPTURE_CHANGED);
registerReceiver(receiver, filter);
  1. 在不需要监听时,记得取消注册截屏广播接收器:
java 复制代码
unregisterReceiver(receiver);
  1. 在BroadcastReceiver中的onReceive方法中可以执行一些逻辑操作,例如显示一个提示消息:
java 复制代码
public class ScreenCaptureReceiver extends BroadcastReceiver {
    @Override
    public void onReceive(Context context, Intent intent) {
        String action = intent.getAction();
        if (Intent.ACTION_SCREEN_CAPTURE_CHANGED.equals(action)) {
            // 截屏操作
            Toast.makeText(context, "用户进行了截屏操作", Toast.LENGTH_SHORT).show();
        }
    }
}

方案二:使用ContentObserver监听截屏操作

​ 另一种监听截屏操作的方法是使用ContentObserver来监听系统截屏文件的变化。以下是一个示例代码:

java 复制代码
public class ScreenCaptureObserver extends ContentObserver {
    private static final String TAG = "ScreenCaptureObserver";
    private static final String SCREENSHOTS_DIR = Environment.getExternalStorageDirectory().toString() + "/Pictures/Screenshots";
    private Context mContext;

    public ScreenCaptureObserver(Context context) {
        super(null);
        mContext = context;
    }

    @Override
    public void onChange(boolean selfChange) {
        super.onChange(selfChange);
        Log.d(TAG, "Screen capture detected");
        if (uri.toString().matches(MediaStore.Images.Media.EXTERNAL_CONTENT_URI.toString() + "/[0-9]+")) {
            Cursor cursor = null;
            try {
                cursor = mContext.getContentResolver().query(uri, new String[]{MediaStore.Images.Media.DATA}, null, null, null);
                if (cursor != null && cursor.moveToFirst()) {
                    String path = cursor.getString(cursor.getColumnIndex(MediaStore.Images.Media.DATA));
                    if (path != null && path.startsWith(SCREENSHOTS_PATH)) {
                        // 此处为用户截屏行为的响应逻辑
                    }
                }
            } finally {
                if (cursor != null) {
                    cursor.close();
                }
            }
        }
    }

    public void start() {
        ContentResolver contentResolver = mContext.getContentResolver();
        contentResolver.registerContentObserver(MediaStore.Images.Media.EXTERNAL_CONTENT_URI, true, this);
    }

    public void stop() {
        ContentResolver contentResolver = mContext.getContentResolver();
        contentResolver.unregisterContentObserver(this);
    }
}

使用示例:

java 复制代码
ScreenCaptureObserver observer = new ScreenCaptureObserver(this);
observer.start();

对比与选型建议

使用 BroadcastReceiver

方案一的优点:

  • 直接监听 Intent.ACTION_SCREENSHOT,无需处理 Uri 的变化。
  • 适用于监听应用内的截屏操作。

方案一的缺点:

  • 只能监听到整个屏幕的截屏操作,无法获取到具体的截屏内容。
  • 有版本限制。从Android Q(10.0)开始,Intent.ACTION_SCREEN_CAPTURED_CHANGED字段不再被支持。
使用 ContentObserver

方案二的优点:

  • 可以监听到截屏操作发生的具体 Uri,可以进一步获取截屏的文件路径和信息。
  • 适用于监听系统级别的截屏操作。

方案二的缺点:

  • 需要指定监听的 Uri,可能需要考虑兼容性问题。
  • 需要在代码中处理 Uri 的变化,并解析截屏的文件路径。
选型建议

​ 根据需求,如果只关心截屏的发生与否,并不需要获取截屏的具体内容,方案一可以考虑。如果需要获取截屏内容的具体信息,方案二比较适合。如果只需要监听应用内的截屏操作,方案一比较方便。如果需要监听系统级别的截屏操作,需要使用方案二。

二.录屏

​ 在 Android 开发中,要监听用户的录屏操作,可以使用以下两种方案:

方案一:使用 MediaProjection API

Android 录屏 - 简书 (jianshu.com)

Android Q之后又前台服务限制

  1. 首先,在你的应用中创建一个 MediaProjectionManager 对象来获取用户录屏的权限。
java 复制代码
private MediaProjectionManager mediaProjectionManager;
private MediaProjection mediaProjection;
private MediaProjection.Callback projectionCallback;

mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
  1. 请求用户授权录屏权限,在回调中获取 MediaProjection 对象。
java 复制代码
private static final int REQUEST_CODE_SCREEN_CAPTURE = 1;

Intent intent = mediaProjectionManager.createScreenCaptureIntent();
startActivityForResult(intent, REQUEST_CODE_SCREEN_CAPTURE);

@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
    if (requestCode == REQUEST_CODE_SCREEN_CAPTURE && resultCode == RESULT_OK) {
        mediaProjection = mediaProjectionManager.getMediaProjection(resultCode, data);
        startScreenCapture();
    }
}
  1. 开始屏幕录制,并通过 MediaProjection.Callback 监听录屏状态。
java 复制代码
private VirtualDisplay virtualDisplay;
private MediaRecorder mediaRecorder;

private void startScreenCapture() {
    DisplayMetrics metrics = getResources().getDisplayMetrics();
    int screenWidth = metrics.widthPixels;
    int screenHeight = metrics.heightPixels;
    int screenDensity = metrics.densityDpi;

    mediaRecorder = new MediaRecorder();
    mediaRecorder.setVideoSource(MediaRecorder.VideoSource.SURFACE);
    mediaRecorder.setOutputFormat(MediaRecorder.OutputFormat.THREE_GPP);
    mediaRecorder.setVideoEncoder(MediaRecorder.VideoEncoder.H264);
    mediaRecorder.setVideoSize(screenWidth, screenHeight);
    mediaRecorder.setVideoFrameRate(30);
    mediaRecorder.setOutputFile(getOutputFilePath());

    Surface surface = mediaRecorder.getSurface();
    virtualDisplay = mediaProjection.createVirtualDisplay(
            "ScreenCapture",
            screenWidth,
            screenHeight,
            screenDensity,
            DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
            surface,
            null,
            null);

    mediaRecorder.prepare();
    mediaRecorder.start();

    projectionCallback = new MediaProjection.Callback() {
        @Override
        public void onStop() {
            stopScreenCapture();
        }
    };

    mediaProjection.registerCallback(projectionCallback, null);
}

private void stopScreenCapture() {
    if (virtualDisplay != null) {
        virtualDisplay.release();
        virtualDisplay = null;
    }

    if (mediaRecorder != null) {
        mediaRecorder.stop();
        mediaRecorder.reset();
        mediaRecorder.release();
        mediaRecorder = null;
    }

    if (mediaProjection != null) {
        mediaProjection.unregisterCallback(projectionCallback);
        mediaProjection.stop();
        mediaProjection = null;
    }
}

方案二:使用 ContentObserver 监听屏幕录制状态变化

  1. 创建一个继承自 ContentObserver 的观察者类,并重写 onChange() 方法。
java 复制代码
public class ScreenRecordObserver extends ContentObserver {
    private static final String SCREEN_RECORD_STATE_PATH = "/sys/class/graphics/fb0/screen_state";

    private OnScreenRecordStateChangedListener listener;

    public ScreenRecordObserver(Handler handler) {
        super(handler);
    }

    public void setOnScreenRecordStateChangedListener(OnScreenRecordStateChangedListener listener) {
        this.listener = listener;
    }

    @Override
    public void onChange(boolean selfChange, Uri uri) {
        if (listener != null) {
            boolean isRecording = isScreenRecording();
            listener.onScreenRecordStateChanged(isRecording);
        }
    }

    private boolean isScreenRecording() {
        try {
            FileInputStream fis = new FileInputStream(SCREEN_RECORD_STATE_PATH);
            BufferedReader bufferedReader = new BufferedReader(new InputStreamReader(fis));
            String state = bufferedReader.readLine();
            bufferedReader.close();

            return !"0".equals(state);
        } catch (Exception e) {
            e.printStackTrace();
        }

        return false;
    }
}

public interface OnScreenRecordStateChangedListener {
    void onScreenRecordStateChanged(boolean isRecording);
}
  1. 注册观察者,监听屏幕录制状态变化。
java 复制代码
private ScreenRecordObserver screenRecordObserver;

screenRecordObserver = new ScreenRecordObserver(new Handler());
screenRecordObserver.setOnScreenRecordStateChangedListener(new OnScreenRecordStateChangedListener() {
    @Override
    public void onScreenRecordStateChanged(boolean isRecording) {
        if (isRecording) {
            // 屏幕开始录制
        } else {
            // 屏幕停止录制
        }
    }
});

getContentResolver().registerContentObserver(Uri.parse("content://" + SCREEN_RECORD_STATE_PATH), true, screenRecordObserver);

对比与选型建议

使用 MediaProjection API

方案一的优点:

  • 监听到的录屏操作更加准确可靠,可以监听到具体的录屏开始和结束时间,同时可以获取到录屏内容。
  • 可以对录屏过程进行更加灵活的控制,如录制分辨率、帧率等。

方案一的缺点:

  • 需要申请录屏权限,用户需要给予应用访问屏幕内容的权限。
  • 需要编写更多的代码进行屏幕录制的控制。
使用 ContentObserver 监听屏幕录制状态变化

方案二的优点:

  • 不需要申请额外的权限。
  • 监听录屏状态变化的实现相对简单。

方案二的缺点:

  • 不能精确确定录屏的开始和结束时间。

  • 无法获取到录屏的具体内容。

选型建议

​ 根据需求,如果需要准确监听录屏的开始和结束时间,并且需要获取到录屏的具体内容,推荐使用方案一。

​ 但如果只是需要知道录屏状态变化,而不关心具体的时间和内容,方案二比较简单方便。

三.投屏

在 Android 开发中,要监听用户的投屏操作,可以使用以下两种方案:

方案一:使用 MediaRouter API

​ 首先,在你的 AndroidManifest.xml 文件中添加以下权限:

xml 复制代码
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  1. 创建一个 MediaRouter.Callback 对象,并重写 onRouteSelected()onRouteUnselected() 方法。
java 复制代码
private MediaRouter mediaRouter;
private MediaRouter.Callback mediaRouterCallback;

mediaRouter = (MediaRouter) getSystemService(Context.MEDIA_ROUTER_SERVICE);

mediaRouterCallback = new MediaRouter.Callback() {
    @Override
    public void onRouteSelected(MediaRouter router, MediaRouter.RouteInfo route) {
        // 投屏开始
    }
    
    @Override
    public void onRouteUnselected(MediaRouter router, MediaRouter.RouteInfo route) {
        // 投屏结束
    }
};
  1. 注册监听器,并指定监听的路由类型。
java 复制代码
mediaRouter.addCallback(MediaRouter.ROUTE_TYPE_LIVE_VIDEO, mediaRouterCallback);

方案二:使用 DisplayManager API

​ 首先,在 AndroidManifest.xml 文件中添加以下权限:

xml 复制代码
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_NETWORK_STATE" />
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
  1. 创建一个 DisplayManager.DisplayListener 对象,并重写 onDisplayAdded()onDisplayRemoved()onDisplayChanged() 方法。
java 复制代码
private DisplayManager displayManager;
private DisplayManager.DisplayListener displayListener;

displayManager = (DisplayManager) getSystemService(Context.DISPLAY_SERVICE);

displayListener = new DisplayManager.DisplayListener() {
    @Override
    public void onDisplayAdded(int displayId) {
        // 投屏开始
    }

    @Override
    public void onDisplayRemoved(int displayId) {
        // 投屏结束
    }

    @Override
    public void onDisplayChanged(int displayId) {
        // 投屏状态变化
    }
};
  1. 注册监听器。
java 复制代码
displayManager.registerDisplayListener(displayListener, null);

对比与选型建议

使用 MediaRouter API

方案一的优点:

  • 使用方便,只需要注册一个回调即可监听到投屏操作。
  • 可以监听到投屏的开始和结束时间。

方案一的缺点:

  • 只能监听到路由类型为 ROUTE_TYPE_LIVE_VIDEO 的投屏操作,其他类型的投屏操作无法监听到。
使用 DisplayManager API

方案二的优点:

  • 可以监听到所有的投屏操作,不限制特定的路由类型。
  • 可以监听到投屏的开始和结束时间。

方案二的缺点:

  • 需要注册更多的监听器和处理投屏状态变化的逻辑。
  • 无法获取到具体的投屏内容。
选型建议

​ 根据需求,如果只需要监听特定类型的投屏操作,并且需要获取投屏的具体内容,推荐使用方案一。

​ 如果需要监听所有类型的投屏操作,方案二比较适合。

​ 同时,如果只关心投屏的开始和结束时间,并不需要具体的投屏内容,两种方案都可以考虑。

相关推荐
幻雨様1 小时前
UE5多人MOBA+GAS 45、制作冲刺技能
android·ue5
Jerry说前后端3 小时前
Android 数据可视化开发:从技术选型到性能优化
android·信息可视化·性能优化
Meteors.4 小时前
Android约束布局(ConstraintLayout)常用属性
android
alexhilton5 小时前
玩转Shader之学会如何变形画布
android·kotlin·android jetpack
whysqwhw9 小时前
安卓图片性能优化技巧
android
风往哪边走9 小时前
自定义底部筛选弹框
android
Yyyy48210 小时前
MyCAT基础概念
android
Android轮子哥10 小时前
尝试解决 Android 适配最后一公里
android
雨白11 小时前
OkHttp 源码解析:enqueue 非同步流程与 Dispatcher 调度
android
风往哪边走12 小时前
自定义仿日历组件弹框
android