最近在研究怎么在无障碍服务中使用截屏;使用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>