返回

Android开发,无障碍服务类中使用mediaProjection获取屏幕的方法

最近在研究怎么在无障碍服务中使用截屏;使用mediaProjection确实可以实现;我主要是在无障碍服务类中监听mediaProjection授权结果,然后处理授权的其他逻辑,但在监听之后的projectionManager.getMediaProjection(resultCode, data)一直遇到报错 java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
其实以上功能也是请教了AI(费了好大功夫)才基本有这个逻辑; 现在隔了几个月重新拾起, 终于解决了这个报错问题

弹出授权提示

//MainActivity.java
        Button startCaptureButton = findViewById(R.id.startCaptureButton);
        startCaptureButton.setOnClickListener(new View.OnClickListener() {
            @Override
            public void onClick(View v) {
                // 在请求权限之前可以弹出提示,告知用户原因
                new AlertDialog.Builder(MainActivity.this)
                        .setTitle("请求屏幕捕获权限")
                        .setMessage("我们需要您的同意来捕获屏幕内容。")
                        .setPositiveButton("同意", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                // 启动ScreenCapturePermissionActivity
                                Intent intent = new Intent(MainActivity.this, ScreenCapturePermissionActivity.class);
                                startActivity(intent);
                            }
                        })
                        .setNegativeButton("拒绝", new DialogInterface.OnClickListener() {
                            @Override
                            public void onClick(DialogInterface dialog, int which) {
                                dialog.dismiss();
                            }
                        })
                        .show();
            }
        });

以上使用了 ScreenCapturePermissionActivity,所以下一步是

新建 ScreenCapturePermissionActivity

public class ScreenCapturePermissionActivity extends Activity {
    private static final int REQUEST_CODE = 1001;

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        Log.d("BaseService", "aconCreate: ");
        super.onCreate(savedInstanceState);
        MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        //请求MediaProjection权限
        startActivityForResult(projectionManager.createScreenCaptureIntent(), REQUEST_CODE);
        Log.d("BaseService", "aconCreate: end");
    }

    @Override
    protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
        if (requestCode == REQUEST_CODE && resultCode == RESULT_OK) {
            Intent intent = new Intent("ACTION_MEDIA_PROJECTION_PERMISSION");
            intent.putExtra("resultCode", resultCode);
            intent.putExtra("data", data);
            Log.d("BaseService", String.valueOf(resultCode));
            Log.d("BaseService", String.valueOf(data));
            LocalBroadcastManager.getInstance(this).sendBroadcast(intent);
        }
        finish();
    }
}

ScreenCapturePermissionActivity 中主要是调起mediaProjection授权请求弹窗,当确认后,会广播授权结果:LocalBroadcastManager.getInstance(this).sendBroadcast(intent);

无障碍服务接收

//你的无障碍服务类
    @Override
    public void onCreate() {
        //super.onCreate();
        createNotificationChannel();
    }
	
	    /* 1.创建通知渠道
    * */
    private void createNotificationChannel() {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
            NotificationChannel serviceChannel = new NotificationChannel(
                    CHANNEL_ID,
                    "屏幕捕获服务",
                    NotificationManager.IMPORTANCE_DEFAULT
            );
            NotificationManager manager = getSystemService(NotificationManager.class);
            if (manager != null) {
                manager.createNotificationChannel(serviceChannel);
            }
        }
    }
    /* 开启前台服务
    * */
    private void startForegroundService() {
        // 创建通知(必须先createNotificationChannel())
        Notification notification = new NotificationCompat.Builder(this, CHANNEL_ID)
                .setContentTitle("屏幕捕获中")
                .setContentText("应用正在捕获屏幕,您可以在此处查看信息。")
                .setSmallIcon(R.mipmap.ic_launcher) // 替换为你的通知图标
                .setPriority(NotificationCompat.PRIORITY_DEFAULT) // 设置优先级
                .setOngoing(true) // 设置为进行中的通知
                .build();
        // 启动前台服务
        startForeground(1, notification);
    }
	
	@Override
    protected void onServiceConnected() {
	        IntentFilter filter = new IntentFilter("ACTION_MEDIA_PROJECTION_PERMISSION");
        LocalBroadcastManager.getInstance(this).registerReceiver(mediaProjectionReceiver, filter);
	}
	    /*截图权限相关 mediaProjection授权 监听intenl广播*/
    private BroadcastReceiver mediaProjectionReceiver = new BroadcastReceiver() {
        @Override
        public void onReceive(Context context, Intent intent) {
        int resultCode = intent.getIntExtra("resultCode", Activity.RESULT_CANCELED);
        Intent data = intent.getParcelableExtra("data");

        // 启动前台服务 否则报错java.lang.SecurityException: Media projections require a foreground service of type ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION
        startForegroundService();

        MediaProjectionManager projectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
        mediaProjection = projectionManager.getMediaProjection(resultCode, data);
        setupVirtualDisplay();
        }
    };
    /*前提mediaProjection授权成功-创建虚拟屏幕,获得屏幕内容,传递到imageReader*/
    private void setupVirtualDisplay() {
        Log.d(TAG, "setupVirtualDisplay: ");
        if (mediaProjection == null) return;

        DisplayMetrics metrics = getResources().getDisplayMetrics();
        imageReader = ImageReader.newInstance(metrics.widthPixels, metrics.heightPixels, ImageFormat.RGB_565, 2);
        mediaProjection.createVirtualDisplay("WinlamsScreenCapture",
                metrics.widthPixels, metrics.heightPixels, metrics.densityDpi,
                DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
                imageReader.getSurface(), null, null);
        Log.d(TAG, "setupVirtualDisplay: ");
    }
	    /*获取当前屏幕*/
    public Mat getScreenCaptureMat() {
        Log.d(TAG, "getScreenCaptureMat: ");
        if (mediaProjection == null) {
            return null;
        }

        try {
            Image image = imageReader.acquireLatestImage();
            if (image == null) {
                return null;
            }

            Image.Plane[] planes = image.getPlanes();
            ByteBuffer buffer = planes[0].getBuffer();
            int pixelStride = planes[0].getPixelStride();
            int rowStride = planes[0].getRowStride();
            int rowPadding = rowStride - pixelStride * imageReader.getWidth();

            Bitmap bitmap = Bitmap.createBitmap(imageReader.getWidth() + rowPadding / pixelStride,
                    imageReader.getHeight(), Bitmap.Config.ARGB_8888);
            bitmap.copyPixelsFromBuffer(buffer);

            // Convert Bitmap to Mat
            Mat mat = new Mat();
            Utils.bitmapToMat(bitmap, mat);

            image.close();
            return mat;
        } catch (Exception e) {
            Log.e(TAG, "Error capturing screen: " + e.getMessage());
            return null;
        }
    }

以上 getScreenCaptureMat() 函数就是授权成功后主要的获取截屏方法了

AndroidManifest调整

如果没有声明相关权限,需要增加上去

	
	<uses-permission android:name="android.permission.RECEIVE_NOTIFICATIONS" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
    <uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
    <uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW" />
	
	<application ...
		<activity
            android:name=".backactity.ScreenCapturePermissionActivity" />
	
		<service
            android:name=".service.AccessService"
            android:exported="false"
            android:label="@string/access_name"
            android:permission="android.permission.BIND_ACCESSIBILITY_SERVICE"
            android:foregroundServiceType="mediaProjection">
            <intent-filter>
                <action android:name="android.accessibilityservice.AccessibilityService" />
            </intent-filter>

            <meta-data
                android:name="android.accessibilityservice"
                android:resource="@xml/accessibilityservice_confing" />
        </service>

评论