最近在做android截圖應用的過程遇到很多問題,接觸了好些截圖方法,但是還是不能實現SufaceView截圖功能。今天就把我嘗試過的方法總結下,希望把我慘痛的經歷寫出來后能夠幫助到要做此功能的朋友少走彎路,或者是給一些思路吧。如果哪位大俠能夠做到SurfaceView截圖,還請分享下思路。
一、無SurfaceView情況下的截圖功能實現
public static Bitmap takeScreenShot(Activity act) { if (act == null || act.isFinishing()) { Log.d(TAG, "act參數為空."); return null; } // 獲取當前視圖的view View scrView = act.getWindow().getDecorView(); scrView.setDrawingCacheEnabled(true); scrView.buildDrawingCache(true); // 獲取狀態欄高度 Rect statuBarRect = new Rect(); scrView.getWindowVisibleDisplayFrame(statuBarRect); int statusBarHeight = statuBarRect.top; int width = act.getWindowManager().getDefaultDisplay().getWidth(); int height = act.getWindowManager().getDefaultDisplay().getHeight(); Bitmap scrBmp = null; try { // 去掉標題欄的截圖 scrBmp = Bitmap.createBitmap( scrView.getDrawingCache(), 0, statusBarHeight, width, height - statusBarHeight); } catch (IllegalArgumentException e) { Log.d("", "#### 旋轉屏幕導致去掉狀態欄失敗"); } scrView.setDrawingCacheEnabled(false); scrView.destroyDrawingCache(); return scrBmp; }
這種情況只能夠截取普通應用的截圖,當應用含有SurfaceView時, SurfaceView區域為黑色,具體什么原因,請移步:http://blog.csdn.net/luoshengyang/article/details/8661317。
二、含有SurfaceView的截圖實現
2.1 系統root的情況下的實現代碼
private static final String DEVICE_NAME = "/dev/graphics/fb0"; @SuppressWarnings("deprecation") public static Bitmap acquireScreenshot(Context context) { WindowManager mWinManager = (WindowManager) context .getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); Display display = mWinManager.getDefaultDisplay(); display.getMetrics(metrics); // 屏幕高 int height = metrics.heightPixels; // 屏幕的寬 int width = metrics.widthPixels; int pixelformat = display.getPixelFormat(); PixelFormat localPixelFormat1 = new PixelFormat(); PixelFormat.getPixelFormatInfo(pixelformat, localPixelFormat1); // 位深 int deepth = localPixelFormat1.bytesPerPixel; byte[] arrayOfByte = new byte[height * width * deepth]; try { // 讀取設備緩存,獲取屏幕圖像流 InputStream localInputStream = readAsRoot(); DataInputStream localDataInputStream = new DataInputStream( localInputStream); localDataInputStream.readFully(arrayOfByte); localInputStream.close(); int[] tmpColor = new int[width * height]; int r, g, b; for (int j = 0; j < width * height * deepth; j += deepth) { b = arrayOfByte[j] & 0xff; g = arrayOfByte[j + 1] & 0xff; r = arrayOfByte[j + 2] & 0xff; tmpColor[j / deepth] = (r << 16) | (g << 8) | b | (0xff000000); } // 構建bitmap Bitmap scrBitmap = Bitmap.createBitmap(tmpColor, width, height, Bitmap.Config.ARGB_8888); return scrBitmap; } catch (Exception e) { Log.d(TAG, "#### 讀取屏幕截圖失敗"); e.printStackTrace(); } return null; } /** * @Title: readAsRoot * @Description: 以root權限讀取屏幕截圖 * @throws Exception * @throws */ public static InputStream readAsRoot() throws Exception { File deviceFile = new File(DEVICE_NAME); Process localProcess = Runtime.getRuntime().exec("su"); String str = "cat " + deviceFile.getAbsolutePath() + "\n"; localProcess.getOutputStream().write(str.getBytes()); return localProcess.getInputStream(); }
系統root的時候,可以直接讀取frame buffer, 因此即使含有SurfaceView也能夠讀取整個窗口的圖像。
2.2 使用SurfaceView和MediaPlayer來播放視頻的情況,這里只獲取SurfaceView的圖像
/** * @Title: getVideoFrame * @Description: 獲取視頻某幀的圖像,但得到的圖像並不一定是指定position的圖像。 * @param path 視頻的本地路徑 * @param position 視頻流播放的position * @return Bitmap 返回的視頻圖像 * @throws */ @SuppressLint("NewApi") public static Bitmap getVideoFrame(String path, MediaPlayer mediaPlayer) { Bitmap bmp = null; // android 9及其以上版本可以使用該方法 MediaMetadataRetriever retriever = new MediaMetadataRetriever(); try { retriever.setDataSource(path); // 這一句是必須的 String timeString = retriever.extractMetadata(MediaMetadataRetriever.METADATA_KEY_DURATION); // 獲取總長度,這一句也是必須的 long titalTime = Long.parseLong(timeString) * 1000; long videoPosition = 0; try { mediaPlayer.setDataSource(path); if (path.startsWith("http")) { mediaPlayer.prepareAsync(); } else { mediaPlayer.prepare(); } int duration = mediaPlayer.getDuration(); // 通過這個計算出想截取的畫面所在的時間 videoPosition = titalTime * position / duration; } catch (IOException e) { e.printStackTrace(); } if (videoPosition > 0) { bmp = retriever.getFrameAtTime(videoPosition, MediaMetadataRetriever.OPTION_CLOSEST); } } catch (IllegalArgumentException ex) { ex.printStackTrace(); } catch (RuntimeException ex) { ex.printStackTrace(); } finally { try { retriever.release(); } catch (RuntimeException ex) { } } return bmp; }
以上代碼單純的獲取 SurfaceView上的圖像,其他控件的圖像並沒有獲取。其原理是讀取本地文件某一position的圖像,而不是截圖。
2.3 沒有root,又有SurfaceView,還想截圖 ?
這個方法我並沒有成功,也不知道是否真的能夠實現,這里給出相關資料,希望有需要的朋友能夠得到幫助,如果能夠實現,還請留個言指教一下,不甚感激。
《Pro android c++ with NDK》的第十二張的Using Graphic API中的Using Native Window API.
結尾:
在經過很多探索后,請教了國內《深入Android系統》系列作者鄧凡平,他給出的答案為:
我知道你說的。我也做過截屏的軟件,但是視頻圖像截不了。
就我之前的研究來看,視頻數據解碼后直接由解碼芯片推到屏幕上了,根本就不會
通過surfaceflinger來混屏,在高通芯片上就是如此。
你想想吧,windows和Linux的截屏軟件都無法截取視頻播放的圖像,正是這個原因。
不過有些芯片平台也許會將視頻數據通過surfaceflinger來混屏,我個人覺得這種情況比較少。
Br
鄧凡平
總之,這條路似乎很難走通,希望有有需求的朋友少走彎路吧。