背景
自 Android API 21 起,应用程序可以全局截屏并录制屏幕。
问题
我在互联网上找到了一些示例代码,但是它存在一些问题:
- 它非常慢。也许可以通过避免在真正不需要时删除通知来避免多次截图的问题。
- 左右两侧有黑色边距,这意味着计算可能出现了问题:
我的尝试
MainActivity.java
public class MainActivity extends AppCompatActivity {
private static final int REQUEST_ID = 1;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
findViewById(R.id.checkIfPossibleToRecordButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.requestScreenshotPermission(MainActivity.this, REQUEST_ID);
}
});
findViewById(R.id.takeScreenshotButton).setOnClickListener(new OnClickListener() {
@Override
public void onClick(final View v) {
ScreenshotManager.INSTANCE.takeScreenshot(MainActivity.this);
}
});
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
if (requestCode == REQUEST_ID)
ScreenshotManager.INSTANCE.onActivityResult(resultCode, data);
}
}
布局文件:activity_main.xml
<LinearLayout
android:id="@+id/rootView"
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center"
android:orientation="vertical">
<Button
android:id="@+id/checkIfPossibleToRecordButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="request if possible"/>
<Button
android:id="@+id/takeScreenshotButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="take screenshot"/>
</LinearLayout>
屏幕截图管理器
public class ScreenshotManager {
private static final String SCREENCAP_NAME = "screencap";
private static final int VIRTUAL_DISPLAY_FLAGS = DisplayManager.VIRTUAL_DISPLAY_FLAG_OWN_CONTENT_ONLY | DisplayManager.VIRTUAL_DISPLAY_FLAG_PUBLIC;
public static final ScreenshotManager INSTANCE = new ScreenshotManager();
private Intent mIntent;
private ScreenshotManager() {
}
public void requestScreenshotPermission(@NonNull Activity activity, int requestId) {
MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) activity.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
activity.startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), requestId);
}
public void onActivityResult(int resultCode, Intent data) {
if (resultCode == Activity.RESULT_OK && data != null)
mIntent = data;
else mIntent = null;
}
@UiThread
public boolean takeScreenshot(@NonNull Context context) {
if (mIntent == null)
return false;
final MediaProjectionManager mediaProjectionManager = (MediaProjectionManager) context.getSystemService(Context.MEDIA_PROJECTION_SERVICE);
final MediaProjection mediaProjection = mediaProjectionManager.getMediaProjection(Activity.RESULT_OK, mIntent);
if (mediaProjection == null)
return false;
final int density = context.getResources().getDisplayMetrics().densityDpi;
final Display display = ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE)).getDefaultDisplay();
final Point size = new Point();
display.getSize(size);
final int width = size.x, height = size.y;
// start capture reader
final ImageReader imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1);
final VirtualDisplay virtualDisplay = mediaProjection.createVirtualDisplay(SCREENCAP_NAME, width, height, density, VIRTUAL_DISPLAY_FLAGS, imageReader.getSurface(), null, null);
imageReader.setOnImageAvailableListener(new OnImageAvailableListener() {
@Override
public void onImageAvailable(final ImageReader reader) {
Log.d("AppLog", "onImageAvailable");
mediaProjection.stop();
new AsyncTask<Void, Void, Bitmap>() {
@Override
protected Bitmap doInBackground(final Void... params) {
Image image = null;
Bitmap bitmap = null;
try {
image = reader.acquireLatestImage();
if (image != null) {
Plane[] planes = image.getPlanes();
ByteBuffer buffer = planes[0].getBuffer();
int pixelStride = planes[0].getPixelStride(), rowStride = planes[0].getRowStride(), rowPadding = rowStride - pixelStride * width;
bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Config.ARGB_8888);
bitmap.copyPixelsFromBuffer(buffer);
return bitmap;
}
} catch (Exception e) {
if (bitmap != null)
bitmap.recycle();
e.printStackTrace();
}
if (image != null)
image.close();
reader.close();
return null;
}
@Override
protected void onPostExecute(final Bitmap bitmap) {
super.onPostExecute(bitmap);
Log.d("AppLog", "Got bitmap?" + (bitmap != null));
}
}.execute();
}
}, null);
mediaProjection.registerCallback(new Callback() {
@Override
public void onStop() {
super.onStop();
if (virtualDisplay != null)
virtualDisplay.release();
imageReader.setOnImageAvailableListener(null, null);
mediaProjection.unregisterCallback(this);
}
}, null);
return true;
}
}
这些问题
关于这些问题:
- 为什么很慢?有方法可以提高吗?
- 如何避免在拍照时移除截图通知?什么时候可以移除通知?这个通知是否意味着它会一直拍照?
- 为什么输出的位图(因为它仍处于 POC 状态,所以目前我没有对它进行任何操作)有黑色边缘?在这方面的代码有什么问题?
注意:我不想只截取当前应用程序的屏幕截图。我想知道如何在全局范围内使用它,即所有应用程序都可以使用它,据我所知,这只能通过使用此 API 来实现。
编辑:我注意到在 CommonsWare 网站上(这里),它说输出的位图由于某种原因会更大,但与我注意到的情况(开头和结尾都有黑色边距)相反,它应该在结尾处:
由于无法解释的原因,它会稍微大一点,在每一行的末尾都有多余的未使用像素。
我尝试了那里提供的内容,但它会崩溃并显示“java.lang.RuntimeException:缓冲区对于像素来说不够大”异常。