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