DELPHI盒子
!实时搜索: 盒子论坛 | 注册用户 | 修改信息 | 退出
检举帖 | 全文检索 | 关闭广告 | 捐赠
技术论坛
 用户名
 密  码
自动登陆(30天有效)
忘了密码
≡技术区≡
DELPHI技术
lazarus/fpc/Free Pascal
移动应用开发
Web应用开发
数据库专区
报表专区
网络通讯
开源项目
论坛精华贴
≡发布区≡
发布代码
发布控件
文档资料
经典工具
≡事务区≡
网站意见
盒子之家
招聘应聘
信息交换
论坛信息
最新加入: jeff1314
今日帖子: 0
在线用户: 4
导航: 论坛 -> DELPHI技术 斑竹:liumazi,sephil  
作者:
男 greenzyzyzy (小张) ▲▲▲▲△ -
普通会员
2018/6/27 12:29:33
标题:
关于android 5.0以上为什么不能用fb0截屏的问题。 浏览:1008
加入我的收藏
楼主: 我的android 4.2.1的手机有/dev/graphics/fb0这个文件。
但是新买的android 5.1.1的手机,/dev这个目录下面什么文件都没有?
何解?不能用fb0截屏了吗?
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2018/6/27 12:32:31
1楼: 试试先 ROOT。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 greenzyzyzy (小张) ▲▲▲▲△ -
普通会员
2018/6/27 12:44:58
2楼: 手机自带权限管理,用RE管理器,看不到fb0
----------------------------------------------
-
作者:
男 wang_80919 (Flying Wang) ★☆☆☆☆ -
普通会员
2018/6/27 12:56:49
3楼: 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

publicvoid takeScreenShot(){
   String mSavedPath = Environment.getExternalStorageDirectory()+File. separator +
try {          
          Runtime. getRuntime().exec(
   } catch (Exception e) {
          e.printStackTrace();
   }

(3).利用系统的API,实现Screenshot,这部分代码是系统隐藏的,需要在源码下编译,
    1).修改Android.mk, 添加系统权限
          LOCAL_CERTIFICATE := platform
         2).修改AndroidManifest.xml 文件,添加
权限
android:name="android.permission.READ_FRAME_BUFFER"
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进行截屏
[java] view plaincopy
public class ScreenShot {  
 private BufferedImage image = null;  
 /** 
  * @param args 
  */  
 public static void main(String[] args) {  
  // TODO Auto-generated method stub  
  AndroidDebugBridge.init(false); //  
  ScreenShot screenshot = new ScreenShot();  
  IDevice device = screenshot.getDevice();  
    
  for (int i = 0; i < 10; i++) {  
   Date date=new Date();  
   SimpleDateFormat df=new SimpleDateFormat("MM-dd-HH-mm-ss");   
   String nowTime = df.format(date);  
   screenshot.getScreenShot(device, "Robotium" + nowTime);  
   try {  
    Thread.sleep(1000);  
   } catch (InterruptedException e) {  
    // TODO Auto-generated catch block  
    e.printStackTrace();  
   }  
  }  
 }  
  
   
 public void getScreenShot(IDevice device,String filename) {  
  RawImage rawScreen = null;  
  try {  
   rawScreen = device.getScreenshot();  
  } catch (TimeoutException e) {  
   // TODO Auto-generated catch block  
   e.printStackTrace();  
  } catch (AdbCommandRejectedException e) {  
   // TODO Auto-generated catch block  
   e.printStackTrace();  
  } catch (IOException e) {  
   // TODO Auto-generated catch block  
   e.printStackTrace();  
  }  
  if (rawScreen != null) {  
   Boolean landscape = false;  
   int width2 = landscape ? rawScreen.height : rawScreen.width;  
   int height2 = landscape ? rawScreen.width : rawScreen.height;  
   if (image == null) {  
    image = new BufferedImage(width2, height2,  
      BufferedImage.TYPE_INT_RGB);  
   } else {  
    if (image.getHeight() != height2 || image.getWidth() != width2) {  
     image = new BufferedImage(width2, height2,  
       BufferedImage.TYPE_INT_RGB);  
    }  
   }  
   int index = 0;  
   int indexInc = rawScreen.bpp >> 3;  
   for (int y = 0; y < rawScreen.height; y++) {  
    for (int x = 0; x < rawScreen.width; x++, index += indexInc) {  
     int value = rawScreen.getARGB(index);  
     if (landscape)  
      image.setRGB(y, rawScreen.width - x - 1, value);  
     else  
      image.setRGB(x, y, value);  
    }  
   }  
   try {  
    ImageIO.write((RenderedImage) image, "PNG", new File("D:/"  
      + filename + ".jpg"));  
   } catch (IOException e) {  
    // TODO Auto-generated catch block  
    e.printStackTrace();  
   }  
  }  
 }  
  
