1,基于Android SDK的截屏方法
(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>
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!");
        }
      }
 
    });
 
  }
}
这个代码会在按下app中按键的时候自动在手机的/sdcard/目录下生成一个时间戳命名的png截屏文件。
这种截屏有一个问题,就是只能截到一部分,比如电池指示部分就截不出来了。
(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(){ 
  2.    String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ; 
  3. try {                     
  4.           Runtime. getRuntime().exec("screencap -p " + mSavedPath); 
  5.    } catch (Exception e) { 
  6.           e.printStackTrace();
  7.    }

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

    1).修改Android.mk, 添加系统权限
          LOCAL_CERTIFICATE := platform
         2).修改AndroidManifest.xml 文件,添加
权限
<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 ;
}
 
 
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. }
3 Android本地编程(Native Programming)读取framebuffer
 
 
(1)命令行,框架的截屏功能是通过framebuffer来实现的,所以我们先来介绍一下framebuffer。
framebuffer介绍
帧缓冲(framebuffer)是Linux为显示设备提供的一个接口,把显存抽象后的一种设备,他允许上层应用程序在图形模式下直接对显示缓冲区进行 读写操作。这种操作是抽象的,统一的。用户不必关心物理显存的位置、换页机制等等具体细节。这些都是由Framebuffer设备驱动来完成的。
Linux FrameBuffer 本质上只是提供了对图形设备的硬件抽象,在开发者看来,FrameBuffer 是一块显示缓存,往显示缓存中写入特定格式的数据就意味着向屏幕输出内容。所以说FrameBuffer就是一块白板。例如对于初始化为16 位色的FrameBuffer 来说, FrameBuffer中的两个字节代表屏幕上一个点,从上到下,从左至右,屏幕位置与内存地址是顺序的线性关系。
帧缓存有个地址,是在内存里。我们通过不停的向frame buffer中写入数据, 显示控制器就自动的从frame buffer中取数据并显示出来。全部的图形都共享内存中同一个帧缓存。
Android截屏实现思路
Android系统是基于Linux内核的,所以也存在framebuffer这个设备,我们要实现截屏的话只要能获取到framebuffer中的数据,然后把数据转换成图片就可以了,android中的framebuffer数据是存放在 /dev/graphics/fb0 文件中的,所以我们只需要来获取这个文件的数据就可以得到当前屏幕的内容。
现在我们的测试代码运行时候是通过RC(remote controller)方式来运行被测应用的,那就需要在PC机上来访问模拟器或者真机上的framebuffer数据,这个的话可以通过android的ADB命令来实现。
具体实现

/***********************************************************************
  *
  *   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; /**
 */
