一、截图原理

Android中每一个页面都是一个Activity,通过Window对象实现页面的显示,每个Window对象实际上都是PhoneWindow的实例,当我们在Activity页面点击屏幕的时候,会触发点击事件,这个事件会一层层分发到处理它的view上,大致会经过这些view:

privatevoidtakeScreenshot(){synchronized(mScreenshotLock){if(mScreenshotConnection !=null){return;}ComponentName cn =newComponentName("com.android.systemui","com.android.systemui.screenshot.TakeScreenshotService");Intent intent =newIntent();
            intent.setComponent(cn);ServiceConnection conn =newServiceConnection(){@OverridepublicvoidonServiceConnected(ComponentName name,IBinder service){synchronized(mScreenshotLock){if(mScreenshotConnection !=this){return;}Messenger messenger =newMessenger(service);Message msg =Message.obtain(null,1);finalServiceConnection myConn =this;Handler h =newHandler(mHandler.getLooper()){@OverridepublicvoidhandleMessage(Message msg){synchronized(mScreenshotLock){if(mScreenshotConnection == myConn){
                                        mContext.unbindService(mScreenshotConnection);
                                        mScreenshotConnection =null;
                                        mHandler.removeCallbacks(mScreenshotTimeout);}}}};
                        msg.replyTo =newMessenger(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){}}}@OverridepublicvoidonServiceDisconnected(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不可见的部分,生成长图,状态栏和导航栏无法截到

funscreenshotView(view: ViewGroup):Bitmap?{var h =0var bitmap:Bitmap?=nullfor(i in0 until view.childCount){
        h += view.getChildAt(i).height
        view.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 in0 until view.childCount){
        view.getChildAt(i).setBackgroundDrawable(null)}return bitmap
}

2. WebView截图

WebView 作为一种特殊的控件,不能像其他系统 View 或者截屏的方式来截图,有特定的Api

// 1.capturePicture方法废弃// 2.getScale方法废弃// 3.getDrawingCache方法privatestaticbyte[]screenshotWebView(){Bitmap bitmap = webView.getDrawingCache();byte[] drawByte =getBitmapByte(bmp);return drawByte;}// 4.draw方法privatestaticbyte[]screenshotWebView(){// webView.setDrawingCacheEnabled(true); 设置缓存Bitmap bitmap =Bitmap.createBitmap(webView.getWidth(), webView.getHeight(),Bitmap.Config.ARGB_8888);Canvas canvas =newCanvas(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
     */publicstaticBitmapcaptureWithStatusBar(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
     */publicstaticBitmapcaptureWithoutStatusBar(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
     */publicstaticintgetScreenHeight(Context context){WindowManager wm =(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);int height = wm.getDefaultDisplay().getHeight();return height;}/**
     * 得到屏幕的宽
     *
     * @param context
     * @return
     */publicstaticintgetScreenWidth(Context context){WindowManager wm =(WindowManager) context.getSystemService(Context.WINDOW_SERVICE);int width = wm.getDefaultDisplay().getWidth();return width;}/**
     * 获取状态栏高度
     *
     * @param context 上下文
     * @return 状态栏高度
     */publicstaticintgetStatusBarHeight(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 转 Base64privatestaticStringgetBitmapString(Bitmap bitmap){String result =null;ByteArrayOutputStream out =null;try{if(bitmap !=null){
      out =newByteArrayOutputStream();
      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 转 Byteprivatestaticbyte[]getBitmapByte(Bitmap bitmap){ByteArrayOutputStream out =newByteArrayOutputStream();// 转换类型,压缩质量,字节流资源
  bitmap.compress(Bitmap.CompressFormat.PNG,100, out);try{
      out.flush();
      out.close();}catch(IOException e){
      e.printStackTrace();}return out.toByteArray();}// Drawable 转 BitmappublicstaticBitmaptoBitmap(Drawable drawable){if(drawable instanceofBitmapDrawable){return((BitmapDrawable) drawable).getBitmap();}elseif(drawable instanceofColorDrawable){//colorBitmap bitmap =Bitmap.createBitmap(32,32,Bitmap.Config.ARGB_8888);Canvas canvas =newCanvas(bitmap);
        canvas.drawColor(((ColorDrawable) drawable).getColor());return bitmap;}elseif(drawable instanceofNinePatchDrawable){//.9.pngBitmap bitmap =Bitmap.createBitmap(drawable.getIntrinsicWidth(),
                drawable.getIntrinsicHeight(),Bitmap.Config.ARGB_8888);Canvas canvas =newCanvas(bitmap);
        drawable.setBounds(0,0, drawable.getIntrinsicWidth(), drawable.getIntrinsicHeight());
        drawable.draw(canvas);return bitmap;}returnnull;}