android 截屏实现

Android 截屏分为四种:View 截屏WebView 截屏系统截屏adb 截屏

1、View 截屏

View 截图是将当前 View 界面截取下来,而对于屏幕上其他信息比如:状态栏或其他应用的界面将无法截取。

1.1 截取除了导航栏之外的屏幕

// 开始截屏
private static byte[] screenshotView() {
  View view = getWindow().getDecorView();
  // view.setDrawingCacheEnabled(true); // 设置缓存,可用于实时截图
  Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
  Canvas canvas = new Canvas(bitmap);
  view.draw(canvas);
  // view.setDrawingCacheEnabled(false); // 清空缓存,可用于实时截图
  byte[] drawByte = getBitmapByte(bitmap); // 位图转为 Byte
  return drawByte;
}

// 位图转 Base64 String
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;
}

// 位图转 Byte
private static byte[] getBitmapByte(Bitmap bitmap){
  ByteArrayOutputStream out = new ByteArrayOutputStream();
  // 参数1转换类型,参数2压缩质量,参数3字节流资源
  bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
  try {
      out.flush();
      out.close();
  } catch (IOException e) {
      e.printStackTrace();
  }
  return out.toByteArray();
}

1.2 截取某个控件或者区域

// 方法1
private static byte[] screenshotView1() {
  View view = title;
  view.setDrawingCacheEnabled(true);
  view.buildDrawingCache();
  Bitmap bitmap = Bitmap.createBitmap(view.getDrawingCache());
  byte[] drawByte = getBitmapByte(bitmap);
  return drawByte;
}

// 方法2
private static byte[] screenshotView2() {
  View view = title;
  Bitmap bitmap = Bitmap.createBitmap(view.getWidth(), view.getHeight(), Bitmap.Config.ARGB_8888);
  // 使用 Canvas,调用自定义 view 控件的 onDraw 方法,绘制图片
  Canvas canvas = new Canvas(bitmap);
  dView.draw(canvas);
  byte[] drawByte = getBitmapByte(bitmap);
  return drawByte;
}

2、WebView 截屏

WebView 截屏有四种方式

2.1 使用 capturePicture() 方法(已废弃)

private static byte[] screenshotWebView() {
  Picture picture = webview.capturePicture();
  //创建位图
  Bitmap bitmap = Bitmap.createBitmap(picture.getWidth(), picture.getHeight(), Bitmap.Config.ARGB_8888);
  Canvas canvas = new Canvas(bitmap);
  // 绘制(会调用 native 方法,完成图形绘制)
  picture.draw(canvas);
  byte[] drawByte = getBitmapByte(bitmap);
  return drawByte;
}

2.2 使用 getScale() 方法(已废弃)

private static byte[] screenshotWebView() {
  float scale = webView.getScale();
  int webViewHeight = (int) (webView.getContentHeight()*scale+0.5);
  Bitmap bitmap = Bitmap.createBitmap(webView.getWidth(),webViewHeight, Bitmap.Config.ARGB_8888);
  Canvas canvas = new Canvas(bitmap);
  webView.draw(canvas);
  byte[] drawByte = getBitmapByte(bitmap);
  return drawByte;
}

2.3 使用 getDrawingCache() 方法

private static byte[] screenshotWebView() {
  Bitmap bitmap = webView.getDrawingCache();
  byte[] drawByte = getBitmapByte(bmp);
  return drawByte;
}

2.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);
  // webView.setDrawingCacheEnabled(false); // 清空缓存
  return drawByte;
}

2.5 截长图配置

if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
  WebView.enableSlowWholeDocumentDraw();
}
setContentView(R.layout.activity_webview);

WebView 截图可能截取不到 cavans 元素,原因是开启了硬件加速,可在 AndroidManifest.xml 中设置属性 android:hardwareAccelerated="false"

关闭硬件加速可能导致页面出现意外情况

3、系统截屏

3.1 MediaProjection

public static final int REQUEST_MEDIA_PROJECTION = 10001;
// 申请截屏权限
private void getScreenShotPower() {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    MediaProjectionManager mProjectionManager = (MediaProjectionManager) getSystemService(Context.MEDIA_PROJECTION_SERVICE);
    if (mProjectionManager != null) {
      startActivityForResult(mProjectionManager.createScreenCaptureIntent(), REQUEST_MEDIA_PROJECTION);
    }
  }
}