public class ScreenShot {     /**
     * @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);
      }
    }
    
}
(2)
 
  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的就没去做测试了。

 
 
4 利用TakeScreenShotService截图
 
Android手机一般都自带有手机屏幕截图的功能:在手机任何界面(当然手机要是开机点亮状态),通过按组合键,屏幕闪一下,然后咔嚓一声,截图的照片会保存到当前手机的图库中,真是一个不错的功能!
以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 + HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:
Android源码中对组合键的捕获。
Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal\policy\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这个函数中我们会看到这么两段代码:
 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
.......
case KeyEvent.KEYCODE_VOLUME_DOWN:
case KeyEvent.KEYCODE_VOLUME_UP:
case KeyEvent.KEYCODE_VOLUME_MUTE: {
if (keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) {
if (down) {
if (isScreenOn && !mVolumeDownKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mVolumeDownKeyTriggered = true;
mVolumeDownKeyTime = event.getDownTime();
mVolumeDownKeyConsumedByScreenshotChord = false;
cancelPendingPowerKeyAction();
interceptScreenshotChord();
}
} else {
mVolumeDownKeyTriggered = false;
cancelPendingScreenshotChordAction();
}
...... case KeyEvent.KEYCODE_POWER: {
result &= ~ACTION_PASS_TO_USER;
if (down) {
if (isScreenOn && !mPowerKeyTriggered
&& (event.getFlags() & KeyEvent.FLAG_FALLBACK) == 0) {
mPowerKeyTriggered = true;
mPowerKeyTime = event.getDownTime();
interceptScreenshotChord();
}
......
可以看到正是在这里(响应Down事件)捕获是否按了音量下键和电源键的,而且两个地方都会进入函数interceptScreenshotChord()中,那么接下来看看这个函数干了什么工作:
 1
2
3
4
5
6
7
8
9
10
11
12
13
    private void interceptScreenshotChord() {
if (mVolumeDownKeyTriggered && mPowerKeyTriggered && !mVolumeUpKeyTriggered) {
final long now = SystemClock.uptimeMillis();
if (now <= mVolumeDownKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS
&& now <= mPowerKeyTime + SCREENSHOT_CHORD_DEBOUNCE_DELAY_MILLIS) {
mVolumeDownKeyConsumedByScreenshotChord = true;
cancelPendingPowerKeyAction(); mHandler.postDelayed(mScreenshotChordLongPress,
ViewConfiguration.getGlobalActionKeyTimeout());
}
}
}
在这个函数中,用两个布尔变量判断是否同时按了音量下键和电源键后,再计算两个按键响应Down事件之间的时间差不超过150毫秒,也就认为是同时按了这两个键后,算是真正的捕获到屏幕截屏的组合键。
附言:文件PhoneWindowManager.java类是拦截键盘消息的处理类,在此类中还有对home键、返回键等好多按键的处理。
Android源码中调用屏幕截图的接口。
捕获到组合键后,我们再看看android源码中是如何调用屏幕截图的函数接口。在上面的函数interceptScreenshotChord中我们看到用handler判断长按组合键500毫秒之后,会进入如下函数:
1
2
3
4
5
    private final Runnable mScreenshotChordLongPress = new Runnable() {
public void run() {
takeScreenshot();
}
};
在这里启动了一个线程来完成截屏的功能,接着看函数takeScreenshot():
 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
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
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() {
@Override
public 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()) {
@Override
public 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) {
}
}
}
@Override
public void onServiceDisconnected(ComponentName name) {}
};
if (mContext.bindService(intent, conn, Context.BIND_AUTO_CREATE)) {
mScreenshotConnection = conn;
mHandler.postDelayed(mScreenshotTimeout, 10000);
}
}
}
可以看到这个函数使用AIDL绑定了service服务到"com.android.systemui.screenshot.TakeScreenshotService",注意在service连接成功时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理。接着我们找到实现这个服务service的类TakeScreenshotService,看看其实现的流程:
 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
public class TakeScreenshotService extends Service {
private static final String TAG = "TakeScreenshotService"; private static GlobalScreenshot mScreenshot; private Handler mHandler = new Handler() {
@Override
public void handleMessage(Message msg) {
switch (msg.what) {
case 1:
final Messenger callback = msg.replyTo;
if (mScreenshot == null) {
mScreenshot = new GlobalScreenshot(TakeScreenshotService.this);
}
mScreenshot.takeScreenshot(new Runnable() {
@Override public void run() {
Message reply = Message.obtain(null, 1);
try {
callback.send(reply);
} catch (RemoteException e) {
}
}
}, msg.arg1 > 0, msg.arg2 > 0);
}
}
}; @Override
public IBinder onBind(Intent intent) {
return new Messenger(mHandler).getBinder();
}
}
在这个类中,我们主要看调用接口,用到了mScreenshot.takeScreenshot()传递了三个参数,第一个是个runnable,第二和第三个是之前message传递的两个参数msg.arg1和msg.arg2。最后我们看看这个函数takeScreenshot(),位于文件GlobalScreenshot.java中(跟之前的函数重名但是文件路径不一样):
 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
38
39
40
41
42
43
44
45
46
47
48
 /**
* Takes a screenshot of the current display and shows an animation.
*/
void takeScreenshot(Runnable finisher, boolean statusBarVisible, boolean navBarVisible) {
// We need to orient the screenshot correctly (and the Surface api seems to take screenshots
// only in the natural orientation of the device :!)
mDisplay.getRealMetrics(mDisplayMetrics);
float[] dims = {mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels};
float degrees = getDegreesForRotation(mDisplay.getRotation());
boolean requiresRotation = (degrees > 0);
if (requiresRotation) {
// Get the dimensions of the device in its native orientation
mDisplayMatrix.reset();
mDisplayMatrix.preRotate(-degrees);
mDisplayMatrix.mapPoints(dims);
dims[0] = Math.abs(dims[0]);
dims[1] = Math.abs(dims[1]);
} // Take the screenshot
mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);
if (mScreenBitmap == null) {
notifyScreenshotError(mContext, mNotificationManager);
finisher.run();
return;
} if (requiresRotation) {
// Rotate the screenshot to the current orientation
Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,
mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(ss);
c.translate(ss.getWidth() / 2, ss.getHeight() / 2);
c.rotate(degrees);
c.translate(-dims[0] / 2, -dims[1] / 2);
c.drawBitmap(mScreenBitmap, 0, 0, null);
c.setBitmap(null);
mScreenBitmap = ss;
} // Optimizations
mScreenBitmap.setHasAlpha(false);
mScreenBitmap.prepareToDraw(); // Start the post-screenshot animation
startAnimation(finisher, mDisplayMetrics.widthPixels, mDisplayMetrics.heightPixels,
statusBarVisible, navBarVisible);
}
这段代码的注释比较详细,其实看到这里,我们算是真正看到截屏的操作了,具体的工作包括对屏幕大小、旋转角度的获取,然后调用Surface类的screenshot方法截屏保存到bitmap中,之后把这部分位图填充到一个画布上,最后再启动一个延迟的拍照动画效果。如果再往下探究screenshot方法,发现已经是一个native方法了:
1
2
3
4
5
6
7
    /**
* Like {@link #screenshot(int, int, int, int)} but includes all
* Surfaces in the screenshot.
*
* @hide
*/
public static native Bitmap screenshot(int width, int height);
使用JNI技术调用底层的代码,如果再往下走,会发现映射这这个jni函数在文件android_view_Surface.cpp中,这个真的已经是底层c++语言了,统一调用的底层函数是:
 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
