1. 顯示層(Layer)和屏幕組成
你了解屏幕顯示的漂亮界面是如何組織的嗎?來看圖8-10所展示的屏幕組成示意圖:
:-: 
圖8-10 屏幕組成示意圖
從圖8-10中可以看出:
- 屏幕位于一個三維坐標系中,其中Z軸從屏幕內指向屏幕外。
- 編號為①②③的矩形塊叫顯示層(Layer)。每一層有自己的屬性,例如顏色、透明度、所處屏幕的位置、寬、高等。除了屬性之外,每一層還有自己對應的顯示內容,也就是需要顯示的圖像。
在Android中,Surface系統工作時,會由SurfaceFlinger對這些按照Z軸排好序的顯示層進行圖像混合,混合后的圖像就是在屏幕上看到的美妙畫面了。這種按Z軸排序的方式符合我們在日常生活中的體驗,例如前面的物體會遮擋住后面的物體。
>[info] **注意**,Surface系統中定義了一個名為Layer類型的類,為了區分廣義概念上的Layer和代碼中的Layer,這里稱廣義層的Layer為顯示層,以免混淆。
Surface系統提供了三種屬性,一共四種不同的顯示層。簡單介紹一下:
- 第一種屬性是eFXSurfaceNormal屬性,大多數的UI界面使用的就是這種屬性。它有兩種模式:
1)Normal模式,這種模式的數據,是通過前面的mView.draw(canvas)畫上去的。這也是絕大多數UI所采用的方式。
2)PushBuffer模式,這種模式對應于視頻播放、攝像機攝錄/預覽等應用場景。以攝像機為例,當攝像機運行時,來自Camera的預覽數據直接push到Buffer中,無須應用層自己再去draw了。
- 第二種屬性是eFXSurfaceBlur屬性,這種屬性的UI有點朦朧美,看起來很像隔著一層毛玻璃。
- 第三種屬性是eFXSurfaceDim屬性,這種屬性的UI看起來有點暗,好像隔了一層深色玻璃。從視覺上講,雖然它的UI看起來有點暗,但并不模糊。而eFXSurfaceBlur不僅暗,還有些模糊。
圖8-11展示了最后兩種類型的視覺效果圖,其中第一幅圖是Blur模式,第二幅圖是Dim模式。
:-: 
:-: 
圖8-11 Blur和Dim效果圖
注意,關于Surface系統的顯示層屬性定義,讀者可參考ISurfaceComposer.h。
本章將重點分析第一種屬性的兩類顯示層的工作原理。
2. FrameBuffer和PageFlipping
我們知道,在Audio系統中,音頻數據傳輸的過程是:
- 由客戶端把數據寫到共享內存中。
- 然后由AudioFlinger從共享內存中取出數據再往Audio HAL中發送。
根據以上介紹可知,在音頻數據傳輸的過程中,共享內存起到了數據承載的重要作用。
無獨有偶,Surface系統中的數據傳輸也存在同樣的過程,但承載圖像數據的是鼎鼎大名的FrameBuffer(簡稱FB)。下面先來介紹FrameBuffer,然后再介紹Surface的數據傳輸過程。
(1)FrameBuffer的介紹
FrameBuffer的中文名叫幀緩沖,它實際上包括兩個不同的方面:
- Frame:幀,就是指一幅圖像。在屏幕上看到的那幅圖像就是一幀。
- Buffer:緩沖,就是一段存儲區域,可這個區域存儲的是幀。
FrameBuffer的概念很清晰,它就是一個存儲圖形/圖像幀數據的緩沖。這個緩沖來自哪里?理解這個問題,需要簡單介紹一下Linux平臺的虛擬顯示設備FrameBuffer Device(簡稱FBD)。FBD是Linux系統中的一個虛擬設備,設備文件對應為/dev/fb%d(比如/dev/fb0)。這個虛擬設備將不同硬件廠商實現的真實設備統一在一個框架下,這樣應用層就可以通過標準的接口進行圖形/圖像的輸入和輸出了。圖8-12展示了FBD示意圖:
:-: 
圖8-12 Linux系統中的FBD示意圖
從上圖中可以看出,應用層通過標準的ioctl或mmap等系統調用,就可以操作顯示設備,用起來非常方便。這里,把mmap的調用列出來,相信大部分讀者都知道它的作用了。
FrameBuffer中的Buffer,就是通過mmap把設備中的顯存映射到用戶空間的,在這塊緩沖上寫數據,就相當于在屏幕上繪畫。
>[info] **注意**:上面所說的框架將引出另外一個概念Linux FrameBuffer(簡稱LFB)。LFB是Linux平臺提供的一種可直接操作FB的機制,依托這個機制,應用層通過標準的系統調用,就可以操作顯示設備了。從使用的角度來看,它和Linux Audio中的OSS有些類似。
為加深讀者對此節內容的理解,這里給出一個小例子,就是在DDMS工具中實現屏幕截圖功能,其代碼在framebuffer_service.c中,如下所示:
**framebuffer_service.c**
~~~
struct fbinfo {//定義一個結構體
unsigned int version;
unsigned int bpp;
unsigned int size;
unsigned int width;
unsigned int height;
unsigned int red_offset;
unsigned int red_length;
unsigned int blue_offset;
unsigned int blue_length;
unsigned int green_offset;
unsigned int green_length;
unsigned int alpha_offset;
unsigned int alpha_length;
} __attribute__((packed));
//fd是一個文件的描述符,這個函數的目的,是把當前屏幕的內容寫到一個文件中
void framebuffer_service(int fd, void *cookie)
{
structfb_var_screeninfo vinfo;
intfb, offset;
charx[256];
structfbinfo fbinfo;
unsigned i, bytespp;
//Android系統上的fb設備路徑在/dev/graphics目錄下
fb =open("/dev/graphics/fb0", O_RDONLY);
if(fb< 0) goto done;
//取出屏幕的屬性
if(ioctl(fb, FBIOGET_VSCREENINFO, &vinfo) < 0) goto done;
fcntl(fb, F_SETFD, FD_CLOEXEC);
bytespp = vinfo.bits_per_pixel / 8;
//根據屏幕的屬性填充fbinfo結構,這個結構要寫到輸出文件的頭部
fbinfo.version = DDMS_RAWIMAGE_VERSION;
fbinfo.bpp = vinfo.bits_per_pixel;
fbinfo.size = vinfo.xres * vinfo.yres * bytespp;
fbinfo.width = vinfo.xres;
fbinfo.height = vinfo.yres;
/*
下面幾個變量和顏色格式有關,以RGB565為例,簡單介紹一下。
RGB565表示一個像素點中R分量為5位,G分量為6位,B分量為5位,并且沒有Alpha分量。
這樣一個像素點的大小為16位,占兩個字節,比RGB888格式的一個像素少一個字節(它一個像素是三個字節)。
x_length的值為x分量的位數,例如,RGB565中R分量就是5位。
x_offset的值代表x分量在內存中的位置。如RGB565一個像素占兩個字節,那么x_offeset
表示x分量在這兩個字節內存區域中的起始位置,但這個順序是反的,也就是B分量在前,
R在最后。所以red_offset的值就是11,而blue_offset的值是0,green_offset的值是6。
這些信息在做格式轉換時(例如從RGB565轉到RGB888的時候)有用。
*/
fbinfo.red_offset = vinfo.red.offset;
fbinfo.red_length = vinfo.red.length;
fbinfo.green_offset = vinfo.green.offset;
fbinfo.green_length = vinfo.green.length;
fbinfo.blue_offset = vinfo.blue.offset;
fbinfo.blue_length = vinfo.blue.length;
fbinfo.alpha_offset = vinfo.transp.offset;
fbinfo.alpha_length = vinfo.transp.length;
offset= vinfo.xoffset * bytespp;
offset+= vinfo.xres * vinfo.yoffset * bytespp;
//將fb信息寫到文件頭部
if(writex(fd, &fbinfo, sizeof(fbinfo))) goto done;
lseek(fb, offset, SEEK_SET);
for(i= 0; i < fbinfo.size; i += 256) {
if(readx(fb, &x, 256)) goto done;//讀取FBD中的數據
if(writex(fd, &x, 256)) goto done;//將數據寫到文件
}
if(readx(fb, &x, fbinfo.size % 256)) goto done;
if(writex(fd, &x, fbinfo.size % 256)) goto done;
done:
if(fb>= 0) close(fb);
close(fd);
}
~~~
上面函數的目的就是截屏,這個例子可加深我們對FB的直觀感受,相信讀者下次再碰到FB時就不會犯怵了。
* * * * *
**注意**:我們可根據這段代碼,寫一個簡單的Native可執行程序,然后adb push到設備上運行。注意上面寫到文件中的是RGB565格式的原始數據,如想在臺式機上看到這幅圖片,可將它轉換成BMP格式。我的個人博客上提供一個RGB565轉BMP的程序,讀者可以下載或自己另寫一個,這樣或許有助于更深入理解圖形/圖像方面的知識。
* * * * *
在繼續分析前,先來問一個問題:
**前面在Audio系統中講過,CB對象通過讀寫指針來協調生產者/消費者的步調,那么Surface系統中的數據傳輸過程,是否也需通過讀寫指針來控制呢?**
答案是肯定的,但不像Audio中的CB那樣復雜。
(2)PageFlipping
圖形/圖像數據和音頻數據不太一樣,我們一般把音頻數據叫音頻流,它是沒有邊界的, 而圖形/圖像數據是一幀一幀的,是有邊界的。這一點非常類似UDP和TCP之間的區別。所以在圖形/圖像數據的生產/消費過程中,人們使用了一種叫PageFlipping的技術。
PageFlipping的中文名叫畫面交換,其操作過程如下所示:
- 分配一個能容納兩幀數據的緩沖,前面一個緩沖叫FrontBuffer,后面一個緩沖叫BackBuffer。
- 消費者使用FrontBuffer中的舊數據,而生產者用新數據填充BackBuffer,二者互不干擾。
- 當需要更新顯示時,BackBuffer變成FrontBuffer,FrontBuffer變成BackBuffer。如此循環,這樣就總能顯示最新的內容了。這個過程很像我們平常的翻書動作,所以它被形象地稱為PageFlipping。
說白了,PageFlipping其實就是使用了一個只有兩個成員的幀緩沖隊列,以后在分析數據傳輸的時候還會見到諸如dequeue和queue的操作。
3. 圖像混合
我們知道,在AudioFlinger中有混音線程,它能將來自多個數據源的數據混合后輸出,那么,SurfaceFlinger是不是也具有同樣的功能呢?
答案是肯定的,否則它就不會叫Flinger了。Surface系統支持軟硬兩個層面的圖像混合:
- 軟件層面的混合:例如使用copyBlt進行源數據和目標數據的混合。
- 硬件層面的混合:使用Overlay系統提供的接口。
無論是硬件還是軟件層面,都需將源數據和目標數據進行混合,混合需考慮很多內容,例如源的顏色和目標的顏色疊加后所產生的顏色。關于這方面的知識,讀者可以學習計算機圖形/圖像學。這里只簡單介紹一下copyBlt和Overlay。
- copyBlt,從名字上看,是數據拷貝,它也可以由硬件實現,例如現在很多的2D圖形加速就是將copyBlt改由硬件來實現,以提高速度的。但不必關心這些,我們只需關心如何調用copyBlt相關的函數進行數據混合即可。
- Overlay方法必須有硬件支持才可以,它主要用于視頻的輸出,例如視頻播放、攝像機攝像等,因為視頻的內容往往變化很快,所以如改用硬件進行混合效率會更高。
總體來說,Surface是一個比較龐大的系統,由于篇幅和精力所限,本章后面的內容將重點關注Surface系統的框架和工作流程。在掌握框架和流程后,讀者就可以在大的脈絡中迅速定位到自己感興趣的地方,然后展開更深入的研究了。
下面通過圖8-9所示的精簡流程,深入分析Android的Surface系統。
- 前言
- 第1章 閱讀前的準備工作
- 1.1 系統架構
- 1.1.1 Android系統架構
- 1.1.2 本書的架構
- 1.2 搭建開發環境
- 1.2.1 下載源碼
- 1.2.2 編譯源碼
- 1.3 工具介紹
- 1.3.1 Source Insight介紹
- 1.3.2 Busybox的使用
- 1.4 本章小結
- 第2章 深入理解JNI
- 2.1 JNI概述
- 2.2 學習JNI的實例:MediaScanner
- 2.3 Java層的MediaScanner分析
- 2.3.1 加載JNI庫
- 2.3.2 Java的native函數和總結
- 2.4 JNI層MediaScanner的分析
- 2.4.1 注冊JNI函數
- 2.4.2 數據類型轉換
- 2.4.3 JNIEnv介紹
- 2.4.4 通過JNIEnv操作jobject
- 2.4.5 jstring介紹
- 2.4.6 JNI類型簽名介紹
- 2.4.7 垃圾回收
- 2.4.8 JNI中的異常處理
- 2.5 本章小結
- 第3章 深入理解init
- 3.1 概述
- 3.2 init分析
- 3.2.1 解析配置文件
- 3.2.2 解析service
- 3.2.3 init控制service
- 3.2.4 屬性服務
- 3.3 本章小結
- 第4章 深入理解zygote
- 4.1 概述
- 4.2 zygote分析
- 4.2.1 AppRuntime分析
- 4.2.2 Welcome to Java World
- 4.2.3 關于zygote的總結
- 4.3 SystemServer分析
- 4.3.1 SystemServer的誕生
- 4.3.2 SystemServer的重要使命
- 4.3.3 關于 SystemServer的總結
- 4.4 zygote的分裂
- 4.4.1 ActivityManagerService發送請求
- 4.4.2 有求必應之響應請求
- 4.4.3 關于zygote分裂的總結
- 4.5 拓展思考
- 4.5.1 虛擬機heapsize的限制
- 4.5.2 開機速度優化
- 4.5.3 Watchdog分析
- 4.6 本章小結
- 第5章 深入理解常見類
- 5.1 概述
- 5.2 以“三板斧”揭秘RefBase、sp和wp
- 5.2.1 第一板斧--初識影子對象
- 5.2.2 第二板斧--由弱生強
- 5.2.3 第三板斧--破解生死魔咒
- 5.2.4 輕量級的引用計數控制類LightRefBase
- 5.2.5 題外話-三板斧的來歷
- 5.3 Thread類及常用同步類分析
- 5.3.1 一個變量引發的思考
- 5.3.2 常用同步類
- 5.4 Looper和Handler類分析
- 5.4.1 Looper類分析
- 5.4.2 Handler分析
- 5.4.3 Looper和Handler的同步關系
- 5.4.4 HandlerThread介紹
- 5.5 本章小結
- 第6章 深入理解Binder
- 6.1 概述
- 6.2 庖丁解MediaServer
- 6.2.1 MediaServer的入口函數
- 6.2.2 獨一無二的ProcessState
- 6.2.3 時空穿越魔術-defaultServiceManager
- 6.2.4 注冊MediaPlayerService
- 6.2.5 秋風掃落葉-StartThread Pool和join Thread Pool分析
- 6.2.6 你徹底明白了嗎
- 6.3 服務總管ServiceManager
- 6.3.1 ServiceManager的原理
- 6.3.2 服務的注冊
- 6.3.3 ServiceManager存在的意義
- 6.4 MediaPlayerService和它的Client
- 6.4.1 查詢ServiceManager
- 6.4.2 子承父業
- 6.5 拓展思考
- 6.5.1 Binder和線程的關系
- 6.5.2 有人情味的訃告
- 6.5.3 匿名Service
- 6.6 學以致用
- 6.6.1 純Native的Service
- 6.6.2 扶得起的“阿斗”(aidl)
- 6.7 本章小結
- 第7章 深入理解Audio系統
- 7.1 概述
- 7.2 AudioTrack的破解
- 7.2.1 用例介紹
- 7.2.2 AudioTrack(Java空間)分析
- 7.2.3 AudioTrack(Native空間)分析
- 7.2.4 關于AudioTrack的總結
- 7.3 AudioFlinger的破解
- 7.3.1 AudioFlinger的誕生
- 7.3.2 通過流程分析AudioFlinger
- 7.3.3 audio_track_cblk_t分析
- 7.3.4 關于AudioFlinger的總結
- 7.4 AudioPolicyService的破解
- 7.4.1 AudioPolicyService的創建
- 7.4.2 重回AudioTrack
- 7.4.3 聲音路由切換實例分析
- 7.4.4 關于AudioPolicy的總結
- 7.5 拓展思考
- 7.5.1 DuplicatingThread破解
- 7.5.2 題外話
- 7.6 本章小結
- 第8章 深入理解Surface系統
- 8.1 概述
- 8.2 一個Activity的顯示
- 8.2.1 Activity的創建
- 8.2.2 Activity的UI繪制
- 8.2.3 關于Activity的總結
- 8.3 初識Surface
- 8.3.1 和Surface有關的流程總結
- 8.3.2 Surface之乾坤大挪移
- 8.3.3 乾坤大挪移的JNI層分析
- 8.3.4 Surface和畫圖
- 8.3.5 初識Surface小結
- 8.4 深入分析Surface
- 8.4.1 與Surface相關的基礎知識介紹
- 8.4.2 SurfaceComposerClient分析
- 8.4.3 SurfaceControl分析
- 8.4.4 writeToParcel和Surface對象的創建
- 8.4.5 lockCanvas和unlockCanvasAndPost分析
- 8.4.6 GraphicBuffer介紹
- 8.4.7 深入分析Surface的總結
- 8.5 SurfaceFlinger分析
- 8.5.1 SurfaceFlinger的誕生
- 8.5.2 SF工作線程分析
- 8.5.3 Transaction分析
- 8.5.4 關于SurfaceFlinger的總結
- 8.6 拓展思考
- 8.6.1 Surface系統的CB對象分析
- 8.6.2 ViewRoot的你問我答
- 8.6.3 LayerBuffer分析
- 8.7 本章小結
- 第9章 深入理解Vold和Rild
- 9.1 概述
- 9.2 Vold的原理與機制分析
- 9.2.1 Netlink和Uevent介紹
- 9.2.2 初識Vold
- 9.2.3 NetlinkManager模塊分析
- 9.2.4 VolumeManager模塊分析
- 9.2.5 CommandListener模塊分析
- 9.2.6 Vold實例分析
- 9.2.7 關于Vold的總結
- 9.3 Rild的原理與機制分析
- 9.3.1 初識Rild
- 9.3.2 RIL_startEventLoop分析
- 9.3.3 RIL_Init分析
- 9.3.4 RIL_register分析
- 9.3.5 關于Rild main函數的總結
- 9.3.6 Rild實例分析
- 9.3.7 關于Rild的總結
- 9.4 拓展思考
- 9.4.1 嵌入式系統的存儲知識介紹
- 9.4.2 Rild和Phone的改進探討
- 9.5 本章小結
- 第10章 深入理解MediaScanner
- 10.1 概述
- 10.2 android.process.media分析
- 10.2.1 MSR模塊分析
- 10.2.2 MSS模塊分析
- 10.2.3 android.process.media媒體掃描工作的流程總結
- 10.3 MediaScanner分析
- 10.3.1 Java層分析
- 10.3.2 JNI層分析
- 10.3.3 PVMediaScanner分析
- 10.3.4 關于MediaScanner的總結
- 10.4 拓展思考
- 10.4.1 MediaScannerConnection介紹
- 10.4.2 我問你答
- 10.5 本章小結