android截屏代码实现方法

最近由于项目需要,在学习android系统。android是一个基于linux的专门针对手机平台的操作系统。当然,现在的android 3似乎也将进入平板电脑的市场。由于至今为止,大部分的智能手机采用的是ARM的硬件平台,因此android本身对ARM的平台进行了全面的支持,从源代码中可以看出,也在逐步加入对x86平台的支持,暂时没有看到第三个平台的身影。

这篇文章是我对android系统认识的一个总结,同时介绍一下我至今为止发现的获取屏幕图像数据的方法。经过一定的搜索,我发现方法有很多种,而实现效果会有所差别。其中,有通过最顶层的Android SDK进行截屏,也可以通过C直接读取framebuffer实现。由于framebuffer的方法网上虽然有很多,但是很多我认为并没有写的十分清楚,特别是在编译的方法上,没有找到很好的文章,可能是因为自己找的不够,或者android本身就在不断发展。对此,我在最后给出了一个Android Native Programming的Hello World Howto,需要先到github上mirror下来Android的源代码(2.多G),里边将会带所有的编译器和Emulator等工具。

1 android之我的粗浅理解

android的版本号很有意思,一个版本号对应一个API Level(比如android 2.3.3则对应Android API Level 10)。写这篇文章的时候,最新的android版本号为3.1,而API Level为12。

学习android,首先需要了解的是其系统框架。如下图所示:

android操作系统框架图

这个图在网上比比皆是,这里还是贴了一下,因为的确可以比较好的描述这个系统。它的最底层是linux的内核,只有这一层是工作在内核级的,其余三层都是工作在用户级的。这里,android也不是使用的标准的linux内核,而是一个经过了裁剪,并添加了一些特定功能的内核。其中一个让我记得的比较有意思的feature是,android内核中会有一个叫做Low Memory Killer的驱动,它的主要功能是在系统缺少内存的情况下,杀死进程。显然,我们平时的电脑中,往往不是很需要这个东西;而在手机中,由于资源十分有限,缺少内存的现象或许会发生的非常频繁,这个功能似乎就显得十分重要了(或许还有一个原因是手机没有虚拟内存?毕竟它是用Flash作为长期存储介质,而Flash的特性似乎并不适合作为虚拟内存来使用)。我想这是android针对手机定制进行优化的一个很好的例子。

