1. 1,基于Android SDK的截屏方法
  1. (1)主要就是利用SDK提供的View.getDrawingCache()方法。网上已经有很多的实例了。首先创建一个android project,然后进行Layout,画一个按键(res/layout/main.xml):
<?xmlversion="1.0"encoding="utf-8"?>
<LinearLayoutxmlns:android="http://schemas.android.com/apk/res/android"
    android:orientation="vertical"
    android:layout_width="fill_parent"
    android:layout_height="fill_parent"
    >
<TextView
    android:layout_width="fill_parent"
    android:layout_height="wrap_content"
    android:text="@string/hello"
    />
<Button
  android:text="NiceButton"
  android:id="@+id/my_button"
  android:layout_width="fill_parent"
  android:layout_height="wrap_content"
  android:layout_alignParentBottom="true"></Button>
</LinearLayout>
  1. HelloAndroid.java实现代码为:
packagecom.example.helloandroid;
 
importjava.io.FileOutputStream;
importjava.text.SimpleDateFormat;
importjava.util.Date;
importjava.util.Locale;
 
importandroid.app.Activity;
importandroid.graphics.Bitmap;
importandroid.os.Bundle;
importandroid.view.View;
importandroid.view.View.OnClickListener;
importandroid.widget.Button;
 
publicclassHelloAndroidextendsActivity {
 
  privateButton button;
 
  /** Called when the activity is first created. */
  @Override
  publicvoidonCreate(Bundle savedInstanceState) {
 
    super.onCreate(savedInstanceState);
    this.setContentView(R.layout.main);
    this.button = (Button) this.findViewById(R.id.my_button);
    this.button.setOnClickListener(newOnClickListener() {
 
      publicvoidonClick(View v) {
        SimpleDateFormat sdf = newSimpleDateFormat(
            "yyyy-MM-dd_HH-mm-ss", Locale.US);
        String fname = "/sdcard/"+ sdf.format(newDate()) + ".png";
        View view = v.getRootView();
        view.setDrawingCacheEnabled(true);
        view.buildDrawingCache();
        Bitmap bitmap = view.getDrawingCache();
        if(bitmap != null) {
          System.out.println("bitmap got!");
          try{
            FileOutputStream out = newFileOutputStream(fname);
            bitmap.compress(Bitmap.CompressFormat.PNG,100, out);
            System.out.println("file " + fname + "output done.");
          }catch(Exception e) {
            e.printStackTrace();
          }
        }else{
          System.out.println("bitmap is NULL!");
        }
      }
 
    });
 
  }
}
  1. 这个代码会在按下app中按键的时候自动在手机的/sdcard/目录下生成一个时间戳命名的png截屏文件。
  1. 这种截屏有一个问题,就是只能截到一部分,比如电池指示部分就截不出来了。
  1. (2)在APK中调用“adb shell screencap -pfilepath 命令
 
该命令读取系统的framebuffer,需要获得系统权限:
(1). 在AndroidManifest.xml文件中添加
<uses-permissionandroid:name="android.permission.READ_FRAME_BUFFER"/>
 
(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
 LOCAL_CERTIFICATE := platform
    1. publicvoid takeScreenShot(){
    1.    String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ;
    1. try {                    
    1.           Runtime. getRuntime().exec("screencap -p " + mSavedPath);
    1.    } catch (Exception e) {
    1.           e.printStackTrace();
    1.    }

(3).利用系统的API,实现Screenshot,这部分代码是系统隐藏的,需要在源码下编译,

    1).修改Android.mk, 添加系统权限
          LOCAL_CERTIFICATE := platform
         2).修改AndroidManifest.xml 文件,添加
  1. 权限
