一、起始原因
最近项目需求中需要实现屏幕截图,开启了新一轮的翻腾,找寻。是的,我就是一个搬运工,简单的搬运工~~做不完的功能,连接不断地需求~~
基本需求:实现当前页面截图并保存;
扩展需求:截图去除自己添加的控件;
完善需求:截图响应速度要快;
反馈完善需求:适配所有机型。
二、具体实现方式
1),第一种实现方式
/** * 对View进行量测,布局后截图 * * @param view * @return */ public Bitmap convertViewToBitmap(View view) { view.measure(View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED), View.MeasureSpec.makeMeasureSpec(0, View.MeasureSpec.UNSPECIFIED)); view.layout(0, 0, view.getMeasuredWidth(), view.getMeasuredHeight()); view.setDrawingCacheEnabled(true); view.buildDrawingCache(); Bitmap bitmap = view.getDrawingCache(); return bitmap; }
此种实现方式,基本可以实现所有View的截图及全屏幕的截图。
在实际的需求中,是对WebView进行截图,且WebView的展示中,是使用绘制程序绘制部分内容。这种方式,绘制的内容截屏展示不出来,只能另寻他法。
2)第二种实现方式 /** * 获取整个窗口的截图 * * @param context * @return */ @SuppressLint("NewApi") private Bitmap captureScreen(Activity context) { View cv = context.getWindow().getDecorView(); cv.setDrawingCacheEnabled(true); cv.buildDrawingCache(); Bitmap bmp = cv.getDrawingCache(); if (bmp == null) { return null; } bmp.setHasAlpha(false); bmp.prepareToDraw(); return bmp; }
3),第三种实现方式【实质是将view作为原图绘制出来】
/** * 对单独某个View进行截图 * * @param v * @return */ private Bitmap loadBitmapFromView(View v) { if (v == null) { return null; } Bitmap screenshot; screenshot = Bitmap.createBitmap(v.getWidth(), v.getHeight(), Bitmap.Config.RGB_565); Canvas c = new Canvas(screenshot); c.translate(-v.getScrollX(), -v.getScrollY()); v.draw(c); return screenshot; }
4),第四种实现方式【针对WebView的实现方式】
/** * 对WebView进行截图 * * @param webView * @return */ public static Bitmap captureWebView1(WebView webView) {//可执行 Picture snapShot = webView.capturePicture(); Bitmap bmp = Bitmap.createBitmap(snapShot.getWidth(), snapShot.getHeight(), Bitmap.Config.ARGB_8888); Canvas canvas = new Canvas(bmp); snapShot.draw(canvas); return bmp; } 5),第五种实现方式
此种实现方式相对较复杂,且很多方法不支持版本较低的Android系统版本。但能够很好地处理WebView绘制内容截图不成功的问题。
在实际的执行中,通过版本和Android内置系统的判断,多种截图方法综合使用,能够实现产品的需求。
只是后来,因为产品业务调整,这一块功能隐藏不使用了~~让我哭会~~~不管怎么样,也算是学东西了,只是后面的路,需要走的更稳妥,更踏实一些~~~
//定义使用变量
/** * 截屏相关 */ private MediaProjectionManager mediaProjectionManager; private MediaProjection mMediaProjection; private VirtualDisplay mVirtualDisplay; private static Intent mResultData = null; private ImageReader mImageReader; private WindowManager mWindowManager; private WindowManager.LayoutParams mLayoutParams; private GestureDetector mGestureDetector; private ImageView mFloatView; private int mScreenWidth; private int mScreenHeight; private int mScreenDensity; private String mPhoneType; public static final int REQUEST_MEDIA_PROJECTION = 18;//onCreate()初始化变量
/** * 初始化变量 */ mediaProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); startActivityForResult(mediaProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION); mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE); DisplayMetrics metrics = new DisplayMetrics(); mWindowManager.getDefaultDisplay().getMetrics(metrics); mScreenDensity = metrics.densityDpi; mScreenWidth = metrics.widthPixels; mScreenHeight = metrics.heightPixels; mImageReader = ImageReader.newInstance(mScreenWidth, mScreenHeight, PixelFormat.RGBA_8888, 1);//截图方法体的实现
@Override protected void onActivityResult(int requestCode, int resultCode, Intent data) { super.onActivityResult(requestCode, resultCode, data); switch (requestCode) { case REQUEST_MEDIA_PROJECTION: if (resultCode == RESULT_OK && data != null) { mResultData = data; //startService(new Intent(getApplicationContext(), FloatWindowsService.class)); } break; } } @Override protected void onDestroy() { super.onDestroy(); try { stopVirtual(); tearDownMediaProjection(); } catch (Exception e) { e.printStackTrace(); } } private void startScreenShot() { Handler handler1 = new Handler(); handler1.postDelayed(new Runnable() { public void run() { // start virtual startVirtual(); } }, 5); handler1.postDelayed(new Runnable() { public void run() { // capture the screen startCapture(); } }, 30); } public void startVirtual() { if (mMediaProjection != null) { virtualDisplay(); } else { setUpMediaProjection(); virtualDisplay(); } } private void stopVirtual() { if (mVirtualDisplay == null) { return; } mVirtualDisplay.release(); mVirtualDisplay = null; } private void startCapture() { Image image = mImageReader.acquireLatestImage(); if (image == null) { startScreenShot(); } else { SaveTask mSaveTask = new SaveTask(); // mSaveTask.execute(image); if (Build.VERSION.SDK_INT >= 11) { // From API 11 onwards, we need to manually select the // THREAD_POOL_EXECUTOR AsyncTaskCompatHoneycomb.executeParallel(mSaveTask, image); } else { // Before API 11, all tasks were run in parallel mSaveTask.execute(image); } // AsyncTaskCompat.executeParallel(mSaveTask, image); } } static class AsyncTaskCompatHoneycomb { static <Params, Progress, Result> void executeParallel(AsyncTask<Params, Progress, Result> task, Params... params) { // 这里显示调用了THREAD_POOL_EXECUTOR,所以就可以使用该线程池了 task.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, params); } } @SuppressLint("NewApi") private void virtualDisplay() { Surface sf = mImageReader.getSurface(); mVirtualDisplay = mMediaProjection.createVirtualDisplay( "screen-mirror", mScreenWidth, mScreenHeight, mScreenDensity, DisplayManager.VIRTUAL_DISPLAY_FLAG_AUTO_MIRROR, mImageReader.getSurface(), null, null); } public void setUpMediaProjection() { if (mResultData == null) { Intent intent = new Intent(Intent.ACTION_MAIN); intent.addCategory(Intent.CATEGORY_LAUNCHER); startActivity(intent); } else { mMediaProjection = getMediaProjectionManager().getMediaProjection( Activity.RESULT_OK, mResultData); } } private MediaProjectionManager getMediaProjectionManager() { return (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE); } public class SaveTask extends AsyncTask<Image, Void, Bitmap> { @Override protected Bitmap doInBackground(Image... params) { if (params == null || params.length < 1 || params[0] == null) { return null; } Image image = params[0]; 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); image.close(); // File file = new File(SAVE_REAL_PATH); // if (bitmap != null) { // try { // if (!file.exists()) { // file.mkdirs(); // } // SimpleDateFormat sdf = new SimpleDateFormat("yyyy-MM-dd_HH-mm-ss", Locale.US); // String fileImage = file.getAbsolutePath() + "/" + sdf.format(new Date()) + ".jpg"; // FileOutputStream out = new FileOutputStream(fileImage); // if (out != null) { // bitmap.compress(Bitmap.CompressFormat.PNG, 100, out); // out.flush(); // out.close(); // Intent media = new Intent( // Intent.ACTION_MEDIA_SCANNER_SCAN_FILE); // Uri contentUri = Uri.fromFile(file); // media.setData(contentUri); // sendBroadcast(media); // fileDestUri = fileImage; // } // } catch (FileNotFoundException e) { // e.printStackTrace(); // file = null; // } catch (IOException e) { // e.printStackTrace(); // file = null; // } // } // if (file != null) { return bitmap; // } // return null; } @Override protected void onPostExecute(Bitmap bitmap) { super.onPostExecute(bitmap); // 预览图片 if (bitmap != null) { //也可以处理保存图片逻辑 captureIV.setImageBitmap(bitmap); } } } private void tearDownMediaProjection() { if (mMediaProjection != null) { mMediaProjection.stop(); mMediaProjection = null; } } //使用
startScreenShot(); 好吧,展示效果,兄弟们知道大概什么情形~~
三、事后总结
除去上面展示的形式,还测试了其他几种方式,并没有获取到想要的结果。要是兄弟们知道怎么解决,可以一起沟通,一起交流哈~~
1),第一种尝试
/** * 应用反射的方法 * 【反射不成功】 * * @return */ private Bitmap getBitmapReverse() { Bitmap mScreenBitmap = null; DisplayMetrics mDisplayMetrics = new DisplayMetrics(); float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels}; Class<?> demo = null; try { demo = Class.forName("android.view.Surface"); } catch (Exception e) { e.printStackTrace(); } try { Method method = demo.getMethod("screenshot", new Class[]{int.class, int.class}); mScreenBitmap = (Bitmap) method.invoke(demo.newInstance(), (int) dims[0], (int) dims[1]); //这里其实可以直接用null替换demo.newInstance(),因为screenshot是静态方法,所以第一个invoke的第一个参数会被自动忽略~所以其实你填什么都没关系。 //获取的返回值是个bitmap,然后我们就可以为所欲为了~ } catch (Exception e) { e.printStackTrace(); } return mScreenBitmap; }应用反射的方法,反射Surface成功,但是反射screenshot()方法不成功,没能够实现截图;
2),第二种尝试
/** * 需要root权限 * * @param activity * @return */ public Bitmap captureScreenSystem(Activity activity) { // 获取屏幕大小: DisplayMetrics metrics = new DisplayMetrics(); WindowManager WM = (WindowManager) activity .getSystemService(Context.WINDOW_SERVICE); Display display = WM.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[] piex = new byte[height * width * deepth]; try { Runtime.getRuntime().exec( new String[]{"/system/bin/su", "-c", "chmod 777 /dev/graphics/fb0"}); } catch (IOException e) { e.printStackTrace(); } try { // 获取fb0数据输入流 InputStream stream = new FileInputStream(new File( "/dev/graphics/fb0")); DataInputStream dStream = new DataInputStream(stream); dStream.readFully(piex); } catch (Exception e) { e.printStackTrace(); } // 保存图片 int[] colors = new int[height * width]; for (int m = 0; m < colors.length; m++) { int r = (piex[m * 4] & 0xFF); int g = (piex[m * 4 + 1] & 0xFF); int b = (piex[m * 4 + 2] & 0xFF); int a = (piex[m * 4 + 3] & 0xFF); colors[m] = (a << 24) + (r << 16) + (g << 8) + b; } // piex生成Bitmap Bitmap bitmap = Bitmap.createBitmap(colors, width, height, Bitmap.Config.ARGB_8888); return bitmap; } 需要root权限,客户当然不给了~~~~
反思一:
在实现图片的保存中,保存时耗时操作,为更好地用户体验,最好保存图片在子线程进行。
以下是压缩生成图片的方法:
FileOutputStream out = null; try { out = new FileOutputStream(fname); } catch (FileNotFoundException e) { e.printStackTrace(); } bitmap1.compress(Bitmap.CompressFormat.JPEG, 20, out);选择图片的格式,压缩图片质量,都会影响生成图片的快慢。在实际中,项目保存图片应用20的质量,大家依据自己的需求来做变更~~~
反思二:
产品决定了上层了流程,会影响后续的很多环节。产品一旦修改,研发,测试都得跟着变更~时间延期,工作重复,都是影响团队决战胜利的因素。
当然了,作为研发,实现产品的需求是我们的天职。别给产品讨论需求哦~~
哈哈哈,若是倾权,立删~~~
研发也是一个小人物,调侃调侃生活~~~做好自己该做的,把自己能做的做得更好,愿天下和平~~~~
欢迎小伙伴共同提高进步~~~~
异次元传送门 ---- 哈哈,我就是一个Demo而已~_~
分离也许是为了更好的团聚,闯荡也许是为了更好的生活……又有谁想背井离乡孤独一人拼搏了,又有谁不想花前月下团团圆圆啦……生活是现实的,人都活在现实生活之中,每一个人,每一个家庭都有各自的活法及生活方式,谁不希望美好的生活,美好的生活是要努力打拼奋斗获得的……
趁还有时间,给自已一点不一样~~~~