简单介绍:
截屏一般有三种方法:
1. 直接获取到一个view,然后通过View的Cache来获取一个bitmap对象,然后将bitmap对象写到图像文件。
2. ddmslib截屏,ddmslib是android内置的一个库,在pc端的截屏都是使用的这种方式,但在网上找不到使用这种方法的资料。
3. framebuffer截屏,网上流传的都是这种方法,不过只有root后的设备能使用
根据各种方法的特点,我们选择的是framebuffer的方式,View的方式也简单介绍一下。由于截屏需要一定的处理时间,而且需要在后台运行,用service来实现是比较好的选择。
View截屏的简单实现
使用view截屏的话首先要获取当前的界面的view
在Activity中获取view是非常容易的,但是仅限于当前Activity的view,这种方法截屏是有限制的,截取的图像没有状态栏。在Activity中截屏的代码如下:
取得bitmap:
View view = v.getRootView();
view.setDrawingCacheEnabled(true);
view.buildDrawingCache();
Bitmap bitmap = view.getDrawingCache();
然后将bitmap保存为png格式:
FileOutputStream out = new FileOutputStream(file_name);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
framebuffer截屏的实现
framebuffer是linux内核对显示的最底层驱动。在一般的linux文件系统中,通过/dev/fb0设备文件来提供给应用程序对framebuffer进行读写的访问。这里,如果有多个显示设备,就将依次出现fb1,fb2,…等文件。而在我们所说的android系统中,这个设备文件被放在了/dev/graphics/fb0,而且往往只有这一个。
这种方法使用的最为普遍,linux系统中经常使用这种方法实现截屏,一般步骤是:
1. 读取framebuffer
2. Framebuffer转换为bitmap
3. bitmap生成图像文件
读取framebuffer
在android上使用这种方法第一个难题是获取framebuffer,因为默认的配置中framebuffer的读取权限是“root”,而Apk的权限最高只能提升到“system”,framework工作的权限也是“system”,也因此网上提供的截屏软件都只能在root过的手机上使用。对拥有源码的人来说,最简单的方法是直接改变framebuffer的权限,普通用户也有权限读取framebuffer。
然后就可以通过读文件的方式直接读取framebuffer,利用如下代码打开一个指向framebuffer的输入流就可以读取了。
public static InputStream getInputStream() throws Exception {
FileInputStream buf = new FileInputStream(
new File("/dev/graphics/fb0"));
return buf;
}//get the InputStream from framebuffer
framebuffer到bitmap的转换
framebuffer的数据是直接送入显示设备的,这些数据没有文件头,而且由于framebuffer读到数据跟显示方式关系很大,在不同设备上framebuffer的大小和数据格式不一样,读取前确定framebuffer的数据格式。
获取屏幕大小:
DisplayMetrics metrics = new DisplayMetrics();
WindowManager WM = (WindowManager) mContext
.getSystemService(Context.WINDOW_SERVICE);
Display display = WM.getDefaultDisplay();
display.getMetrics(metrics);
int height = metrics.heightPixels; //屏幕高
int with = metrics.widthPixels; //屏幕的宽
获取显示方式
int pixelformat = display.getPixelFormat();
PixelFormat localPixelFormat1 = new PixelFormat();
PixelFormat.getPixelFormatInfo(pixelformat, localPixelFormat1);
int deepth = localPixelFormat1.bytesPerPixel;//位深
pixelformat代表的意义是在PixelFormat.java定义的:
public static final int RGBA_8888 = 1;
public static final int RGBX_8888 = 2;
public static final int RGB_888 = 3;
public static final int RGB_565 = 4;
public static final int RGBA_5551 = 6;
public static final int RGBA_4444 = 7;
public static final int A_8 = 8;
public static final int L_8 = 9;
public static final int LA_88 = 0xA;
public static final int RGB_332 = 0xB;
pixelformat是下面进行判断处理的依据,根据pixelformat计算出实际的深度。
一般
Framebuffer大小 = (height* with* deepth)*2;
之所以“*2”,是因为Framebuffer包含两帧画面,我们使用任何一帧都可以。
piex = new byte[height * with * deepth];
InputStream stream = getInputStream(
new File("/dev/graphics/fb0"));
DataInputStream dStream = new DataInputStream(stream);
dStream.readFully(piex);
这样framebuffer的数据就被写进了piex。
piex 生成bitmap
bitmap = Bitmap.createBitmap(data, mwidth, mheight, config);
data:像素数据,data的一个元素表示一个像素,所以这里不能直接使用piex,必须经过转换。
mwidth, mheight:图像大小
config:图像格式,可以取以下数值
Bitmap.Config.ALPHA_8 (2),
Bitmap.Config.RGB_565 (4),
Bitmap.Config.ARGB_4444 (5),
Bitmap.Config.ARGB_8888 (6);
bitmap保存为png格式:
这是跟上面一样的
FileOutputStream out = new FileOutputStream(file_name);
bitmap.compress(Bitmap.CompressFormat.PNG, 100, out);
这样截取的一幅图片就被保存到了固定位置。
快捷键的添加
由于快捷键是全局的,所以要在PhoneWindowManager中添加
public int interceptKeyBeforeQueueing(
long whenNanos, int action, int flags,
int keyCode, int scanCode, int policyFlags,
boolean isScreenOn)
这个函数是在按键事件进入消息队列之前调用的,所以先与所有app相应事件。但是在这个函数对于每个按键事件是单独处理的,组合键需要我们自己来识别。
下图分别是power键和volumedown键的按键处理,黄色区域为新增加代码。由于现在power键的长按和短按都是有事件响应的,所以设计的关键在于必须在长按和短按的间隙处理截屏事件,而且截屏后保证power的长按不在响应。
service设计:
这个服务的功能比较单一,只在启动时响应请求就可以,不需要驻留在后台,也不需要调用接口,所以不需要AIDL,也不需要远程连接。
客户端,使用intent启动服务
Intent intent = new Intent();
intent.setAction("com.android.ScreenServer.SHOT");
mContext.startService(intent);
在应用程序端
声明权限使用SD卡
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
Intentfilter过滤事件
<application
android:icon="@drawable/ic_launcher"
android:label="@string/app_name" >
<service
android:name="shotservice" >
<intent-filter >
<action android:name="com.android.ScreenServer.SHOT"/>
intent-filter>
service>
application>
程序代码:
public void onStart(Intent intent, int startId) {
// TODO Auto-generated method stub
super.onStart(intent, startId);
mContext = this;
。。。
使用framebuffer截屏
。。。
}
这样截屏结束后,Service的生命周期就结束了,系统会自动销毁Service。