<uses-permissionandroid:name="android.permission.READ_FRAME_BUFFER"/>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
      public boolean takeScreenShot(String imagePath){
                     
                    
                     
             if(imagePath.equals("" )){
                      imagePath = Environment.getExternalStorageDirectory()+File. separator+"Screenshot.png" ;
             }
                     
          Bitmap mScreenBitmap;
          WindowManager mWindowManager;
          DisplayMetrics mDisplayMetrics;
          Display mDisplay;
                  
          mWindowManager = (WindowManager) mcontext.getSystemService(Context.WINDOW_SERVICE);
          mDisplay = mWindowManager.getDefaultDisplay();
          mDisplayMetrics = new DisplayMetrics();
          mDisplay.getRealMetrics(mDisplayMetrics);
                                 
          float[] dims = {mDisplayMetrics.widthPixels , mDisplayMetrics.heightPixels };
          mScreenBitmap = Surface. screenshot((int) dims[0], ( int) dims[1]);
                     
          if (mScreenBitmap == null) {  
                 return false ;
          }
                  
       try {
          FileOutputStream out = new FileOutputStream(imagePath);
          mScreenBitmap.compress(Bitmap.CompressFormat. PNG, 100, out);
             
        catch (Exception e) {
                
                
          return false ;
        }       
                            
       return true ;
}
 
  1.  
  1. 2 基于Android ddmlib进行截屏
  1. public class ScreenShot {
  2. private BufferedImage image = null;
  3. /**
  4. * @param args
  5. */
  6. public static void main(String[] args) {
  7. // TODO Auto-generated method stub
  8. AndroidDebugBridge.init(false); //
  9. ScreenShot screenshot = new ScreenShot();
  10. IDevice device = screenshot.getDevice();
  11. for (int i = 0; i < 10; i++) {
  12. Date date=new Date();
  13. SimpleDateFormat df=new SimpleDateFormat("MM-dd-HH-mm-ss");
  14. String nowTime = df.format(date);
  15. screenshot.getScreenShot(device, "Robotium" + nowTime);
  16. try {
  17. Thread.sleep(1000);
  18. } catch (InterruptedException e) {
  19. // TODO Auto-generated catch block
  20. e.printStackTrace();
  21. }
  22. }
  23. }
  24. public void getScreenShot(IDevice device,String filename) {
  25. RawImage rawScreen = null;
  26. try {
  27. rawScreen = device.getScreenshot();
  28. } catch (TimeoutException e) {
  29. // TODO Auto-generated catch block
  30. e.printStackTrace();
  31. } catch (AdbCommandRejectedException e) {
  32. // TODO Auto-generated catch block
  33. e.printStackTrace();
  34. } catch (IOException e) {
  35. // TODO Auto-generated catch block
  36. e.printStackTrace();
  37. }
  38. if (rawScreen != null) {
  39. Boolean landscape = false;
  40. int width2 = landscape ? rawScreen.height : rawScreen.width;
  41. int height2 = landscape ? rawScreen.width : rawScreen.height;
  42. if (image == null) {
  43. image = new BufferedImage(width2, height2,
  44. BufferedImage.TYPE_INT_RGB);
  45. } else {
  46. if (image.getHeight() != height2 || image.getWidth() != width2) {
  47. image = new BufferedImage(width2, height2,
  48. BufferedImage.TYPE_INT_RGB);
  49. }
  50. }
  51. int index = 0;
  52. int indexInc = rawScreen.bpp >> 3;
  53. for (int y = 0; y < rawScreen.height; y++) {
  54. for (int x = 0; x < rawScreen.width; x++, index += indexInc) {
  55. int value = rawScreen.getARGB(index);
  56. if (landscape)
  57. image.setRGB(y, rawScreen.width - x - 1, value);
  58. else
  59. image.setRGB(x, y, value);
  60. }
  61. }
  62. try {
  63. ImageIO.write((RenderedImage) image, "PNG", new File("D:/"
  64. + filename + ".jpg"));
  65. } catch (IOException e) {
  66. // TODO Auto-generated catch block
  67. e.printStackTrace();
  68. }
  69. }
  70. }
  71. /**
  72. * 获取得到device对象
  73. * @return
  74. */
  75. private IDevice getDevice(){
  76. IDevice device;
  77. AndroidDebugBridge bridge = AndroidDebugBridge
  78. .createBridge("adb", true);//如果代码有问题请查看API,修改此处的参数值试一下
  79. waitDevicesList(bridge);
  80. IDevice devices[] = bridge.getDevices();
  81. device = devices[0];
  82. return device;
  83. }
  84. /**
  85. * 等待查找device
  86. * @param bridge
  87. */
  88. private void waitDevicesList(AndroidDebugBridge bridge) {
  89. int count = 0;
  90. while (bridge.hasInitialDeviceList() == false) {
  91. try {
  92. Thread.sleep(500);
  93. count++;
  94. } catch (InterruptedException e) {
  95. }
  96. if (count > 240) {
  97. System.err.print("等待获取设备超时");
  98. break;
  99. }
  100. }
  101. }
  1. 3 Android本地编程(Native Programming)读取framebuffer
  1.  
  1.  
  1. (1)命令行,框架的截屏功能是通过framebuffer来实现的,所以我们先来介绍一下framebuffer
  1. framebuffer介绍
    帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行 读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
    Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。
    帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
  1. Android截屏实现思路
    Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。
    现在我们的测试代码运行时候是通过RC(remote controller)方式来运行被测应用的,那就需要在PC机上来访问模拟器或者真机上的framebuffer数据,这个的话可以通过androidADB命令来实现。
  1. 具体实现
  2.  
  3. /***********************************************************************
      *
      *   ScreenShot.java
      ***********************************************************************/
    import java.awt.image.BufferedImage;
    import java.io.ByteArrayOutputStream;
    import java.io.DataInput;
    import java.io.EOFException;
    import java.io.File;
    import java.io.FileInputStream;
    import java.io.IOException;
    import java.io.InputStream;
    import javax.imageio.ImageIO;
    import org.openqa.selenium.OutputType;
    import org.openqa.selenium.internal.Base64Encoder;
    import com.google.common.io.Closeables;
    import com.google.common.io.LittleEndianDataInputStream;
  4.  
  5. /**
     */
    public class ScreenShot {
  6.  
  7.     /**
         * @param args
         * @throws InterruptedException 
         */
        public static void main(String[] args) throws InterruptedException {    
            try {
                //分辨率大小,后续可以通过代码来获取到当前的分辨率
                int xResolution = 320;
                int yResolution = 480;
                //执行adb命令,把framebuffer中内容保存到fb1文件中
                 Runtime.getRuntime().exec("adb pull /dev/graphics/fb0 C:/fb1");
                 //等待几秒保证framebuffer中的数据都被保存下来,如果没有保存完成进行读取操作会有IO异常
                 Thread.sleep(15000);
                 //读取文件中的数据
                 InputStream in = (InputStream)new FileInputStream("C:/fb1");
                 DataInput frameBuffer = new LittleEndianDataInputStream(in);
                 
                 BufferedImage screenImage = new BufferedImage(
                         xResolution, yResolution, BufferedImage.TYPE_INT_ARGB);
                     int[] oneLine = new int[xResolution];
                    for (int y = 0; y < yResolution; y++) {
                        //从frameBuffer中计算出rgb值
                        convertToRgba32(frameBuffer, oneLine);
                        //把rgb值设置到image对象中
                        screenImage.setRGB(0, y, xResolution, 1, oneLine, 0, xResolution);
                    }
                    Closeables.closeQuietly(in);
                    
                    ByteArrayOutputStream rawPngStream = new ByteArrayOutputStream();
                    try {
                          if (!ImageIO.write(screenImage, "png", rawPngStream)) {
                            throw new RuntimeException(
                                "This Java environment does not support converting to PNG.");
                          }
                        } catch (IOException exception) {
                          // This should never happen because rawPngStream is an in-memory stream.
                         System.out.println("IOException=" + exception);
                        }
                    byte[] rawPngBytes = rawPngStream.toByteArray();
                    String base64Png = new Base64Encoder().encode(rawPngBytes);
                    
                    File screenshot = OutputType.FILE.convertFromBase64Png(base64Png);
                    System.out.println("screenshot==" + screenshot.toString());
                    screenshot.renameTo(new File("C:\\screenshottemp.png"));
                    
            } catch (IOException e) {
                // TODO Auto-generated catch block
                e.printStackTrace();
                System.out.println(e);
            }
        }
        
        
        public static void convertToRgba32(DataInput frameBuffer, int[] into) {
            try {
                for (int x = 0; x < into.length; x++) {
                    try{
                    int rgb = frameBuffer.readShort() & 0xffff;
                    int red = rgb >> 11;
                    red = (red << 3) | (red >> 2);
                    int green = (rgb >> 5) & 63;
                    green = (green << 2) | (green >> 4);
                    int blue = rgb & 31;
                    blue = (blue << 3) | (blue >> 2);
                    into[x] = 0xff000000 | (red << 16) | (green << 8) | blue;
                    }catch (EOFException e){
                        System.out.println("EOFException=" + e);
                    }
                  }
            } catch (IOException exception) {
                System.out.println("convertToRgba32Exception=" + exception);
          }
        }
        
    }
  1. 2
  1.  
  1. 首先是直接移植SystemUI的代码,实现截图效果,这部分的代码就不贴出来了,直接去下载代码吧, 关键的代码没有几句,最最主要的是:Surface.screenshot(),请看代码吧。
  2. [java]
  3. <SPAN style="FONT-SIZE: 16px">package org.winplus.ss;
  4. import java.io.File;
  5. import java.io.FileNotFoundException;
  6. import java.io.FileOutputStream;
  7. import java.io.IOException;
  8. import java.text.SimpleDateFormat;
  9. import java.util.Date;
  10. import android.app.Activity;
  11. import android.content.Context;
  12. import android.graphics.Bitmap;
  13. import android.graphics.Canvas;
  14. import android.graphics.Matrix;
  15. import android.os.Bundle;
  16. import android.util.DisplayMetrics;
  17. import android.util.Log;
  18. import android.view.Display;
  19. import android.view.Surface;
  20. import android.view.WindowManager;
  21. import android.os.SystemProperties;
  22. public class SimpleScreenshotActivity extends Activity {
  23. private Display mDisplay;
  24. private WindowManager mWindowManager;
  25. private DisplayMetrics mDisplayMetrics;
  26. private Bitmap mScreenBitmap;
  27. private Matrix mDisplayMatrix;
  28. @Override
  29. public void onCreate(Bundle savedInstanceState) {
  30. super.onCreate(savedInstanceState);
  31. setContentView(R.layout.main);
  32. new Thread(new Runnable() {
  33. @Override
  34. public void run() {
  35. takeScreenshot();
  36. }
  37. }).start();
  38. }
  39. private float getDegreesForRotation(int value) {
  40. switch (value) {
  41. case Surface.ROTATION_90:
  42. return 360f - 90f;
  43. case Surface.ROTATION_180:
  44. return 360f - 180f;
  45. case Surface.ROTATION_270:
  46. return 360f - 270f;
  47. }
  48. return 0f;
  49. }
  50. private void takeScreenshot() {
  51. mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  52. mDisplay = mWindowManager.getDefaultDisplay();
  53. mDisplayMetrics = new DisplayMetrics();
  54. mDisplay.getRealMetrics(mDisplayMetrics);
  55. mDisplayMatrix = new Matrix();
  56. float[] dims = { mDisplayMetrics.widthPixels,
  57. mDisplayMetrics.heightPixels };
  58. int value = mDisplay.getRotation();
  59. String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");
  60. if (hwRotation.equals("270") || hwRotation.equals("90")) {
  61. value = (value + 3) % 4;
  62. }
  63. float degrees = getDegreesForRotation(value);
  64. boolean requiresRotation = (degrees > 0);
  65. if (requiresRotation) {
  66. // Get the dimensions of the device in its native orientation
  67. mDisplayMatrix.reset();
  68. mDisplayMatrix.preRotate(-degrees);
  69. mDisplayMatrix.mapPoints(dims);
  70. dims[0] = Math.abs(dims[0]);
  71. dims[1] = Math.abs(dims[1]);
  72. }
  73. mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
  74. if (requiresRotation) {
  75. // Rotate the screenshot to the current orientation
  76. Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
  77. mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
  78. Canvas c = new Canvas(ss);
  79. c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
  80. c.rotate(degrees);
  81. c.translate(-dims[0] / 2, -dims[1] / 2);
  82. c.drawBitmap(mScreenBitmap, 0, 0, null);
  83. c.setBitmap(null);
  84. mScreenBitmap = ss;
  85. }
  86. // If we couldn't take the screenshot, notify the user
  87. if (mScreenBitmap == null) {
  88. return;
  89. }
  90. // Optimizations
  91. mScreenBitmap.setHasAlpha(false);
  92. mScreenBitmap.prepareToDraw();
  93. try {
  94. saveBitmap(mScreenBitmap);
  95. } catch (IOException e) {
  96. System.out.println(e.getMessage());
  97. }
  98. }
  99. public void saveBitmap(Bitmap bitmap) throws IOException {
  100. String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
  101. .format(new Date(System.currentTimeMillis()));
  102. File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");
  103. if(!file.exists()){
  104. file.createNewFile();
  105. }
  106. FileOutputStream out;
  107. try {
  108. out = new FileOutputStream(file);
  109. if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {
  110. out.flush();
  111. out.close();
  112. }
  113. } catch (FileNotFoundException e) {
  114. e.printStackTrace();
  115. } catch (IOException e) {
  116. e.printStackTrace();
  117. }
  118. }
  119. }
  120. </SPAN>
  121. package org.winplus.ss;
  122. import java.io.File;
  123. import java.io.FileNotFoundException;
  124. import java.io.FileOutputStream;
  125. import java.io.IOException;
  126. import java.text.SimpleDateFormat;
  127. import java.util.Date;
  128. import android.app.Activity;
  129. import android.content.Context;
  130. import android.graphics.Bitmap;
  131. import android.graphics.Canvas;
  132. import android.graphics.Matrix;
  133. import android.os.Bundle;
  134. import android.util.DisplayMetrics;
  135. import android.util.Log;
  136. import android.view.Display;
  137. import android.view.Surface;
  138. import android.view.WindowManager;
  139. import android.os.SystemProperties;
  140. public class SimpleScreenshotActivity extends Activity {
  141. private Display mDisplay;
  142. private WindowManager mWindowManager;
  143. private DisplayMetrics mDisplayMetrics;
  144. private Bitmap mScreenBitmap;
  145. private Matrix mDisplayMatrix;
  146. @Override
  147. public void onCreate(Bundle savedInstanceState) {
  148. super.onCreate(savedInstanceState);
  149. setContentView(R.layout.main);
  150. new Thread(new Runnable() {
  151. @Override
  152. public void run() {
  153. takeScreenshot();
  154. }
  155. }).start();
  156. }
  157. private float getDegreesForRotation(int value) {
  158. switch (value) {
  159. case Surface.ROTATION_90:
  160. return 360f - 90f;
  161. case Surface.ROTATION_180:
  162. return 360f - 180f;
  163. case Surface.ROTATION_270:
  164. return 360f - 270f;
  165. }
  166. return 0f;
  167. }
  168. private void takeScreenshot() {
  169. mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);
  170. mDisplay = mWindowManager.getDefaultDisplay();
  171. mDisplayMetrics = new DisplayMetrics();
  172. mDisplay.getRealMetrics(mDisplayMetrics);
  173. mDisplayMatrix = new Matrix();
  174. float[] dims = { mDisplayMetrics.widthPixels,
  175. mDisplayMetrics.heightPixels };
  176. int value = mDisplay.getRotation();
  177. String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");
  178. if (hwRotation.equals("270") || hwRotation.equals("90")) {
  179. value = (value + 3) % 4;
  180. }
  181. float degrees = getDegreesForRotation(value);
  182. boolean requiresRotation = (degrees > 0);
  183. if (requiresRotation) {
  184. // Get the dimensions of the device in its native orientation
  185. mDisplayMatrix.reset();
  186. mDisplayMatrix.preRotate(-degrees);
  187. mDisplayMatrix.mapPoints(dims);
  188. dims[0] = Math.abs(dims[0]);
  189. dims[1] = Math.abs(dims[1]);
  190. }
  191. mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
  192. if (requiresRotation) {
  193. // Rotate the screenshot to the current orientation
  194. Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
  195. mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
  196. Canvas c = new Canvas(ss);
  197. c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
  198. c.rotate(degrees);
  199. c.translate(-dims[0] / 2, -dims[1] / 2);
  200. c.drawBitmap(mScreenBitmap, 0, 0, null);
  201. c.setBitmap(null);
  202. mScreenBitmap = ss;
  203. }
  204. // If we couldn't take the screenshot, notify the user
  205. if (mScreenBitmap == null) {
  206. return;
  207. }
  208. // Optimizations
  209. mScreenBitmap.setHasAlpha(false);
  210. mScreenBitmap.prepareToDraw();
  211. try {
  212. saveBitmap(mScreenBitmap);
  213. } catch (IOException e) {
  214. System.out.println(e.getMessage());
  215. }
  216. }
  217. public void saveBitmap(Bitmap bitmap) throws IOException {
  218. String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")
  219. .format(new Date(System.currentTimeMillis()));
  220. File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");
  221. if(!file.exists()){
  222. file.createNewFile();
  223. }
  224. FileOutputStream out;
  225. try {
  226. out = new FileOutputStream(file);
  227. if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {
  228. out.flush();
  229. out.close();
  230. }
  231. } catch (FileNotFoundException e) {
  232. e.printStackTrace();
  233. } catch (IOException e) {
  234. e.printStackTrace();
  235. }
  236. }
  237. }
  238. PS:1、需要在AndroidManifest.xml中加入代码:android:sharedUserId="android.uid.system"
  239. 2、由于调用了@hide的API,所以编译得时候请使用makefile编译。或者通过在Eclipse中添加Jar文件通过编译。
  240. 3、此代码只在Android4.0中使用过,2.3的就没去做测试了。
  1.  
  1.  
  1.  
  1. 4 利用TakeScreenShotService截图
  1.  
  1. Android手机一般都自带有手机屏幕截图的功能:在手机任何界面(当然手机要是开机点亮状态),通过按组合键,屏幕闪一下,然后咔嚓一声,截图的照片会保存到当前手机的图库中,真是一个不错的功能!
  1. 以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 + HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:
  1. Android源码中对组合键的捕获。
  1. Android源码中对按键的捕获位于文件PhoneWindowManager.javaalps\frameworks\base\policy\src\com\android\internal\policy\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这个函数中我们会看到这么两段代码:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  1. .......
  2. case KeyEvent.KEYCODE_VOLUME_DOWN:
  3. case KeyEvent.KEYCODE_VOLUME_UP:
  4. case KeyEvent.KEYCODE_VOLUME_MUTE: {
  5. if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
  6. if (down) {
  7. if (isScreenOn && !mVolumeDownKeyTriggered
  8. && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
  9. mVolumeDownKeyTriggered = true;
  10. mVolumeDownKeyTime = event.getDownTime();
  11. mVolumeDownKeyConsumedByScreenshotChord = false;
  12. cancelPendingPowerKeyAction();
  13. interceptScreenshotChord();
  14. }
  15. } else {
  16. mVolumeDownKeyTriggered = false;
  17. cancelPendingScreenshotChordAction();
  18. }
  19. ......
  20.  
  21. case KeyEvent.KEYCODE_POWER: {
  22. result &= ~ACTION_PASS_TO_USER;
  23. if (down) {
  24. if (isScreenOn && !mPowerKeyTriggered
  25. && (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
  26. mPowerKeyTriggered = true;
  27. mPowerKeyTime = event.getDownTime();
  28. interceptScreenshotChord();
  29. }
  30. ......
  1. 可以看到正是在这里(响应Down事件)捕获是否按了音量下键和电源键的,而且两个地方都会进入函数interceptScreenshotChord()中,那么接下来看看这个函数干了什么工作:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  1. private void interceptScreenshotChord() {
  2. if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
  3. final long now = SystemClock.uptimeMillis();
  4. if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
  5. && now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
  6. mVolumeDownKeyConsumedByScreenshotChord = true;
  7. cancelPendingPowerKeyAction();
  8.  
  9. mHandler.postDelayed(mScreenshotChordLongPress,
  10. ViewConfiguration.getGlobalActionKeyTimeout());
  11. }
  12. }
  13. }
  1. 在这个函数中,用两个布尔变量判断是否同时按了音量下键和电源键后,再计算两个按键响应Down事件之间的时间差不超过150毫秒,也就认为是同时按了这两个键后,算是真正的捕获到屏幕截屏的组合键。
  1. 附言:文件PhoneWindowManager.java类是拦截键盘消息的处理类,在此类中还有对home键、返回键等好多按键的处理。
  1. Android源码中调用屏幕截图的接口。
  1. 捕获到组合键后,我们再看看android源码中是如何调用屏幕截图的函数接口。在上面的函数interceptScreenshotChord中我们看到用handler判断长按组合键500毫秒之后,会进入如下函数:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  1. private final Runnable mScreenshotChordLongPress = new Runnable() {
  2. public void run() {
  3. takeScreenshot();
  4. }
  5. };
  1. 在这里启动了一个线程来完成截屏的功能,接着看函数takeScreenshot():
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  49. 49
  50. 50
  51. 51
  52. 52
  1. private void takeScreenshot() {
  2. synchronized (mScreenshotLock) {
  3. if (mScreenshotConnection != null) {
  4. return;
  5. }
  6. ComponentName cn = new ComponentName("com.android.systemui",
  7. "com.android.systemui.screenshot.TakeScreenshotService");
  8. Intent intent = new Intent();
  9. intent.setComponent(cn);
  10. ServiceConnection conn = new ServiceConnection() {
  11. @Override
  12. public void onServiceConnected(ComponentName name, IBinder service) {
  13. synchronized (mScreenshotLock) {
  14. if (mScreenshotConnection != this) {
  15. return;
  16. }
  17. Messenger messenger = new Messenger(service);
  18. Message msg = Message.obtain(null, 1);
  19. final ServiceConnection myConn = this;
  20. Handler h = new Handler(mHandler.getLooper()) {
  21. @Override
  22. public void handleMessage(Message msg) {
  23. synchronized (mScreenshotLock) {
  24. if (mScreenshotConnection == myConn) {
  25. mContext.unbindService(mScreenshotConnection);
  26. mScreenshotConnection = null;
  27. mHandler.removeCallbacks(mScreenshotTimeout);
  28. }
  29. }
  30. }
  31. };
  32. msg.replyTo = new Messenger(h);
  33. msg.arg1 = msg.arg2 = 0;
  34. if (mStatusBar != null && mStatusBar.isVisibleLw())
  35. msg.arg1 = 1;
  36. if (mNavigationBar != null && mNavigationBar.isVisibleLw())
  37. msg.arg2 = 1;
  38. try {
  39. messenger.send(msg);
  40. } catch (RemoteException e) {
  41. }
  42. }
  43. }
  44. @Override
  45. public void onServiceDisconnected(ComponentName name) {}
  46. };
  47. if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
  48. mScreenshotConnection = conn;
  49. mHandler.postDelayed(mScreenshotTimeout, 10000);
  50. }
  51. }
  52. }
  1. 可以看到这个函数使用AIDL绑定了service服务到"com.android.systemui.screenshot.TakeScreenshotService",注意在service连接成功时,对messagemsg.arg1msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理。接着我们找到实现这个服务service的类TakeScreenshotService,看看其实现的流程:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  1. public class TakeScreenshotService extends Service {
  2. private static final String TAG = "TakeScreenshotService";
  3.  
  4. private static GlobalScreenshot mScreenshot;
  5.  
  6. private Handler mHandler = new Handler() {
  7. @Override
  8. public void handleMessage(Message msg) {
  9. switch (msg.what) {
  10. case 1:
  11. final Messenger callback = msg.replyTo;
  12. if (mScreenshot == null) {
  13. mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
  14. }
  15. mScreenshot.takeScreenshot(new Runnable() {
  16. @Override public void run() {
  17. Message reply = Message.obtain(null, 1);
  18. try {
  19. callback.send(reply);
  20. } catch (RemoteException e) {
  21. }
  22. }
  23. }, msg.arg1 > 0, msg.arg2 > 0);
  24. }
  25. }
  26. };
  27.  
  28. @Override
  29. public IBinder onBind(Intent intent) {
  30. return new Messenger(mHandler).getBinder();
  31. }
  32. }
  1. 在这个类中,我们主要看调用接口,用到了mScreenshot.takeScreenshot()传递了三个参数,第一个是个runnable,第二和第三个是之前message传递的两个参数msg.arg1msg.arg2。最后我们看看这个函数takeScreenshot(),位于文件GlobalScreenshot.java中(跟之前的函数重名但是文件路径不一样):
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  33. 33
  34. 34
  35. 35
  36. 36
  37. 37
  38. 38
  39. 39
  40. 40
  41. 41
  42. 42
  43. 43
  44. 44
  45. 45
  46. 46
  47. 47
  48. 48
  1. /**
  2. * Takes a screenshot of the current display and shows an animation.
  3. */
  4. void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
  5. // We need to orient the screenshot correctly (and the Surface api seems to take screenshots
  6. // only in the natural orientation of the device :!)
  7. mDisplay.getRealMetrics(mDisplayMetrics);
  8. float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
  9. float degrees = getDegreesForRotation(mDisplay.getRotation());
  10. boolean requiresRotation = (degrees > 0);
  11. if (requiresRotation) {
  12. // Get the dimensions of the device in its native orientation
  13. mDisplayMatrix.reset();
  14. mDisplayMatrix.preRotate(-degrees);
  15. mDisplayMatrix.mapPoints(dims);
  16. dims[0] = Math.abs(dims[0]);
  17. dims[1] = Math.abs(dims[1]);
  18. }
  19.  
  20. // Take the screenshot
  21. mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
  22. if (mScreenBitmap == null) {
  23. notifyScreenshotError(mContext, mNotificationManager);
  24. finisher.run();
  25. return;
  26. }
  27.  
  28. if (requiresRotation) {
  29. // Rotate the screenshot to the current orientation
  30. Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
  31. mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
  32. Canvas c = new Canvas(ss);
  33. c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
  34. c.rotate(degrees);
  35. c.translate(-dims[0] / 2, -dims[1] / 2);
  36. c.drawBitmap(mScreenBitmap, 0, 0, null);
  37. c.setBitmap(null);
  38. mScreenBitmap = ss;
  39. }
  40.  
  41. // Optimizations
  42. mScreenBitmap.setHasAlpha(false);
  43. mScreenBitmap.prepareToDraw();
  44.  
  45. // Start the post-screenshot animation
  46. startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
  47. statusBarVisible, navBarVisible);
  48. }
  1. 这段代码的注释比较详细,其实看到这里,我们算是真正看到截屏的操作了,具体的工作包括对屏幕大小、旋转角度的获取,然后调用Surface类的screenshot方法截屏保存到bitmap中,之后把这部分位图填充到一个画布上,最后再启动一个延迟的拍照动画效果。如果再往下探究screenshot方法,发现已经是一个native方法了:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  1. /**
  2. * Like {@link #screenshot(int, int, int, int)} but includes all
  3. * Surfaces in the screenshot.
  4. *
  5. * @hide
  6. */
  7. public static native Bitmap screenshot(int width, int height);
  1. 使用JNI技术调用底层的代码,如果再往下走,会发现映射这这个jni函数在文件android_view_Surface.cpp中,这个真的已经是底层c++语言了,统一调用的底层函数是:
  1. 1
  2. 2
  3. 3
  4. 4
  5. 5
  6. 6
  7. 7
  8. 8
  9. 9
  10. 10
  11. 11
  12. 12
  13. 13
  14. 14
  15. 15
  16. 16
  17. 17
  18. 18
  19. 19
  20. 20
  21. 21
  22. 22
  23. 23
  24. 24
  25. 25
  26. 26
  27. 27
  28. 28
  29. 29
  30. 30
  31. 31
  32. 32
  1. static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
  2. jint minLayer, jint maxLayer, bool allLayers)
  3. {
  4. ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
  5. if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {
  6. delete pixels;
  7. return 0;
  8. }
  9.  
  10. uint32_t w = pixels->getWidth();
  11. uint32_t h = pixels->getHeight();
  12. uint32_t s = pixels->getStride();
  13. uint32_t f = pixels->getFormat();
  14. ssize_t bpr = s * android::bytesPerPixel(f);
  15.  
  16. SkBitmap* bitmap = new SkBitmap();
  17. bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
  18. if (f == PIXEL_FORMAT_RGBX_8888) {
  19. bitmap->setIsOpaque(true);
  20. }
  21.  
  22. if (w > 0 && h > 0) {
  23. bitmap->setPixelRef(pixels)->unref();
  24. bitmap->lockPixels();
  25. } else {
  26. // be safe with an empty bitmap.
  27. delete pixels;
  28. bitmap->setPixels(NULL);
  29. }
  30.  
  31. return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
  32. }
  1.  

