最近在做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邓凡平
总之,这条路似乎很难走通,希望有有需求的朋友少走弯路吧。