簡單的截屏和錄屏功能。
因為MediaProjection是5.0以上才出現的,所以今天所講述功能實現,只在5.0以上的系統有效。
截屏:
步驟如下:
1:獲取MediaProjectionManager
2:通過MediaProjectionManager.createScreenCaptureIntent()獲取Intent
3:通過startActivityForResult傳入Intent然后在onActivityResult中通過MediaProjectionManager.getMediaProjection(resultCode,data)獲取MediaProjection
4:創建ImageReader,構建VirtualDisplay
5:最后就是通過ImageReader截圖,就可以從ImageReader里獲得Image對象。
6:將Image對象轉換成bitmap
實現:
步驟已經給出了,我們就按照步驟來實現代碼吧。
首先MediaProjectionManager是系統服務,我們通過getSystemService(MEDIA_PROJECTION_SERVICE)獲取它
然后調用startActivityForResult傳入projectionManager.createScreenCaptureIntent()創建的Intent
緊接著我們就可以在onActivityResult(int requestCode, int resultCode, Intent data)中通過resultCode和data來獲取MediaProjection
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { if(requestCode == SCREEN_SHOT){ if(resultCode == RESULT_OK){ //獲取MediaProjection mediaProjection = projectionManager.getMediaProjection(requestCode,data); } } }然后就是創建ImageReader和VirtualDisplay
imageReader = ImageReader.newInstance(width, height, PixelFormat.RGBA_8888, 1); if(imageReader!=null){ Log.d(TAG, "imageReader Successful"); } mediaProjection.createVirtualDisplay("ScreenShout", width,height,dpi, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, imageReader.getSurface(),null,null);這里我們依次講解一下。
首先是ImageReader.newInstance方法:
方法里接收四個參數。
前兩個width,height是用來指定生成圖像的寬和高。
第三個參數format是圖像的格式,這個格式必須是ImageFormat或PixelFormat中的一個,這兩個Format里有很多格式,大家可以點進去看看,我們例子中使用的是PixelFormat.RGBA_8888格式(需要注意的是并不是所有的格式都被ImageReader支持,比如說ImageFormat.NV21)。
第四個參數是maxImages,這個參數指的是你想同時在ImageReader里獲取到的Image對象的個數,這個參數我不是很懂,我不理解同時的意思。我的理解是ImageReader是一個類似數組的東西,然后我們可以通過acquireLatestImage()或acquireNextImage()方法來得到里面的Image對象(可能有誤,僅供參考)。這個值應該設置的越小越好,但是得大于0,所以我們上面設置的是1。
然后我們看看mediaProjection.createVirtualDisplay方法:
createVirtualDisplay(@NonNull String name, int width, int height, int dpi, int flags, @Nullable Surface surface, @Nullable VirtualDisplay.Callback callback, @Nullable Handler handler)
首先這個方法返回的是VirtualDisplay。
前四個不用說了,分別是VirtualDisplay的名字,寬,高和dpi。
第五個參數,大家可以點 DisplayManager查看所有的flags,我沒有具體的研究過,在本次要實現的例子里,除了VIRTUAL_DISPLAY_FLAG_SECURE這個會報錯,其他的flags效果都一樣。
第六個參數,是一個Surface。我這里表達一下我的理解,當VirtualDisplay被創建出來時,也就是createVirtualDisplay調用后,你在真實屏幕上的每一幀都會輸入到Surface參數里。也就是說,如果你放個SurfaceView,然后傳入SurfaceView的Surface那么你在屏幕上的操作都會顯示在SurfaceView里(這里我們后面錄屏會講)。我們這里傳入的是ImageReader的Surface。這其中的邏輯我的理解是這樣的,真實屏幕的每一幀都都會傳給ImageReader,根據ImageReader的maxImages參數,比如說maxImages是2,那么ImageReader始終保持兩幀圖片,但這兩幀圖片是一直隨著真實屏幕的操作而更新的(不知道大家有沒有聽懂)。
第七個參數,是一個回調函數,在VirtualDisplay狀態改變時調用。因為我們這里沒有,所以傳null。
第八個參數,這里我給出原文:“The Handler on which the callback should be invoked, or null if the callback should be invoked on the calling thread's main Looper.”因為我翻譯不好。不過和普通的Handler使用場景類似。
現在我們ImageReader和VirtualDisplay,接下來我們就可以通過ImageReader的acquireLatestImage()或acquireNextImage()來得到Image對象了。
SystemClock.sleep(1000);Image image = imageReader.acquireNextImage();
這里有個坑,就是你在獲取Image的時候,得先暫停1秒左右,不然就會獲取失敗(原因未知)。
現在我們有了Image對象,但是Image對象并不能直接作為UI資源被使用,我們可以將它轉換成Bitmap對象。
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.createBitmap(width + rowPadding / pixelStride, height, Bitmap.Config.ARGB_8888); bitmap.copyPixelsFromBuffer(buffer); image.close();
這里最主要的邏輯就是像素與字節的轉換,我們需要將Image對象的字節流寫進Bitmap里,但是Bitmap接收的是像素格式的。
我們一行一行來看:
首先獲取image對象的寬和高,注意width和height是像素格式的。
然后獲取ByteBuffer,里面存放的就是圖片的字節流,是字節格式的。我是這么理解的,ByteBuffer里面是一長串的字節序列,按照某種格式分成行列就變成了圖片。
然后獲取PixelStride,這指的是兩個像素的距離(就是一個像素頭部到相鄰像素的頭部),這是字節格式的。
RowStride是一行占用的距離(就是一行像素頭部到相鄰行像素的頭部),這個大小和width有關,這里需要注意,因為內存對齊的原因,所以每行會有一些空余。這個值也是字節格式的。
緊接著我們需要創建一個Bitmap用來接受Image的buffer的輸入,buffer是字節流,它會按照我們設置的format轉換成像素,所以這里最重要的一個地方就是Bitmap創建的大小,因為高度就是行數所以就是height,但是寬度因為上面說的內存對齊問題會有些空余,所以我們要先求出空余部分,然后加上width。
int rowPadding = rowStride - pixelStride * width;
這句話用整行的距離減去了一行里像素及空隙占用的距離,剩下的就是空余部分。但是這個是字節格式的。我們將它除以pixelStride,也就是一個像素及空隙占用的字節大小,就轉換成了像素格式。
然后:
width + rowPadding / pixelStride
這個就是一行里像素的占用了,我們將它傳給Bitmap:
創建出合適大小的Bitmap,然后把Image的buffer傳給它,就成功的將Image對象轉換成了Bitmap。
這里我可能講的不清楚,我給大家畫了張圖:

上面的一小格一小格是一塊塊像素。
好了,現在我們已經獲取到了bitmap了,我們可以把它放到ImageView里顯示一下,我寫了一個例子,效果如下:

點擊按鈕,彈出一個對話框請求截屏,點擊立即開始的話,截屏就會顯示在下面的ImageView里。
截屏就這樣,我已經盡力了, 主站蜘蛛池模板: 临沂市| 石台县| 荔浦县| 佛坪县| 紫阳县| 大埔区| 同德县| 崇明县| 唐河县| 格尔木市| 桐庐县| 宁德市| 天柱县| 海城市| 普兰店市| 南康市| 南丰县| 化德县| 禹州市| 洪湖市| 缙云县| 阿尔山市| 明溪县| 秦安县| 凤山县| 津南区| 新民市| 阳山县| 会昌县| 南城县| 缙云县| 张家口市| 钟祥市| 肥东县| 蓝田县| 吴堡县| 龙岩市| 曲水县| 大城县| 通海县| 琼结县|