第二层就是所谓的Native层,由C/C++实现。如果熟悉ARM的应用程序开发的话,我理解这一层就是ARM平台的应用开发层。我们仍然可以用一个类似arm-linux-gcc的交叉编译工具对C程序进行编译(这里是arm-linux-androideabi-gcc)进行编译,具体的方法一会详细介绍。而在这一层,系统给我们提供的库可是比较少的,往往就是最基本的libc等(从提供的可用C基础库来看,数量上肯定是android

第三层由Java实现,是Android SDK的核心,是android最上层的框架。所有的Android SDK的接口我想就是在这一层实现的了。

最上面就不说了,我们基于android SDK使用Java开发出来的东西应该都在这了。

因为我之前对通用ARM平台有一定的了解,而android也是一个ARM平台,但是比较特殊。让我用我现在的理解来对android总结一下的话:Android=(ARM平台)+(针对手机平台的定制)+(Dalvik虚拟机核心 & Android SDK)。

2 开发平台搭建

在android平台下,我想有三个开发内容。第一个,就是最好入门、最常见的应用程序开发了(也就是我们所说的基于SDK的开发),我们需要搭建一个SDK的环境,还需要懂得Java语言。如何搭建我就不说了,来看看这里吧!另外两个,一个就是ative代码的开发(比如,用C写一个native库,通过JNI给Java调用,或者直接写一个native application),还一个就是牛人们才能搞定的android源代码开发了(我短期恐怕是入不了门了),这些工作都最好搞到android的source tree比较好(这里也包括了所谓的NDK,也就是Native Development Kit)。方法我也不说了,来看这里!

基本的实验方法,SDK在上面的developer网站上写的很详细,有一个HelloWorld的demo,走一遍肯定就知道了。一会我会给出一个利用NDK开发的helloworld。

3 截屏方法种种

是的,让我们开始截屏吧!这里我截屏的环境都是在Android Emulator中(这个基于qemu的模拟器在Android SDK和NDK中都有提供)

3.1 基于Android SDK的截屏方法

主要就是利用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截屏文件。

这种截屏有一个问题,就是只能截到一部分,比如电池指示部分就截不出来了。

3.2 基于Android ddmlib进行截屏

这种方法网上有很多相关资料。我也没有测试过,此处略过。

3.3 Android本地编程(Native Programming)读取framebuffer

这是我现在使用的方法。它的优点是整个屏幕都可以截下来,同时不需要写JNI,也不需要Java层的实现。而且如果是emulator的话,也可以直接用adb来操作,十分方便(其实,有一个库android-screenshot-lib应该实现了类似的功能,但是我尝试了一下没有截图成功,图片大小不正确,且是黑屏。就没有进一步尝试了)。

3.3.1 Android的framebuffer介绍

framebuffer是linux内核对显示的最底层驱动。在一般的linux文件系统中,通过/dev/fb0设备文件来提供给应用程序对framebuffer进行读写的访问。这里,如果有多个显示设备,就将依次出现fb1,fb2,…等文件。而在我们所说的android系统中,这个设备文件被放在了/dev/graphics/fb0,而且往往只有这一个。

3.3.2 framebuffer的读取

读取的方法很简单,就将/dev/graphics/fb0当作一般的文件读取即可。可以通过ioctl()方法获取图像的长宽,以及每一个pixel对应的数据量。在android系统中,采用的是rbg565的编码方式。这里编程的方法是C最基本的,难点主要是编译器的配置我会在第4节介绍一个Native的tutorial: hello world程序,这里会讲到编译的方法和具体配置。

3.3.3 在android emulator中利用命令直接进行截屏

这一篇文章有一个大致的流程。主要是用cat读取fb,后用ffmpeg转换编码的方法。此处略。

4 Android本地编程入门:”hello world!” again!

我简单介绍一下怎么在android上开发基本的C程序。如果做过ARM的C应用程序开发的话会发现,ARM一般情况下提供了十分完备的编译器,而android没有而已(android提供了完善的Java层开发工具,C的却不是那么完善)。

4.1 编写hello.c

这个太简单了,不是么?

#include
int main( void )
{
     printf ( "hello world!\n" );
     return 0;
}

4.2 编写Android的编译器配置文件make_android

在Android SDK中,并没有提供Android系统的C编译器。就算是在NDK中,也只是提供了ndk-build工具,用来编译native static/dynamic library。只有仔细翻阅NDK的手册(它的手册位于NDK根目录的doc/OVERVIEW.html,比较简略),才会发现有一个STANDALONE-TOOLCHAIN的页面,会提到单独编译C Level应用程序的方法。我这里提供一段简单的makefile,命名文件为make_android,用来配置CC宏:

# make_android: this is a sub makefile for android native compile
# you have to set ANDROID_VER and ANDROID_ROOT to your flavor to work
  
### these two things have to be set first!!!
ANDROID_VER=android-8
ANDROID_ROOT=/home/xzpeter/android
  
PLATFORM_DIR=${ANDROID_ROOT}/prebuilt/ndk/android-ndk-r4/platforms
SYSROOT=${PLATFORM_DIR}/${ANDROID_VER}/arch-arm
  
EABI_GCC=${ANDROID_ROOT}/prebuilt/linux-x86/toolchain/arm-linux-androideabi-4.4.x/bin/arm-linux-androideabi-gcc
  
CC=${EABI_GCC} --sysroot=${SYSROOT}

这里,ANDROID_ROOT和ANDROID_VER是需要针对自己的android source目录地址和android API level修改一下的。这里的android source是我用repo sync从github上mirror下来的android源代码。

4.3 编写Makefile

利用上面的make_android,写Makefile:

# to make x86 version of code, run: "make X86=1"
ifdef X86
CC=gcc
CLFAGS=-g
else
include make_android
endif
  
default: hello
  
hello: hello.o
  
clean:
   rm hello *.o

我们提供了X86和android两种编译方式,默认是android方式。

4.4 编译

可以用make X86=1先在本地编译一下,并运行./hello试试看。如果想编译android版本,先make clean一下,然后直接make就可以了。

4.5 在模拟器中运行

利用shell命令启动emulator并将文件放到目标模拟器上去:

emulator - avd my_avd # my_avd is my config name of avd
# wait for some time to boot up
adb push . / hello / data / hello
adb shell chmod 0755 / data / hello
adb shell . / data / hello

应该可以看到返回的”hello world!”字符串了。


转载地址为:

http://xzpeter.org/?p=229

你可能感兴趣的:(Android智能手机开发)