Android开发笔记:安卓程序截屏方法的更多相关文章

  1. IOS开发-几种截屏方法

    IOS开发-几种截屏方法 1.        UIGraphicsBeginImageContextWithOptions(pageView.page.bounds.size, YES, zoomSc ...

  2. 【Android开发笔记】程序崩溃异常总结

    广播注册相关(broadcastReceiver) 没有注册广播就注销广播 注册广播但未注销广播 注册广播后重复注销广播 解决办法: 添加一个布尔变量,注册广播后为true,若为true在执行注销,注 ...

  3. [置顶] Android开发笔记(成长轨迹)

    分类: 开发学习笔记2013-06-21 09:44 26043人阅读 评论(5) 收藏 Android开发笔记 1.控制台输出:called unimplemented OpenGL ES API ...

  4. 【转】Android开发笔记(序)写在前面的目录

    原文:http://blog.csdn.net/aqi00/article/details/50012511 知识点分类 一方面写写自己走过的弯路掉进去的坑,避免以后再犯:另一方面希望通过分享自己的经 ...

  5. 【转】Android开发笔记——圆角和边框们

    原文地址:http://blog.xianqu.org/2012/04/android-borders-and-radius-corners/ Android开发笔记——圆角和边框们 在做Androi ...

  6. Android开发笔记:打包数据库

    对于数据比较多的控制一般会加入SQLite数据库进行数据存储,在打包时这些数据库是不自动打包到apk中的,如何创建数据库呢 方法1:将创建数据库的sql语句在SQLiteHelper继承类中实现,在第 ...

  7. Android开发笔记--hello world 和目录结构

    原文:Android开发笔记--hello world 和目录结构 每接触一个新东西 都有一个hello world的例子. 1.新建项目 2.配置AVD AVD 没有要新建个,如果不能创建 运行SD ...

  8. Android开发笔记——以Volley图片加载、缓存、请求及展示为例理解Volley架构设计

    Volley是由Google开源的.用于Android平台上的网络通信库.Volley通过优化Android的网络请求流程,形成了以Request-RequestQueue-Response为主线的网 ...

  9. Android开发笔记(一百三十四)协调布局CoordinatorLayout

    协调布局CoordinatorLayout Android自5.0之后对UI做了较大的提升.一个重大的改进是推出了MaterialDesign库,而该库的基础即为协调布局CoordinatorLayo ...

随机推荐

  1. github+Hexo快速搭建个人博客

    注意 本文主要针对Windows平台和Hexo 3.x 准备工作 下载Git [下载地址] [Git官网](https://git-scm.com/download/) 下载Node.js [下载地址 ...

  2. HDU1106

    为了给学弟学妹讲课,我又水了一题-- 1: import java.util.*; 2: import java.io.*; 3: 4: public class HDU1106 5: { 6: pu ...

  3. android.support.v7.widget.Toolbar 中menu图标不显示问题

    <?xml version="1.0" encoding="utf-8"?><menu xmlns:android="http:// ...

  4. codeforces B. Prison Transfer

    题意:输入n,t,c,再输入n个数,然后问有多少个连续区间内的最大值小于等于t; #include <cstdio> #include <cstring> #include & ...

  5. oralce 仅配置精简客户端 连接plsql ( 版本需一直,要不都是32要不是都是64)

    1.Oracle服务器已经安装完成,版本10.2.0. 2.访问www.oracle.com,下载Oracle精简客户端. 下载页面地址:http://www.oracle.com/technetwo ...

  6. MySQL 没有索引 锁全表

    <h3 class="title" style="box-sizing: inherit; margin: 8px 0px 15px; padding: 0px; ...

  7. 今天愉快的hack小记

    今天发生了一件很好玩的事情...那就是WZJ的数据结构(负五)被人水掉了...用的是线段树暴力大发好... XYZ折腾了多长时间的论文题就这么被搞掉了...?窝来维护正义了! 怎么卡呢:让线段树走到叶 ...

  8. ♫【JS】offsetParent

    This property will return null on Webkit if the element is hidden (the style.display of this element ...

  9. 【转】JAVA程序中Float和Double精度丢失问题

    原文网址:http://blog.sina.com.cn/s/blog_827d041701017ctm.html 问题提出:12.0f-11.9f=0.10000038,"减不尽" ...

  10. 搭建ftp环境

    首先明确,ftp站点设置在服务器上,而在客户端上来使用ftp工具来进行上传文件 具体环境搭建如下两个链接,一个server2003,一个是win7 server2003:http://jingyan. ...