NO IMAGE

最近由於專案需要,在學習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<ARM<x86/64)。隨著android的發展,提供的庫也在不斷增多。詳細的關於Native
API的資訊需要參考Android NDK Dev Guide文件中有關Stable API的部分。這一層還有一個最核心的東西,就是Dalvik虛擬機器。我想,android的核心就是這個虛擬機器(或者可以說:Java的核心就是虛擬機器?簡直是廢話!而虛擬機器的核心是Portable,即可移植)。

第三層由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 <stdio.h>
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