首先,在Manifest文件中添加自定义的前台服务:
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools">
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION" />
<application>
<service android:name=".service.MediaProjectionService"
android:foregroundServiceType="mediaProjection" />
</application>
</manifest>
启动投屏服务时,先获取MediaProjectionManager:
MediaProjectionManager MEDIA_PROJECTION_MANAGER = (MediaProjectionManager) App.getApp().getSystemService(Context.MEDIA_PROJECTION_SERVICE)
启动Activity来请求录制屏幕:
// RequestCode自己定义
startActivityForResult(MEDIA_PROJECTION_MANAGER.createScreenCaptureIntent(), RequestCode.START_MEDIA_PROJECTION);
此时系统会显示请求弹窗:
处理返回结果,判断弹窗成功授权后启动服务:
@Override
protected void onActivityResult(int requestCode, int resultCode, @Nullable Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode != RequestCode.START_MEDIA_PROJECTION) {
return;
}
if (resultCode == Activity.RESULT_OK) {
// 自定义变量保存resultCode、resultData字段,服务创建时会用到
MediaProjectionService.resultCode = resultCode;
MediaProjectionService.resultData = resultData;
Intent SERVICE_INTENT = new Intent(this, MediaProjectionService.class);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
startForegroundService(SERVICE_INTENT);
} else {
startService(SERVICE_INTENT);
}
} else {
// 录制服务启动失败
}
}
增加服务类型判断:
public interface ServiceType {
// 截图
int SCREENSHOT = 0;
// 投屏
int PROJECTION = 1;
}
服务创建时(注意,一定要先发送前台服务通知再获取MediaProjection):
public static int serviceType = ServiceType.SCREENSHOT;
public static int resultCode;
public static Intent resultData;
@Override
public void onCreate() {
super.onCreate();
startMediaProjectionForeground();
mMediaProjection = MEDIA_PROJECTION_MANAGER.getMediaProjection(resultCode, resultData);
if (serviceType == ServiceType.SCREENSHOT) {
createImageReaderVirtualDisplay();
} else if (serviceType == ServiceType.PROJECTION) {
createProjectionVirtualDisplay();
}
}
发送通知:
private void startMediaProjectionForeground() {
NotificationManager NOTIFICATION_MANAGER = (NotificationManager) getSystemService(Context.NOTIFICATION_SERVICE);
Notification.Builder notificationBuilder = new Notification.Builder(this)
.setSmallIcon(R.mipmap.ic_launcher)
.setContentTitle("服务已启动");
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
String channelId = "CHANNEL_ID_MEDIA_PROJECTION";
NotificationChannel channel = new NotificationChannel(channelId, "屏幕录制", NotificationManager.IMPORTANCE_HIGH);
NOTIFICATION_MANAGER.createNotificationChannel(channel);
notificationBuilder.setChannelId(channelId);
}
Notification notification = notificationBuilder.build();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
startForeground(1, notification, ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(1, notification);
}
}
使用ImageReader记录画面:
private static final MediaProjection.Callback MEDIA_PROJECTION_CALLBACK = new MediaProjection.Callback() {
};
private void createImageReaderVirtualDisplay() {
if (mMediaProjection != null) {
WindowManager WINDOW_MANAGER = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
WINDOW_MANAGER.getDefaultDisplay().getRealMetrics(dm);
mImageReader = ImageReader.newInstance(dm.widthPixels, dm.heightPixels, PixelFormat.RGBA_8888, 1);
mImageReader.setOnImageAvailableListener(reader -> {
mImageAvailable = true;
}, null);
mMediaProjection.registerCallback(MEDIA_PROJECTION_CALLBACK, null);
mVirtualDisplayImageReader = mMediaProjection.createVirtualDisplay("ImageReader", dm.widthPixels, dm.heightPixels, dm.densityDpi, Display.FLAG_ROUND, mImageReader.getSurface(), null, null);
}
}
public static void screenshot() {
if (!mImageAvailable) {
Log.e(TAG, "screenshot: mImageAvailable is false");
ToastUtils.shortCall("截屏失败");
return;
}
if (mImageReader == null) {
Log.e(TAG, "screenshot: mImageReader is null");
ToastUtils.shortCall("截屏失败");
return;
}
try {
Image image = mImageReader.acquireLatestImage();
// 获取数据
int width = image.getWidth();
int height = image.getHeight();
final Image.Plane plane = image.getPlanes()[0];
final ByteBuffer buffer = plane.getBuffer();
// 重新计算Bitmap宽度,防止Bitmap显示错位
int pixelStride = plane.getPixelStride();
int rowStride = plane.getRowStride();
int rowPadding = rowStride - pixelStride * width;
int bitmapWidth = width + rowPadding / pixelStride;
// 创建Bitmap
Bitmap bitmap = Bitmap.createBitmap(bitmapWidth, height, Bitmap.Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
// 释放资源
image.close();
// 裁剪Bitmap,因为重新计算宽度原因,会导致Bitmap宽度偏大
Bitmap result = Bitmap.createBitmap(bitmap, 0, 0, width, height);
bitmap.recycle();
String fileName = createScreenshotFileName();
File file = new File(App.getApp().getExternalFilesDir(null).getParent(), fileName);
if (!file.exists()) {
file.createNewFile();
}
FileOutputStream fos = new FileOutputStream(file);
BufferedOutputStream bos = new BufferedOutputStream(fos);
result.compress(Bitmap.CompressFormat.PNG, 100, bos);
bos.close();
result.recycle();
ToastUtils.longCall("截图成功!"+fileName);
} catch (IOException e) {
e.printStackTrace();
ToastUtils.longCall("截图失败!");
}
}
private static String createScreenshotFileName() {
Calendar calendar = Calendar.getInstance(Locale.CHINA);
int year = calendar.get(Calendar.YEAR);
int month = calendar.get(Calendar.MONTH) + 1;
int day = calendar.get(Calendar.DAY_OF_MONTH);
int hour = calendar.get(Calendar.HOUR_OF_DAY);
int minute = calendar.get(Calendar.MINUTE);
int second = calendar.get(Calendar.SECOND);
return String.format("Screenshot-%d%02d%02d%02d%02d%02d.png", year, month, day, hour, minute, second);
}
即可实现截图功能。
投屏功能和截图功能类似,只不过需要将SurfaceView的Surface传入其中。方法如下:
public void createProjectionVirtualDisplay() {
if (mMediaProjection != null && ProjectionView.isSurfaceCreated()) {
WindowManager WINDOW_MANAGER = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
DisplayMetrics dm = new DisplayMetrics();
WINDOW_MANAGER.getDefaultDisplay().getRealMetrics(dm);
mMediaProjection.registerCallback(MEDIA_PROJECTION_CALLBACK, null);
mVirtualDisplayProjection = mMediaProjection.createVirtualDisplay("Projection", dm.widthPixels, dm.heightPixels, dm.densityDpi, Display.FLAG_ROUND, /*Surface*/, null, null);
}
}
这里写了一个小Demo,投屏效果演示(小的窗口是悬浮窗,里面是SurfaceView,显示的是当前屏幕的内容):
Demo的源代码:
https://github.com/MagicianGuo/Android-MediaProjectionDemo