Android MediaProjection學習(一)之和ImageReader實現屏幕截圖

MediaProjection是什麼?

按照慣例,附上Google官方文檔鏈接: 官方文檔

A token granting applications the ability to capture screen contents and/or record system audio. The exact capabilities granted depend on the type of MediaProjection.
A screen capture session can be started through MediaProjectionManager.createScreenCaptureIntent(). This grants the ability to capture screen contents, but not system audio

根據文檔介紹。MediaProjection是手環用戶獲取屏幕內容或者記錄系統的界面視頻。你需要什麼權限取決於MediaProjection的類型。通過MediaProjectionManager.createScreenCaptureIntent()去請求系統的屏幕信息權限,但是不會錄製聲音。

MediaProjection的重要方法

官方文檔裏提供了四種方法,但是最重要的就是下面這個方法

返回值 說明
VirtualDisplay createVirtualDisplay(String name, int width, int height, int dpi, int flags, Surface surface, VirtualDisplay.Callback callback, Handler handler)Creates a VirtualDisplay to capture the contents of the screen.
參數 說明
name String: The name of the virtual display, must be non-empty.This value must never be null.
- 這個值不能爲空,用途還沒有搞明白
width int: The width of the virtual display in pixels. Must be greater than 0.
- 用px表示的確切的值,必須大於0,我們截圖的話傳進去屏幕寬度就好
height int: The height of the virtual display in pixels. Must be greater than 0.
- 同上,傳入屏幕高度
dpi int: The density of the virtual display in dpi. Must be greater than 0.
- 傳入屏幕的dpi值
flags int: A combination of virtual display flags. See DisplayManager for the full list of flags.
- virtual displays的標識組合
surface Surface: The surface to which the content of the virtual display should be rendered, or null if there is none initially.
- 這個是特別重要的一個參數,是我們的屏幕繪製的內容,會放在這個參數中回調
callback VirtualDisplay.Callback: Callback to call when the virtual display’s state changes, or null if none.
- VirtualDisplay.Callback的實例對象作爲參數,當展示的狀態發生變化時回調
handler Handler: The Handler on which the callback should be invoked, or null if the callback should be invoked on the calling thread’s main Looper.
- callback回調時要做的操作放在Handler裏

不明白?操作一下

說再多的概念也沒有實際寫一個Demo來的實在,那就寫一個。
開始我們已經瞭解到 要通過MediaProjectionManager.createScreenCaptureIntent()方法獲取一個intent來獲取權限。

Intent intent = mMediaProjectionManager.createScreenCaptureIntent();
        startActivityForResult(intent, RESULT_CODE);

其中mMediaProjectionManager是MediaProjectionManager的一個實例,RESULT_CODE是一個int的變量,我設置的是1。經過以上操作之後運行app就會有一個彈窗如圖所示:
在這裏插入圖片描述

點擊開始就可以獲取獲取屏幕內容的權限了。我們使用了startActivityForResult方法來啓動這個intent,就是爲了獲取返回值。

protected void onActivityResult(int requestCode, int resultCode, Intent data) {
        super.onActivityResult(requestCode, resultCode, data);
        switch (requestCode) {
            case RESULT_CODE:
                if (resultCode == Activity.RESULT_OK) {
                    mMediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
                    mMediaProjection.createVirtualDisplay("shot_image", windowManager.getDefaultDisplay().getWidth(),
                            windowManager.getDefaultDisplay().getHeight(),
                            displayMetrics.densityDpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader
                                    .getSurface(), null, null);
                }
                break;
        }
    }

這裏在onActivityResult方法裏用MediaProjectionManager類的getMediaProjection方法傳入回傳的數據實例化了MediaProjection的對象。
然後重頭戲來了,我們調用了createVirtualDisplay方法,上文中已經對這個方法進行了詳細的說明,前面幾個不再詳細說明,我們看看幾個關鍵參數。

1、DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR

這個參數官方的說明是:Virtual display flag: Allows content to be mirrored on private displays when no content is being shown.大概意思就是屏幕開始展示的時候就會把內容傳出來。

2、imageReader.getSurface()

這個參數實際上是通過ImageReader的getSurface()方法來獲取一個surface對象。然後把截屏內容傳給這個imageReader的實例來處理。

聊聊ImageReader

怎麼畫風一轉開始聊ImageReader呢?其實到上一步,MediaProjection的任務已經完成了。接下來要把數據解析成bitmap然後再使用就好,可以保存到本地或者加載到ImageView控件上,這裏我們直接加載到Imageview的控件上。

要轉化就一步一步來,這個surface是什麼?

A Surface is generally created by or from a consumer of image buffers (such as a SurfaceTexture, MediaRecorder, or Allocation), and is handed to some kind of producer (such as OpenGL, MediaPlayer, or CameraDevice) to draw into.

官方文檔裏說他就是一個Image的緩衝區,交給一些可以繪製Image的工具去繪製後存在這個緩衝區裏。surface我們瞭解這麼多就夠了。

接下來看看ImageReader

The ImageReader class allows direct application access to image data rendered into a Surface

這是官方的解釋,我們結合代碼來看看ImageReader:

private void startCapture() {
        mImageName = System.currentTimeMillis() + ".png";
        Image image = imageReader.acquireLatestImage();
        if (image == null)
            return;
        int width = image.getWidth();
        int height = image.getHeight();
        final Image.Plane[] planes = image.getPlanes();
        final ByteBuffer buffer = planes[0].getBuffer();
        int pixelStride = planes[0].getPixelStride();
        int rowStride = planes[0].getRowStride();
        int rowPadding = rowStride - pixelStride * width;
        Bitmap bitmap = Bitmap.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888);
        bitmap.copyPixelsFromBuffer(buffer);
        bitmap = Bitmap.createBitmap(bitmap, 0, 0, width, height);
        if (bitmap != null)
            iv_screen.setImageBitmap(bitmap);
        image.close();
    }

經過上面幾行代碼之後我們就把拿到的數據轉換成了bitmap,這裏面主要說明的有幾個方法。

1、Image image = imageReader.acquireLatestImage();

acquireLatestImage()
Acquire the latest Image from the ImageReader’s queue, dropping older images.

獲得最新的Image從ImageReader的隊列裏,丟掉舊的images。

這裏斜體字可以不看
之前我通過這個方法判斷獲取的 Image是否爲空,判斷完以後再去獲取發現還是空,最後看了官方文檔才知道會丟掉這張Image所以你判斷完的時候就再也獲取不到了,所以就要先獲取再判斷

迴歸正題,下面就看看另外一句關鍵的代碼

2、後面的Image轉換爲bitmap的代碼,我還不是太懂。求告知。

Final

通過以上的操作就可以實現屏幕截圖了,那段轉換代碼我決定去求助一下別人。主要過程都在博文裏說了,大家可以自己多看看,不會也可以問我。

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章