 /** 
  * 获取得到device对象 
  * @return 
  */  
 private IDevice getDevice(){  
  IDevice device;  
  AndroidDebugBridge bridge = AndroidDebugBridge  
    .createBridge("adb", true);//如果代码有问题请查看API,修改此处的参数值试一下  
  waitDevicesList(bridge);  
  IDevice devices[] = bridge.getDevices();  
  device = devices[0];  
  return device;  
 }  
   
 /** 
  * 等待查找device 
  * @param bridge 
  */  
 private void waitDevicesList(AndroidDebugBridge bridge) {  
  int count = 0;  
  while (bridge.hasInitialDeviceList() == false) {  
   try {  
    Thread.sleep(500);   
    count++;  
   } catch (InterruptedException e) {  
   }  
   if (count > 240) {  
    System.err.print("等待获取设备超时");  
    break;  
   }  
  }  
 }  
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("con_vertToRgba32Exception=" + exception);
      }
    }
    
}

(2)

 

[java] view plaincopy
首先是直接移植SystemUI的代码,实现截图效果,这部分的代码就不贴出来了,直接去下载代码吧, 关键的代码没有几句,最最主要的是:Surface.screenshot(),请看代码吧。  
  
[java]  
<SPAN style="FONT-SIZE: 16px">package org.winplus.ss;   
   
import java.io.File;   
import java.io.FileNotFoundException;   
import java.io.FileOutputStream;   
import java.io.IOException;   
import java.text.SimpleDateFormat;   
import java.util.Date;   
   
import android.app.Activity;   
import android.content.Context;   
import android.graphics.Bitmap;   
import android.graphics.Canvas;   
import android.graphics.Matrix;   
import android.os.Bundle;   
import android.util.DisplayMetrics;   
import android.util.Log;   
import android.view.Display;   
import android.view.Surface;   
import android.view.WindowManager;   
import android.os.SystemProperties;   
   
public class SimpleScreenshotActivity extends Activity {   
   
    private Display mDisplay;   
    private WindowManager mWindowManager;   
    private DisplayMetrics mDisplayMetrics;   
    private Bitmap mScreenBitmap;   
    private Matrix mDisplayMatrix;   
   
    @Override   
    public void onCreate(Bundle savedInstanceState) {   
        super.onCreate(savedInstanceState);   
        setContentView(R.layout.main);   
   
        new Thread(new Runnable() {   
   
          @Override   
          public void run() {   
          takeScreenshot();   
   
          }   
        }).start();   
    }   
   
    private float getDegreesForRotation(int value) {   
        switch (value) {   
        case Surface.ROTATION_90:   
          return 360f - 90f;   
        case Surface.ROTATION_180:   
          return 360f - 180f;   
        case Surface.ROTATION_270:   
          return 360f - 270f;   
        }   
        return 0f;   
    }   
   
    private void takeScreenshot() {   
        mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);   
        mDisplay = mWindowManager.getDefaultDisplay();   
        mDisplayMetrics = new DisplayMetrics();   
        mDisplay.getRealMetrics(mDisplayMetrics);   
        mDisplayMatrix = new Matrix();   
        float[] dims = { mDisplayMetrics.widthPixels,   
          mDisplayMetrics.heightPixels };   
   
        int value = mDisplay.getRotation();   
        String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");   
        if (hwRotation.equals("270") || hwRotation.equals("90")) {   
          value = (value + 3) % 4;   
        }   
        float degrees = getDegreesForRotation(value);   
   
        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]);   
        }   
   
        mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);   
   
        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;   
        }   
   
        // If we couldn't take the screenshot, notify the user    
        if (mScreenBitmap == null) {   
          return;   
        }   
   
        // Optimizations    
        mScreenBitmap.setHasAlpha(false);   
        mScreenBitmap.prepareToDraw();   
          
        try {   
          saveBitmap(mScreenBitmap);   
        } catch (IOException e) {   
          System.out.println(e.getMessage());   
        }   
    }   
   
    public void saveBitmap(Bitmap bitmap) throws IOException {   
        String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")   
          .format(new Date(System.currentTimeMillis()));   
        File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");   
        if(!file.exists()){   
          file.createNewFile();   
        }   
        FileOutputStream out;   
        try {   
          out = new FileOutputStream(file);   
          if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {   
          out.flush();   
          out.close();   
          }   
        } catch (FileNotFoundException e) {   
          e.printStackTrace();   
        } catch (IOException e) {   
          e.printStackTrace();   
        }   
    }   
}   
</SPAN>   
  
package org.winplus.ss;  
  
import java.io.File;  
import java.io.FileNotFoundException;  
import java.io.FileOutputStream;  
import java.io.IOException;  
import java.text.SimpleDateFormat;  
import java.util.Date;  
  
import android.app.Activity;  
import android.content.Context;  
import android.graphics.Bitmap;  
import android.graphics.Canvas;  
import android.graphics.Matrix;  
import android.os.Bundle;  
import android.util.DisplayMetrics;  
import android.util.Log;  
import android.view.Display;  
import android.view.Surface;  
import android.view.WindowManager;  
import android.os.SystemProperties;  
  
