(1)主要就是利用SDK提供的View.getDrawingCache()方法。网上已经有很多的实例了。首先创建一个android project,然后进行Layout,画一个按键(res/layout/main.xml):
<?
xml
version
=
"1.0"
encoding
=
"utf-8"
?>
<
LinearLayout
xmlns: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实现代码为:
package
com.example.helloandroid;
import
java.io.FileOutputStream;
import
java.text.SimpleDateFormat;
import
java.util.Date;
import
java.util.Locale;
import
android.app.Activity;
import
android.graphics.Bitmap;
import
android.os.Bundle;
import
android.view.View;
import
android.view.View.OnClickListener;
import
android.widget.Button;
public
class
HelloAndroid
extends
Activity {
private
Button button;
/** Called when the activity is first created. */
@Override
public
void
onCreate(Bundle savedInstanceState) {
super
.onCreate(savedInstanceState);
this
.setContentView(R.layout.main);
this
.button = (Button)
this
.findViewById(R.id.my_button);
this
.button.setOnClickListener(
new
OnClickListener() {
public
void
onClick(View v) {
SimpleDateFormat sdf =
new
SimpleDateFormat(
"yyyy-MM-dd_HH-mm-ss"
, Locale.US);
String fname =
"/sdcard/"
+ sdf.format(
new
Date()) +
".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 =
new
FileOutputStream(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” 命令
(1). 在AndroidManifest.xml文件中添加< uses-permission android:name = "android.permission.READ_FRAME_BUFFER" />(2). 修改APK为系统权限,将APK放到源码中编译, 修改Android.mkLOCAL_CERTIFICATE := platform
publicvoid takeScreenShot(){
String mSavedPath = Environment.getExternalStorageDirectory()+File. separator + "screenshot.png" ;
try {
Runtime. getRuntime().exec("screencap -p " + mSavedPath);
} catch (Exception e) {
e.printStackTrace();
}
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
;
}
|
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; } } }
(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命令来实现。
(2)
首先是直接移植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的就没去做测试了。
Android手机一般都自带有手机屏幕截图的功能:在手机任何界面(当然手机要是开机点亮状态),通过按组合键,屏幕闪一下,然后咔嚓一声,截图的照片会保存到当前手机的图库中,真是一个不错的功能!
以我手头的测试手机为例,是同时按电源键+音量下键来实现截屏,苹果手机则是电源键 + HOME键,小米手机是菜单键+音量下键,而HTC一般是按住电源键再按左下角的“主页”键。那么Android源码中使用组合键是如何实现屏幕截图功能呢?前段时间由于工作的原因仔细看了一下,这两天不忙,便把相关的知识点串联起来整理一下,分下面两部分简单分析下实现流程:
Android源码中对按键的捕获位于文件PhoneWindowManager.java(alps\frameworks\base\policy\src\com\android\internal\policy\impl)中,这个类处理所有的键盘输入事件,其中函数interceptKeyBeforeQueueing()会对常用的按键做特殊处理。以我手头的测试机为例,是同时按电源键和音量下键来截屏,那么在这个函数中我们会看到这么两段代码:
可以看到正是在这里(响应Down事件)捕获是否按了音量下键和电源键的,而且两个地方都会进入函数interceptScreenshotChord()中,那么接下来看看这个函数干了什么工作:
在这个函数中,用两个布尔变量判断是否同时按了音量下键和电源键后,再计算两个按键响应Down事件之间的时间差不超过150毫秒,也就认为是同时按了这两个键后,算是真正的捕获到屏幕截屏的组合键。
附言:文件PhoneWindowManager.java类是拦截键盘消息的处理类,在此类中还有对home键、返回键等好多按键的处理。
捕获到组合键后,我们再看看android源码中是如何调用屏幕截图的函数接口。在上面的函数interceptScreenshotChord中我们看到用handler判断长按组合键500毫秒之后,会进入如下函数:
在这里启动了一个线程来完成截屏的功能,接着看函数takeScreenshot():
可以看到这个函数使用AIDL绑定了service服务到"com.android.systemui.screenshot.TakeScreenshotService",注意在service连接成功时,对message的msg.arg1和msg.arg2两个参数的赋值。其中在mScreenshotTimeout中对服务service做了超时处理。接着我们找到实现这个服务service的类TakeScreenshotService,看看其实现的流程:
在这个类中,我们主要看调用接口,用到了mScreenshot.takeScreenshot()传递了三个参数,第一个是个runnable,第二和第三个是之前message传递的两个参数msg.arg1和msg.arg2。最后我们看看这个函数takeScreenshot(),位于文件GlobalScreenshot.java中(跟之前的函数重名但是文件路径不一样):
这段代码的注释比较详细,其实看到这里,我们算是真正看到截屏的操作了,具体的工作包括对屏幕大小、旋转角度的获取,然后调用Surface类的screenshot方法截屏保存到bitmap中,之后把这部分位图填充到一个画布上,最后再启动一个延迟的拍照动画效果。如果再往下探究screenshot方法,发现已经是一个native方法了:
使用JNI技术调用底层的代码,如果再往下走,会发现映射这这个jni函数在文件android_view_Surface.cpp中,这个真的已经是底层c++语言了,统一调用的底层函数是:
由于对C++不熟,我这里就不敢多言了。其实到这里,算是对手机android源码中通过组合键屏幕截图的整个流程有个大体了解了,一般我们在改动中熟悉按键的捕获原理,并且清楚调用的截屏函数接口即可,如果有兴趣的,可以继续探究更深的底层是如何实现的。
应用