如何在全球范围内正确拍摄屏幕截图? [英] How to properly take a screenshot, globally?

查看:83
本文介绍了如何在全球范围内正确拍摄屏幕截图?的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

从Android API 21开始,应用程序可以在全球范围内截取屏幕截图并记录屏幕.

Since Android API 21, it's possible for apps to take screenshots globally and record the screen.

我已经在Internet上找到了所有示例代码,但是有一些问题:

I've made a sample code out of all I've found on the Internet, but it has a few issues:

  1. 这很慢.也许有可能避免这种情况,至少对于多个​​屏幕截图而言,是通过避免在真正不需要之前将其删除的通知来避免.
  2. 左右两侧有黑色边距,这意味着计算可能有问题:

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);
    }
}

layout/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>

ScreenshotManager

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;
    }
}

问题

是关于问题的:

  1. 为什么这么慢?有办法改善吗?
  2. 如何在两次截屏之间避免删除它们的通知?我什么时候可以删除通知?通知是否意味着它会不断拍摄屏幕截图?
  3. 为什么输出位图(当前我不对其进行任何处理,因为它仍然是POC)中有黑色边距?此问题中的代码有什么问题?

注意:我不想仅截取当前应用程序的屏幕截图.我想知道如何在所有应用程序中全局使用它,据我所知,只有通过使用此API才能正式实现.

NOTE: I don't want to take a screenshot only of the current app. I want to know how to use it globally, for all apps, which is possible officially only by using this API, as far as I know.

我注意到在CommonsWare网站上( 此处 ),据说输出位图由于某种原因而较大,但与我所注意到的(开始和结束处的黑色边距)相反,它说应该在末尾:

I've noticed that on CommonsWare website (here), it is said that the output bitmap is larger for some reason, but as opposed to what I've noticed (black margin in beginning AND end), it says it's supposed to be in the end:

出于莫名其妙的原因,它会更大一些,有多余的未使用每一行的像素.

For inexplicable reasons, it will be a bit larger, with excess unused pixels on each row on the end.

我尝试了那里提供的功能,但由于"java.lang.RuntimeException:缓冲区不足以容纳像素"而崩溃.

I've tried what was offered there, but it crashes with the exception "java.lang.RuntimeException: Buffer not large enough for pixels" .

推荐答案

为什么输出位图(当前我不对其进行任何处理,因为它仍然是POC)中有黑色边距?此问题中的代码有什么问题?

由于您未使用所在窗口的realSize,因此屏幕快照周围有黑色空白.要解决此问题:

You have black margins around your screenshot because you are not using realSize of the window you're in. To solve this:

  1. 获取窗口的实际大小:




    final Point windowSize = new Point();
    WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
    windowManager.getDefaultDisplay().getRealSize(windowSize);


  1. 使用它来创建图像阅读器:




    imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);


  1. 可能不需要这第三步,但我在我的应用的生产代码中看到了其他内容(该代码可在各种android设备上运行).当您获取ImageReader的图像并从中创建位图时.使用以下代码,使用窗口大小来裁剪该位图.




    // fix the extra width from Image
    Bitmap croppedBitmap;
    try {
        croppedBitmap = Bitmap.createBitmap(bitmap, 0, 0, windowSize.x, windowSize.y);
    } catch (OutOfMemoryError e) {
        Timber.d(e, "Out of memory when cropping bitmap of screen size");
        croppedBitmap = bitmap;
    }
    if (croppedBitmap != bitmap) {
        bitmap.recycle();
    }


我不想仅截取当前应用程序的屏幕截图.我想知道如何在所有应用程序中全局使用它,据我所知,只有通过使用此API才能正式实现.

要捕获屏幕截图/屏幕截图,您需要一个MediaProjection对象.要创建这样的对象,您需要一对resultCode(int)和Intent.您已经知道如何获取这些对象并将其缓存在ScreenshotManager类中.

To capture screen/take screenshot you need an object of MediaProjection. To create such object, you need pair of resultCode (int) and Intent. You already know how these objects are acquired and cache those in your ScreenshotManager class.

回到获取任何应用程序的屏幕快照,您需要遵循获取这些变量resultCode和Intent的相同过程,但与其将其本地缓存在类变量中,还需要启动后台服务并将这些变量传递给任何其他人其他正常参数.看看Telecine是如何做到的

Coming back to taking screenshots of any app, you need to follow the same procedure of getting these variables resultCode and Intent but instead of caching it locally in your class variables, start a background service and pass these variables to the same like any other normal parameters. Take a look at how Telecine does it here. When this background service is started it can provide a trigger (a notification button) to the user which when clicked, will perform the same operations of capturing screen/taking screenshot as you are doing in your ScreenshotManager class.

为什么这么慢?有办法改善吗?

这对您的期望有多慢?我的Media Projection API用例是截取屏幕截图并将其呈现给用户进行编辑.对我而言,速度已经足够了.我觉得值得一提的是ImageReader类可以在setOnImageAvailableListener中接受线程的Handler.如果在此处提供处理程序,则将在处理程序线程上触发onImageAvailable回调,而不是创建ImageReader的回调.当图像可用时,这将帮助您避免创建AsyncTask(并启动它),而不是回调本身将在后台线程中发生.这就是我创建ImageReader的方式:

How much slow is it to your expectations? My use case for Media projection API is to take a screenshot and present it to the user for editing. For me the speed is decent enough. One thing I feel worth mentioning is that the ImageReader class can accept a Handler to a thread in setOnImageAvailableListener. If you provide a handler there, onImageAvailable callback will be triggered on the handler thread instead of the one that created the ImageReader. This will help you in NOT creating a AsyncTask (and starting it) when an image is available instead the callback itself will happen on a background thread. This is how I create my ImageReader:




    private void createImageReader() {
            startBackgroundThread();
            imageReader = ImageReader.newInstance(windowSize.x, windowSize.y, PixelFormat.RGBA_8888, MAX_IMAGES);
            ImageHandler imageHandler = new ImageHandler(context, domainModel, windowSize, this, notificationManager, analytics);
            imageReader.setOnImageAvailableListener(imageHandler, backgroundHandler);
        }

    private void startBackgroundThread() {
            backgroundThread = new HandlerThread(NAME_VIRTUAL_DISPLAY);
            backgroundThread.start();
            backgroundHandler = new Handler(backgroundThread.getLooper());
        }


这篇关于如何在全球范围内正确拍摄屏幕截图?的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