• Android中使用代码截图的各种方法总结


    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文件中添加
    android:name="android.permission.READ_FRAME_BUFFER"/>
    (2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mk
     LOCAL_CERTIFICATE := platform

    1. publicvoid takeScreenShot(){

    2.    String mSavedPath = Environment.getExternalStorageDirectory()+File. separator +

      try {                    

    3.           Runtime. getRuntime().exec(

         } catch (Exception e) {

    4.           e.printStackTrace();

    5.    }


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

        1).修改Android.mk, 添加系统权限
              LOCAL_CERTIFICATE := platform
             2).修改AndroidManifest.xml 文件,添加
    权限
    android: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.     
    12.   for (int i = 0; i < 10; i++) {  
    13.    Date date=new Date();  
    14.    SimpleDateFormat df=new SimpleDateFormat("MM-dd-HH-mm-ss");   
    15.    String nowTime = df.format(date);  
    16.    screenshot.getScreenShot(device, "Robotium" + nowTime);  
    17.    try {  
    18.     Thread.sleep(1000);  
    19.    } catch (InterruptedException e) {  
    20.     // TODO Auto-generated catch block  
    21.     e.printStackTrace();  
    22.    }  
    23.   }  
    24.  }  
    25.   
    26.    
    27.  public void getScreenShot(IDevice device,String filename) {  
    28.   RawImage rawScreen = null;  
    29.   try {  
    30.    rawScreen = device.getScreenshot();  
    31.   } catch (TimeoutException e) {  
    32.    // TODO Auto-generated catch block  
    33.    e.printStackTrace();  
    34.   } catch (AdbCommandRejectedException e) {  
    35.    // TODO Auto-generated catch block  
    36.    e.printStackTrace();  
    37.   } catch (IOException e) {  
    38.    // TODO Auto-generated catch block  
    39.    e.printStackTrace();  
    40.   }  
    41.   if (rawScreen != null) {  
    42.    Boolean landscape = false;  
    43.    int width2 = landscape ? rawScreen.height : rawScreen.width;  
    44.    int height2 = landscape ? rawScreen.width : rawScreen.height;  
    45.    if (image == null) {  
    46.     image = new BufferedImage(width2, height2,  
    47.       BufferedImage.TYPE_INT_RGB);  
    48.    } else {  
    49.     if (image.getHeight() != height2 || image.getWidth() != width2) {  
    50.      image = new BufferedImage(width2, height2,  
    51.        BufferedImage.TYPE_INT_RGB);  
    52.     }  
    53.    }  
    54.    int index = 0;  
    55.    int indexInc = rawScreen.bpp >> 3;  
    56.    for (int y = 0; y < rawScreen.height; y++) {  
    57.     for (int x = 0; x < rawScreen.width; x++, index += indexInc) {  
    58.      int value = rawScreen.getARGB(index);  
    59.      if (landscape)  
    60.       image.setRGB(y, rawScreen.width - x - 1, value);  
    61.      else  
    62.       image.setRGB(x, y, value);  
    63.     }  
    64.    }  
    65.    try {  
    66.     ImageIO.write((RenderedImage) image, "PNG", new File("D:/"  
    67.       + filename + ".jpg"));  
    68.    } catch (IOException e) {  
    69.     // TODO Auto-generated catch block  
    70.     e.printStackTrace();  
    71.    }  
    72.   }  
    73.  }  
    74.   
    75.  /** 
    76.   * 获取得到device对象 
    77.   * @return 
    78.   */  
    79.  private IDevice getDevice(){  
    80.   IDevice device;  
    81.   AndroidDebugBridge bridge = AndroidDebugBridge  
    82.     .createBridge("adb", true);//如果代码有问题请查看API,修改此处的参数值试一下  
    83.   waitDevicesList(bridge);  
    84.   IDevice devices[] = bridge.getDevices();  
    85.   device = devices[0];  
    86.   return device;  
    87.  }  
    88.    
    89.  /** 
    90.   * 等待查找device 
    91.   * @param bridge 
    92.   */  
    93.  private void waitDevicesList(AndroidDebugBridge bridge) {  
    94.   int count = 0;  
    95.   while (bridge.hasInitialDeviceList() == false) {  
    96.    try {  
    97.     Thread.sleep(500);   
    98.     count++;  
    99.    } catch (InterruptedException e) {  
    100.    }  
    101.    if (count > 240) {  
    102.     System.err.print("等待获取设备超时");  
    103.     break;  
    104.    }  
    105.   }  
    106.  }  

    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.   
    3. [java]  
    4. <SPAN style="FONT-SIZE: 16px">package org.winplus.ss;   
    5.    
    6. import java.io.File;   
    7. import java.io.FileNotFoundException;   
    8. import java.io.FileOutputStream;   
    9. import java.io.IOException;   
    10. import java.text.SimpleDateFormat;   
    11. import java.util.Date;   
    12.    
    13. import android.app.Activity;   
    14. import android.content.Context;   
    15. import android.graphics.Bitmap;   
    16. import android.graphics.Canvas;   
    17. import android.graphics.Matrix;   
    18. import android.os.Bundle;   
    19. import android.util.DisplayMetrics;   
    20. import android.util.Log;   
    21. import android.view.Display;   
    22. import android.view.Surface;   
    23. import android.view.WindowManager;   
    24. import android.os.SystemProperties;   
    25.    
    26. public class SimpleScreenshotActivity extends Activity {   
    27.    
    28.     private Display mDisplay;   
    29.     private WindowManager mWindowManager;   
    30.     private DisplayMetrics mDisplayMetrics;   
    31.     private Bitmap mScreenBitmap;   
    32.     private Matrix mDisplayMatrix;   
    33.    
    34.     @Override   
    35.     public void onCreate(Bundle savedInstanceState) {   
    36.         super.onCreate(savedInstanceState);   
    37.         setContentView(R.layout.main);   
    38.    
    39.         new Thread(new Runnable() {   
    40.    
    41.             @Override   
    42.             public void run() {   
    43.                 takeScreenshot();   
    44.    
    45.             }   
    46.         }).start();   
    47.     }   
    48.    
    49.     private float getDegreesForRotation(int value) {   
    50.         switch (value) {   
    51.         case Surface.ROTATION_90:   
    52.             return 360f - 90f;   
    53.         case Surface.ROTATION_180:   
    54.             return 360f - 180f;   
    55.         case Surface.ROTATION_270:   
    56.             return 360f - 270f;   
    57.         }   
    58.         return 0f;   
    59.     }   
    60.    
    61.     private void takeScreenshot() {   
    62.         mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);   
    63.         mDisplay = mWindowManager.getDefaultDisplay();   
    64.         mDisplayMetrics = new DisplayMetrics();   
    65.         mDisplay.getRealMetrics(mDisplayMetrics);   
    66.         mDisplayMatrix = new Matrix();   
    67.         float[] dims = { mDisplayMetrics.widthPixels,   
    68.                 mDisplayMetrics.heightPixels };   
    69.    
    70.         int value = mDisplay.getRotation();   
    71.         String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");   
    72.         if (hwRotation.equals("270") || hwRotation.equals("90")) {   
    73.             value = (value + 3) % 4;   
    74.         }   
    75.         float degrees = getDegreesForRotation(value);   
    76.    
    77.         boolean requiresRotation = (degrees > 0);   
    78.         if (requiresRotation) {   
    79.             // Get the dimensions of the device in its native orientation    
    80.             mDisplayMatrix.reset();   
    81.             mDisplayMatrix.preRotate(-degrees);   
    82.             mDisplayMatrix.mapPoints(dims);   
    83.    
    84.             dims[0] = Math.abs(dims[0]);   
    85.             dims[1] = Math.abs(dims[1]);   
    86.         }   
    87.    
    88.         mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);   
    89.    
    90.         if (requiresRotation) {   
    91.             // Rotate the screenshot to the current orientation    
    92.             Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,   
    93.                     mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);   
    94.             Canvas c = new Canvas(ss);   
    95.             c.translate(ss.getWidth() / 2, ss.getHeight() / 2);   
    96.             c.rotate(degrees);   
    97.             c.translate(-dims[0] / 2, -dims[1] / 2);   
    98.             c.drawBitmap(mScreenBitmap, 0, 0, null);   
    99.             c.setBitmap(null);   
    100.             mScreenBitmap = ss;   
    101.         }   
    102.    
    103.         // If we couldn't take the screenshot, notify the user    
    104.         if (mScreenBitmap == null) {   
    105.             return;   
    106.         }   
    107.    
    108.         // Optimizations    
    109.         mScreenBitmap.setHasAlpha(false);   
    110.         mScreenBitmap.prepareToDraw();   
    111.            
    112.         try {   
    113.             saveBitmap(mScreenBitmap);   
    114.         } catch (IOException e) {   
    115.             System.out.println(e.getMessage());   
    116.         }   
    117.     }   
    118.    
    119.     public void saveBitmap(Bitmap bitmap) throws IOException {   
    120.         String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")   
    121.                 .format(new Date(System.currentTimeMillis()));   
    122.         File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");   
    123.         if(!file.exists()){   
    124.             file.createNewFile();   
    125.         }   
    126.         FileOutputStream out;   
    127.         try {   
    128.             out = new FileOutputStream(file);   
    129.             if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {   
    130.                 out.flush();   
    131.                 out.close();   
    132.             }   
    133.         } catch (FileNotFoundException e) {   
    134.             e.printStackTrace();   
    135.         } catch (IOException e) {   
    136.             e.printStackTrace();   
    137.         }   
    138.     }   
    139. }   
    140. </SPAN>   
    141.   
    142. package org.winplus.ss;  
    143.   
    144. import java.io.File;  
    145. import java.io.FileNotFoundException;  
    146. import java.io.FileOutputStream;  
    147. import java.io.IOException;  
    148. import java.text.SimpleDateFormat;  
    149. import java.util.Date;  
    150.   
    151. import android.app.Activity;  
    152. import android.content.Context;  
    153. import android.graphics.Bitmap;  
    154. import android.graphics.Canvas;  
    155. import android.graphics.Matrix;  
    156. import android.os.Bundle;  
    157. import android.util.DisplayMetrics;  
    158. import android.util.Log;  
    159. import android.view.Display;  
    160. import android.view.Surface;  
    161. import android.view.WindowManager;  
    162. import android.os.SystemProperties;  
    163.   
    164. public class SimpleScreenshotActivity extends Activity {  
    165.   
    166.  private Display mDisplay;  
    167.  private WindowManager mWindowManager;  
    168.  private DisplayMetrics mDisplayMetrics;  
    169.  private Bitmap mScreenBitmap;  
    170.  private Matrix mDisplayMatrix;  
    171.   
    172.  @Override  
    173.  public void onCreate(Bundle savedInstanceState) {  
    174.   super.onCreate(savedInstanceState);  
    175.   setContentView(R.layout.main);  
    176.   
    177.   new Thread(new Runnable() {  
    178.   
    179.    @Override  
    180.    public void run() {  
    181.     takeScreenshot();  
    182.   
    183.    }  
    184.   }).start();  
    185.  }  
    186.   
    187.  private float getDegreesForRotation(int value) {  
    188.   switch (value) {  
    189.   case Surface.ROTATION_90:  
    190.    return 360f - 90f;  
    191.   case Surface.ROTATION_180:  
    192.    return 360f - 180f;  
    193.   case Surface.ROTATION_270:  
    194.    return 360f - 270f;  
    195.   }  
    196.   return 0f;  
    197.  }  
    198.   
    199.  private void takeScreenshot() {  
    200.   mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);  
    201.   mDisplay = mWindowManager.getDefaultDisplay();  
    202.   mDisplayMetrics = new DisplayMetrics();  
    203.   mDisplay.getRealMetrics(mDisplayMetrics);  
    204.   mDisplayMatrix = new Matrix();  
    205.   float[] dims = { mDisplayMetrics.widthPixels,  
    206.     mDisplayMetrics.heightPixels };  
    207.   
    208.   int value = mDisplay.getRotation();  
    209.   String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");  
    210.   if (hwRotation.equals("270") || hwRotation.equals("90")) {  
    211.    value = (value + 3) % 4;  
    212.   }  
    213.   float degrees = getDegreesForRotation(value);  
    214.   
    215.   boolean requiresRotation = (degrees > 0);  
    216.   if (requiresRotation) {  
    217.    // Get the dimensions of the device in its native orientation  
    218.    mDisplayMatrix.reset();  
    219.    mDisplayMatrix.preRotate(-degrees);  
    220.    mDisplayMatrix.mapPoints(dims);  
    221.   
    222.    dims[0] = Math.abs(dims[0]);  
    223.    dims[1] = Math.abs(dims[1]);  
    224.   }  
    225.   
    226.   mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);  
    227.   
    228.   if (requiresRotation) {  
    229.             // Rotate the screenshot to the current orientation  
    230.             Bitmap ss = Bitmap.createBitmap(mDisplayMetrics.widthPixels,  
    231.                     mDisplayMetrics.heightPixels, Bitmap.Config.ARGB_8888);  
    232.             Canvas c = new Canvas(ss);  
    233.             c.translate(ss.getWidth() / 2, ss.getHeight() / 2);  
    234.             c.rotate(degrees);  
    235.             c.translate(-dims[0] / 2, -dims[1] / 2);  
    236.             c.drawBitmap(mScreenBitmap, 0, 0, null);  
    237.             c.setBitmap(null);  
    238.             mScreenBitmap = ss;  
    239.         }  
    240.   
    241.         // If we couldn't take the screenshot, notify the user  
    242.         if (mScreenBitmap == null) {  
    243.             return;  
    244.         }  
    245.   
    246.         // Optimizations  
    247.         mScreenBitmap.setHasAlpha(false);  
    248.         mScreenBitmap.prepareToDraw();  
    249.          
    250.   try {  
    251.    saveBitmap(mScreenBitmap);  
    252.   } catch (IOException e) {  
    253.    System.out.println(e.getMessage());  
    254.   }  
    255.  }  
    256.   
    257.  public void saveBitmap(Bitmap bitmap) throws IOException {  
    258.   String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")  
    259.     .format(new Date(System.currentTimeMillis()));  
    260.   File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");  
    261.   if(!file.exists()){  
    262.    file.createNewFile();  
    263.   }  
    264.   FileOutputStream out;  
    265.   try {  
    266.    out = new FileOutputStream(file);  
    267.    if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {  
    268.     out.flush();  
    269.     out.close();  
    270.    }  
    271.   } catch (FileNotFoundException e) {  
    272.    e.printStackTrace();  
    273.   } catch (IOException e) {  
    274.    e.printStackTrace();  
    275.   }  
    276.  }  
    277. }  
    278.   
    279. PS:1、需要在AndroidManifest.xml中加入代码:android:sharedUserId="android.uid.system"  
    280.   
    281.          2、由于调用了@hide的API,所以编译得时候请使用makefile编译。或者通过在Eclipse中添加Jar文件通过编译。  
    282.   
    283.          3、此代码只在Android4.0中使用过,2.3的就没去做测试了。  



    4 利用TakeScreenShotService截图

    Android手机一般都自带有手机屏幕截图的功能:在手机任何界面(当然手机要是开机点亮状态),通过按组合键,屏幕闪一下,然后咔嚓一声,截图的照片会保存到当前手机的图库中,真是一个不错的功能!

    以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 + HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能 呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:

    Android源码中对组合键的捕获。

    Android源码中对按键的捕获位于文件PhoneWindowManager.java(alpsframeworksasepolicy srccomandroidinternalpolicyimpl)中,这个类处理所有的键盘输入事件,其中函数 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);
    }
    

    由于对C++不熟,我这里就不敢多言了。其实到这里,算是对手机android源码中通过组合键屏幕截图的整个流程有个大体了解了,一般我们在改动中熟悉按键的捕获原理,并且清楚调用的截屏函数接口即可,如果有兴趣的,可以继续探究更深的底层是如何实现的。

    应用

  • 相关阅读:
    DataTable转List<T>
    Ajax跨域解决方案
    日期格式换算
    序列化和反序列化
    C#导出数据量大于100万【csv】
    DataSet转Model
    正则表达式-小数-XML取值验证
    微信绑定欢迎页面
    正则表达式 从X开始到X结束
    html5的新特性有哪些?除了新标签之外还有新的特性?新增的标签主要是为了什么?
  • 原文地址:https://www.cnblogs.com/xin36933/p/3807860.html
Copyright © 2020-2023  润新知