private MediaProjection mMediaProjection;
// 回调
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
  super.onActivityResult(requestCode, resultCode, data);
  if (requestCode == REQUEST_MEDIA_PROJECTION && data != null) {
    if (resultCode == RESULT_OK) {
      MediaProjection mediaProjection = mMediaProjectionManager.getMediaProjection(resultCode, data);
      if (mediaProjection == null) {
        Toast.makeText(this,"程序发生错误:MediaProjection@1",Toast.LENGTH_SHORT).show();
        return;
      }
      // mediaProjection 就是当前屏幕流
    } else if (resultCode == RESULT_CANCELED) {
      Log.d(TAG, "用户取消");
    }
  }
}

参考

3.2 Surface(需要 root 权限)

使用前需要:

  • AndroidMenifest.xml 中添加 android:sharedUserId="android.uid.system" 属性
  • 需要给程序添加系统签名
  • 或者 root 系统

使用 Java 反射机制,调用系统 API 截图:

private void screenshotSystem() {
  Class<?> surfaceClass;
  Method method = null;
  Bitmap bitmap = null;
  try {
    if (android.os.Build.VERSION.SDK_INT >= android.os.Build.VERSION_CODES.JELLY_BEAN_MR2) {
        surfaceClass = Class.forName("android.view.SurfaceControl");
    } else {
        surfaceClass = Class.forName("android.view.Surface");
    }
    method = surfaceClass.getDeclaredMethod("screenshot", Integer.TYPE, Integer.TYPE);
    method.setAccessible(true);
    bitmap =  (Bitmap)method.invoke((Object)null, webview.getWidth(), webview.getHeight());
    byte[] drawByte = getBitmapByte(bitmap);
  } catch (ClassNotFoundException e){
    e.printStackTrace();
  } catch (NoSuchMethodException e){
    e.printStackTrace();
  } catch (IllegalAccessException e){
    e.printStackTrace();
  } catch (InvocationTargetException e){
    e.printStackTrace();
  }
}

3.3 PixelCopy

private void screenshotSystem() {
  if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
    PixelCopy.request(getWindow(), bitmap, new PixelCopy.OnPixelCopyFinishedListener() {
        @Override
        public void onPixelCopyFinished(int copyResult){
            if (PixelCopy.SUCCESS == copyResult) {
                byte[] drawByte = getBitmapByte(bitmap);
            } else {
                // onErrorCallback()
            }
        }
    }, new Handler());
  }
}

3.4 framebuffer(需要 root 权限)

String DEVICE_NAME = "/dev/graphics/fb0";
File deviceFile = new File(DEVICE_NAME);
Process localProcess = Runtime.getRuntime().exec("supersu");
String str = "cat " + deviceFile.getAbsolutePath() + "\n";
localProcess.getOutputStream().write(str.getBytes());
return localProcess.getInputStream();

3.5 screencap 命令(需要 root 权限)

private static String getScreenshot(){
  Process process = null;
  String filePath = "mnt/sdcard/" + System.currentTimeMillis() + ".png";
  try {
    process = Runtime.getRuntime().exec("su");
    PrintStream outputStream = null;
    outputStream = new PrintStream(new BufferedOutputStream(process.getOutputStream(), 8192));
    outputStream.println("screencap -p " + filePath);
    outputStream.flush();
    outputStream.close();
    process.waitFor();
  } catch (Exception e) {
    e.printStackTrace();
  } finally {
    if(process != null){
          process.destroy();
    }
  }
  return filePath;
}

4、adb 截屏

$ adb shell
shell@ $ screencap /sdcard/screen.png
shell@ $ exit
$ adb pull /sdcard/screen.png
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 206,839评论 6 482
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 88,543评论 2 382
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 153,116评论 0 344
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 55,371评论 1 279
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 64,384评论 5 374
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 49,111评论 1 285
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 38,416评论 3 400
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,053评论 0 259
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 43,558评论 1 300
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,007评论 2 325
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,117评论 1 334
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 33,756评论 4 324
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 39,324评论 3 307
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,315评论 0 19
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 31,539评论 1 262
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 45,578评论 2 355
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 42,877评论 2 345

推荐阅读更多精彩内容