static jobject doScreenshot(JNIEnv* env, jobject clazz, jint width, jint height,
jint minLayer, jint maxLayer, bool allLayers)
{
ScreenshotPixelRef* pixels = new ScreenshotPixelRef(NULL);
if (pixels->update(width, height, minLayer, maxLayer, allLayers) != NO_ERROR) {
delete pixels;
return 0;
} uint32_t w = pixels->getWidth();
uint32_t h = pixels->getHeight();
uint32_t s = pixels->getStride();
uint32_t f = pixels->getFormat();
ssize_t bpr = s * android::bytesPerPixel(f); SkBitmap* bitmap = new SkBitmap();
bitmap->setConfig(convertPixelFormat(f), w, h, bpr);
if (f == PIXEL_FORMAT_RGBX_8888) {
bitmap->setIsOpaque(true);
} if (w > 0 && h > 0) {
bitmap->setPixelRef(pixels)->unref();
bitmap->lockPixels();
} else {
// be safe with an empty bitmap.
delete pixels;
bitmap->setPixels(NULL);
} return GraphicsJNI::createBitmap(env, bitmap, false, NULL);
}
												

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. Windows开发技术的历史

    原文地址:http://www.kuqin.com/windows/20090315/40172.html Windows已经有22年的历史,这22年来,微软官方主力推行的编程语言与API有四个分水岭 ...

  2. How to solve "The specified service has been marked for deletion" error

    There may be several causes which lead to the service being stuck in “marked for deletion”. Microsof ...

  3. 【深入浅出jQuery】源码浅析2--使用技巧

    最近一直在研读 jQuery 源码,初看源码一头雾水毫无头绪,真正静下心来细看写的真是精妙,让你感叹代码之美. 其结构明晰,高内聚.低耦合,兼具优秀的性能与便利的扩展性,在浏览器的兼容性(功能缺陷.渐 ...

  4. ExtJS简单的动画效果2(ext js淡入淡出特效)

    Ext 开发小组则提供了 Fx 类集中处理了大部分常用的 js 动画特效,减少了我们自己手写代码的复杂度. 面我给出一个简单的实例代码,其中囊括了大部分的 Ext 动画效果: (注意导入js和css文 ...

  5. ORACLE PL/SQL异常处理(Exception)学习笔记

    1.PL/SQL错误类型 错误类型 报告者 处理方法 编译时错误 PL/SQL编译器 交互式地处理:编译器报告错误,你必须更正这些错误 运行时错误 PL/SQL运行时引擎 程序化地处理:异常由异常处理 ...

  6. SCVMM和SQL分别建在不同服务器上报错:Error ID 319 during database creation on remote SQL Server

    问题发生了,测试过权限,帐号,服务,工具问题,均不是. 在微软网站找到解决办法. 就是MASTER.SQL的脚本在数据库服务器上单独运行. 然后,安装SCVMM时,不新建数据库,直接指现建好的库. 搞 ...

  7. Android Wear开发 - 数据通讯 - 第二节 : 数据的发送与接收

    本节由介绍3种数据的发送接收:1.Data Items : 比特类型数据,限制100KB以内2.Assets : 资源类型数据,大小无上限3.Message : 发送消息,触发指令 http://de ...

  8. php pack、unpack、ord 函数使用方法

    string pack ( string $format [, mixed $args [, mixed $... ]] ) Pack given arguments into a binary st ...

  9. 《how to design programs》15章 相互引用的数据定义

    由结构体组成的表与结构体中的表. 在用追溯形式建立家家谱树时,我们通常从某个后代除法,依次处理它的父母,组父母等.而构建树时,我们会不断添加谁是谁的孩子,而不是写出谁是谁的父母,从而建立一颗后代家谱树 ...

  10. [Locked] Binary Tree Vertical Order Traversal

    Binary Tree Vertical Order Traversal Given a binary tree, return the vertical order traversal of its ...