目录
- 一、截图原理
- 二、实现方式
- 1. View截图
- 2. WebView截图
- 3. 屏幕截图
- 三、格式转换方法
一、截图原理
我们的手机一般同时按下音量-键和电源键就会将当前屏幕显示的内容截取下来,那里面具体经过哪些流程呢?
Android中每一个页面都是一个Activity,通过Window对象实现页面的显示,每个Window对象实际上都是PhoneWindow的实例,当我们在Activity页面点击屏幕的时候,会触发点击事件,这个事件会一层层分发到处理它的view上,大致会经过这些view:
先会调PhoneWindowManager中的dispatchUnhandledKey方法,一层层往下,这里不详细展开,我们往下找会发现最终会调用一个takeScreenshot截屏的方法:
private void takeScreenshot() {synchronized (mScreenshotLock) {if (mScreenshotConnection != null) {return;}ComponentName cn = new ComponentName("com.android.systemui","com.android.systemui.screenshot.TakeScreenshotService");Intent intent = new Intent();intent.setComponent(cn);ServiceConnection conn = new ServiceConnection() {@Overridepublic void onServiceConnected(ComponentName name, IBinder service) {synchronized (mScreenshotLock) {if (mScreenshotConnection != this) {return;}Messenger messenger = new Messenger(service);Message msg = Message.obtain(null, 1);final ServiceConnection myConn = this;Handler h = new Handler(mHandler.getLooper()) {@Overridepublic void handleMessage(Message msg) {synchronized (mScreenshotLock) {if (mScreenshotConnection == myConn) {mContext.unbindService(mScreenshotConnection);mScreenshotConnection = null;mHandler.removeCallbacks(mScreenshotTimeout);}}}};msg.replyTo = new Messenger(h);msg.arg1 = msg.arg2 = 0;if (mStatusBar != null && mStatusBar.isVisibleLw())msg.arg1 = 1;if (mNavigationBar != null && mNavigationBar.isVisibleLw())msg.arg2 = 1;try {messenger.send(msg);} catch (RemoteException e) {}}}@Overridepublic void onServiceDisconnected(ComponentName name) {}};if (mContext.bindServiceAsUser(intent, conn, Context.BIND_AUTO_CREATE, UserHandle.CURRENT)) {mScreenshotConnection = conn;mHandler.postDelayed(mScreenshotTimeout, 10000);}}}
这里通过反射机制调用了TakeScreenshotService的bindServiceAsUser方法,创建TakeScreenshotService服务,再通过其内部的SurfaceControl.screenshot 生成 bitmap,生成图片成功会给系统发送通知。
系统截图的大致流程就是这样,在里面截图原理大致就是:获取需要截屏的区域的宽高,创建一个画布,然后区域内的内容绘制在画布上,最后生成bitmap图片。
二、实现方式
Android 截图主要为四种:View 截图、WebView 截图、屏幕截图、系统截图和 adb 截图。后两种截图不常用,不详细展开。
1. View截图
可以截取到View不可见的部分,生成长图,状态栏和导航栏无法截到
fun screenshotView(view: ViewGroup):Bitmap?{var h = 0var bitmap:Bitmap?=nullfor(i in 0 until view.childCount){h += view.getChildAt(i).heightview.getChildAt(i).setBackgroundColor(Color.parseColor("#6CC287"))}bitmap = Bitmap.createBitmap(view.width, h, Bitmap.Config.RGB_565)val canvas = Canvas(bitmap)view.draw(canvas)//重新赋色for(i in 0 until view.childCount){view.getChildAt(i).setBackgroundDrawable(null)}return bitmap
}
2. WebView截图
WebView 作为一种特殊的控件,不能像其他系统 View 或者截屏的方式来截图,有特定的Api
// 1.capturePicture方法废弃
// 2.getScale方法废弃// 3.getDrawingCache方法
private static byte[] screenshotWebView() {Bitmap bitmap = webView.getDrawingCache();byte[] drawByte = getBitmapByte(bmp);return drawByte;
}// 4.draw方法
private static byte[] screenshotWebView() {// webView.setDrawingCacheEnabled(true); 设置缓存Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(), webView.getHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);webView.draw(canvas);webView.destroyDrawingCache();byte[] drawByte = getBitmapByte(bitmap);return drawByte;
}
可能会截取不到 cavans 元素,原因是开启了硬件加速(关闭硬件加速可能导致页面异常),可在 AndroidManifest.xml 中设置:
android:hardwareAccelerated="false"
截长图的话需要配置:
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {WebView.enableSlowWholeDocumentDraw();
}
setContentView(R.layout.webview);
3. 屏幕截图
截取应用当前屏幕的图片:
/*** 获取当前屏幕截图,包含状态栏** @param activity activity* @return Bitmap*/public static Bitmap captureWithStatusBar(Activity activity) {View view = activity.getWindow().getDecorView();view.setDrawingCacheEnabled(true);view.buildDrawingCache();Bitmap bmp = view.getDrawingCache();int width = getScreenWidth(activity);int height = getScreenHeight(activity);Bitmap ret = Bitmap.createBitmap(bmp, 0, 0, width, height);view.destroyDrawingCache();return ret;}/*** 获取当前屏幕截图,不包含状态栏** @param activity activity* @return Bitmap*/public static Bitmap captureWithoutStatusBar(Activity activity) {View view = activity.getWindow().getDecorView();view.setDrawingCacheEnabled(true);view.buildDrawingCache();Bitmap bmp = view.getDrawingCache();int statusBarHeight = getStatusBarHeight(activity);int width = getScreenWidth(activity);int height = getScreenHeight(activity);Bitmap ret = Bitmap.createBitmap(bmp, 0, statusBarHeight, width, height - statusBarHeight);view.destroyDrawingCache();return ret;}/*** 得到屏幕的高** @param context* @return*/public static int getScreenHeight(Context context) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);int height = wm.getDefaultDisplay().getHeight();return height;}/*** 得到屏幕的宽** @param context* @return*/public static int getScreenWidth(Context context) {WindowManager wm = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);int width = wm.getDefaultDisplay().getWidth();return width;}/*** 获取状态栏高度** @param context 上下文* @return 状态栏高度*/public static int getStatusBarHeight(Context context) {int result = 0;int resourceId = context.getResources().getIdentifier("status_bar_height", "dimen", "android");if (resourceId > 0) {result = context.getResources().getDimensionPixelSize(resourceId);}return result;}
三、格式转换方法
下面列出了一些常用的转换方法:
// Bitmap 转 Base64
private static String getBitmapString(Bitmap bitmap) {String result = null;ByteArrayOutputStream out = null;try {if (bitmap != null) {out = new ByteArrayOutputStream();bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
out.flush();out.close();
byte[] bitmapBytes = out.toByteArray();result = Base64.encodeToString(bitmapBytes, Base64.DEFAULT);}} catch (IOException e) {e.printStackTrace();} finally {try {if (out != null) {out.flush();out.close();}} catch (IOException e) {e.printStackTrace();}}return result;
}
// Bitmap 转 Byte
private static byte[] getBitmapByte(Bitmap bitmap){ByteArrayOutputStream out = new ByteArrayOutputStream();// 转换类型,压缩质量,字节流资源bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);try {out.flush();out.close();} catch (IOException e) {e.printStackTrace();}return out.toByteArray();
}// Drawable 转 Bitmap
public static Bitmap toBitmap(Drawable drawable) {if (drawable instanceof BitmapDrawable) {return ((BitmapDrawable) drawable).getBitmap();} else if (drawable instanceof ColorDrawable) {//colorBitmap bitmap = Bitmap.createBitmap(32, 32, Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);canvas.drawColor(((ColorDrawable) drawable).getColor());return bitmap;} else if (drawable instanceof NinePatchDrawable) {//.9.pngBitmap bitmap = Bitmap.createBitmap(drawable.getIntrinsicWidth(),drawable.getIntrinsicHeight(), Bitmap.Config.ARGB_8888);Canvas canvas = new Canvas(bitmap);drawable.setBounds(0, 0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());drawable.draw(canvas);return bitmap;}return null;
}