对于Android 5.0以上,系统提供了一个可用于截(录)屏的API,这里主要讲一下利用MediaProjection截屏并取出图片byte[]数据的方法以及自己在使用过程中踩到的坑.
主要涉及到的类有MediaProjectionManager,MediaProjection,ImageReader,
VirtualDisplay等;
2.使用步骤
a.MediaProjection使用时需要经过用户授权因此需要一个Activity作为载体来完成授权操作,这里在参考网上的做法实现一个完全透明的Activity来完成授权操作:
startActivityForResult(((MediaProjectionManager)getSystemService(Context.MEDIA_PROJECTION_SERVICE)).createScreenCaptureIntent(),
REQUEST_MEDIA_PROJECTION
)
a.获取MediaProjectionManager对象,该对象是一个系统服务,可通getSystemService(Context.MEDIA_PROJECTION_SERVICE)获取,
获取完成后利用manager创建授权需要的Intent:
Intent intent = mediaProjectionManager.createScreenCaptureIntent();
然后startActivityForResult(intent,requestCode);
然后在onActivityResult()方法中获取到授权成功的回执即Intent数据,需要注意的是MediaProjection的创建需要该Intent,如果不希望在onActivityResult中直接创建MediaProjection对象,那么需要将该Intent保存起来。
b.创建MediaProjection对象:
mMediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mRequestPermissionData)
c.创建读取数据需要的ImageReader
mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, 3)
d.创建虚拟的屏幕投影
mVirtualDisplay = mMediaProjection.createVirtualDisplay("screen-mirror", mScreenWidth,
mScreenHeight, mDensityDpi,
DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR,
mImageReader.getSurface(), null, null)
e.读取截屏数据,自己在写代码的时候发现,以上对象的创建完成需要一定的时间,从创建对象到图片数据真正可用需要在OnImageAvailableListener的回调方法中完成:
mImageReader.setOnImageAvailableListener(new ImageReader.OnImageAvailableListener() {
@Override
public void onImageAvailable(ImageReader reader) {
if (mFirstScreencap) {
Image image = mImageReader.acquireNextImage();
if (image != null) {
updateImageParams(image.getWidth(), image.getHeight());
mFrameBytes = getRgbaDataFromImage(image);
if (onShotListener != null) {
onShotListener.onReady();
}
mAvailabled = true;
mFirstScreencap = false;
mImageReader.setOnImageAvailableListener(null, null);
image.close();
}
}
}
}, mImageReaderHandler);
f.取出byte[]数据并完成图片数据修正
private static byte[] getRgbaDataFromImage(Image image) {
int width = image.getWidth();
int height = image.getHeight();
final Image.Plane[] planes = image.getPlanes();
final ByteBuffer buffer = planes[0].getBuffer();
int rowStride = planes[0].getRowStride();
byte[] data = new byte[width * height * BYTES_PER_PIXEL];
if (rowStride == width * BYTES_PER_PIXEL) {
buffer.get(data);
return data;
}
ByteBuffer destBuffer = ByteBuffer.wrap(data);
byte[] buf = new byte[rowStride];
int byteCount = width * BYTES_PER_PIXEL;
for (int i = 0; i < height; i++) {
buffer.get(buf);
destBuffer.put(buf, 0, byteCount);
}
return data;
}
g.返回最终的byte[]数据
public byte[] getFrameBuffer() {
Image image = mImageReader.acquireNextImage();
if (image == null) {
return mFrameBytes;
} else {
mFrameBytes = getRgbaDataFromImage(image);
updateImageParams(image.getWidth(), image.getHeight());
image.close();
return mFrameBytes;
}
}
3.总结
mImageReader.acquireNextImage();注意该方法不是每时每刻都有值返回,只有当surface里面有新的缓存图像可用时才有数据返回,因此在使用时应该将每次取到的数据缓存起来,当没有新的图像返回时,直接使用上一次的缓存数据。
ByteBuffer里面取出的byte数组中可能包含一些无用的数据信息,需要将其修正。
第一次写博客,希望大家别喷哈~~