public class SimpleScreenshotActivity extends Activity {  
  
 private Display mDisplay;  
 private WindowManager mWindowManager;  
 private DisplayMetrics mDisplayMetrics;  
 private Bitmap mScreenBitmap;  
 private Matrix mDisplayMatrix;  
  
 @Override  
 public void onCreate(Bundle savedInstanceState) {  
  super.onCreate(savedInstanceState);  
  setContentView(R.layout.main);  
  
  new Thread(new Runnable() {  
  
   @Override  
   public void run() {  
    takeScreenshot();  
  
   }  
  }).start();  
 }  
  
 private float getDegreesForRotation(int value) {  
  switch (value) {  
  case Surface.ROTATION_90:  
   return 360f - 90f;  
  case Surface.ROTATION_180:  
   return 360f - 180f;  
  case Surface.ROTATION_270:  
   return 360f - 270f;  
  }  
  return 0f;  
 }  
  
 private void takeScreenshot() {  
  mWindowManager = (WindowManager) getSystemService(Context.WINDOW_SERVICE);  
  mDisplay = mWindowManager.getDefaultDisplay();  
  mDisplayMetrics = new DisplayMetrics();  
  mDisplay.getRealMetrics(mDisplayMetrics);  
  mDisplayMatrix = new Matrix();  
  float[] dims = { mDisplayMetrics.widthPixels,  
    mDisplayMetrics.heightPixels };  
  
  int value = mDisplay.getRotation();  
  String hwRotation = SystemProperties.get("ro.sf.hwrotation", "0");  
  if (hwRotation.equals("270") || hwRotation.equals("90")) {  
   value = (value + 3) % 4;  
  }  
  float degrees = getDegreesForRotation(value);  
  
  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]);  
  }  
  
  mScreenBitmap = Surface.screenshot((int) dims[0], (int) dims[1]);  
  
  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;  
        }  
  
        // If we couldn't take the screenshot, notify the user  
        if (mScreenBitmap == null) {  
          return;  
        }  
  
        // Optimizations  
        mScreenBitmap.setHasAlpha(false);  
        mScreenBitmap.prepareToDraw();  
         
  try {  
   saveBitmap(mScreenBitmap);  
  } catch (IOException e) {  
   System.out.println(e.getMessage());  
  }  
 }  
  
 public void saveBitmap(Bitmap bitmap) throws IOException {  
  String imageDate = new SimpleDateFormat("yyyy-MM-dd-HH-mm-ss")  
    .format(new Date(System.currentTimeMillis()));  
  File file = new File("/mnt/sdcard/Pictures/"+imageDate+".png");  
  if(!file.exists()){  
   file.createNewFile();  
  }  
  FileOutputStream out;  
  try {  
   out = new FileOutputStream(file);  
   if (bitmap.compress(Bitmap.CompressFormat.PNG, 70, out)) {  
    out.flush();  
    out.close();  
   }  
  } catch (FileNotFoundException e) {  
   e.printStackTrace();  
  } catch (IOException e) {  
   e.printStackTrace();  
  }  
 }  
}  
  
PS:1、需要在AndroidManifest.xml中加入代码:android:sharedUserId="android.uid.system"  
  
         2、由于调用了@hide的API,所以编译得时候请使用makefile编译。或者通过在Eclipse中添加Jar文件通过编译。  
  
         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()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这 个函数中我们会看到这么两段代码:

.......
 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()中,那么接下来看看这个函数干了什么工作:
    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毫秒之后,会进入如下函数:
    private final Runnable mScreenshotChordLongPress = new Runnable() {
        public void run() {
          takeScreenshot();
        }
    };
在这里启动了一个线程来完成截屏的功能,接着看函数takeScreenshot():
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,看看其实现的流程:
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中(跟之前的函数重名但是文件路径不一样):

 /**
     * 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方法了:
    /**
     * 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++语言了,统一调用的底层函数是:
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源码中通过组合键屏幕截图的整个流程有个大体了解了,一般我们在改动中熟悉按键的捕获原理,并且清楚调用的截屏函数接口即可,如果有兴趣的,可以继续探究更深的底层是如何实现的。
----------------------------------------------
(C)(P)Flying Wang
作者:
男 greenzyzyzy (小张) ▲▲▲▲△ -
普通会员
2018/6/27 13:33:37
4楼: 好像确实如楼上老大说的,要ROOT,我的是移动定制机,不知道,能不能ROOT
之前下的那个RE管理器和RE文件管理器是两个不一样的东西,后者才是真的,
后者提示要ROOT才能访问。有时间再试一下,谢谢了。
----------------------------------------------
-
信息
登陆以后才能回复
Copyright © 2CCC.Com 盒子论坛 v3.0.1 版权所有 页面执行157.7148毫秒 RSS