[深入理解Android卷一全文-第八章]深入理解Surface系統
由於《深入理解Android 卷一》和《深入理解Android卷二》不再出版,而知識的傳播不應該因為紙質媒介的問題而中斷,所以我將在CSDN部落格中全文轉發這兩本書的全部內容。
第8章 深入理解Surface系統
本章主要內容
· 詳細分析一個Activity的顯示過程。
· 詳細分析Surface。
· 詳細分析SurfaceFlinger。
本章涉及的原始碼檔名及位置:
· ActivityThread.java
framework/base/core/java/android/app/ActivityThread.java
· Activity.java
framework/base/core/java/android/app/Activity.java
· Instrumentation.java
framework/base/core/java/android/app/Instrumentation.java
· PolicyManager.java
frameworks/policies/base/phone/com/android/internal/policy/impl/PolicyManager.java
· Policy.java
frameworks/policies/base/phone/com/android/internal/policy/impl/Policy.java
· PhoneWindow.java
frameworks/policies/base/phone/com/android/internal/policy/impl/PhoneWindow.java
· Window.java
framework/base/core/java/android/view/Window.java
· WindowManagerImpl
framework/ base/core/java/android/view/WindowManagerImpl.java
· ViewRoot.java
framework/base/core/java/android/view/ViewRoot.java
· Surface.java
framework/base/core/java/android/view/Surface.java
· WindowManagerService.java
framework/base/services/java/com/android/server/WindowManagerService.java
· IWindowSession.aidl
framework/base/core/java/android/view/IWindowSession.aidl
· IWindow.aidl
framework/base/core/java/android/view/IWindow.aidl
· SurfaceSession.java
framework/base/core/java/android/view/SurfaceSession.java
· android_view_Surface.cpp
framework/base/core/jni/android_view_Surface.cpp
· framebuffer_service.c
system/core/adb/framebuffer_service.c
· SurfaceComposerClient.cpp
framework/base/libs/surfaceflinger_client/SurfaceComposerClient.cpp
· SurfaceFlinger.cpp
framework/base/libs/surfaceflinger/SurfaceFlinger.cpp
· ISurfaceComposer.h
framework/base/include/surfaceflinger/ISurfaceComposer.h
· Layer.h
framework/base/include/surfaceflinger/Layer.h
· Layer.cpp
framework/base/libs/surfaceflinger/Layer.cpp
· LayerBase.cpp
framework/base/libs/surfaceflinger/LayerBase.cpp
· Surface.cpp
framework/base/libs/surfaceflinger_client/Surface.cpp
· SharedBufferStack.cpp
framework/base/libs/surfaceflinger_client/SharedBufferStack.cpp
· GraphicBuffer.h
framework/base/include/ui/GraphicBuffer.h
· GraphicBuffer.cpp
framework/base/libs/ui/GraphicBuffer.cpp
· GraphicBufferAllocator.h
framework/base/include/ui/GraphicBufferAllocator.h
· GraphicBufferAllocator.cpp
framework/base/libs/ui/GraphicBufferAllocator.cpp
· GraphicBufferMapper.cpp
framework/base/libs/ui/GraphicBufferMapper.cpp
· Android_natives.h
framework/base/include/ui/egl/Android_natives.h
· android_native_buffer.h
framework/base/include/ui/android_native_buffer.h
· native_handle.h
system/core/include/cutils/native_handle.h
· gralloc.h
hardware/libhardware/include/hardware/gralloc.h
· ISurface.cpp
framework/base/libs/surfaceflinger_client/ISurface.cpp
· DisplayHardware.cpp
framework/base/libs/surfaceflinger/DisplayHardware.cpp
8.1 概述
Surface是繼Audio系統後要破解第二個複雜的系統。它的難度和複雜度遠遠超過了Audio。基於這種情況,本章將集中精力打通Surface系統的“任督二脈”,這任督二脈分別是:
· 任脈:應用程式和Surface的關係。
· 督脈:Surface和SurfaceFlinger之間的關係。
當這二脈打通後,我們就可以自行修煉更高層次的功夫了。圖8-1顯示了這二脈的關係:
圖8-1 Surface系統的任督二脈
其中,左圖是任脈,右圖是督脈。
· 先看左圖。可以發現,不論是使用Skia繪製二維影象,還是用OpenGL繪製三維影象,最終Application都要和Surface互動。Surface就像是UI的畫布,而App則像是在Surface上作畫。所以要想打通任脈,就須破解App和Surface之間的關係。
· 再看右圖。Surface和SurfaceFlinger的關係,很像Audio系統中AudioTrack和AudioFlinger的關係。Surface向SurfaceFlinger提供資料,而SurfaceFlinger則混合資料。所謂打通督脈的關鍵,就是破解Surface和SurfaceFlinger之間的關係。
目標已清楚,讓我們開始“運功”破解程式碼吧!
說明:為書寫方便起見,後文將SurfaceFlinger簡寫為SF。
8.2 一個Activity的顯示
一般來說,應用程式的外表是通過Activity來展示的。那麼,Activity是如何完成介面繪製工作的呢?根據前面所講的知識,應用程式的顯示和Surface有關,那麼具體到Activity上,它和Surface又是什麼關係呢?
本節就來討論這些問題。首先從Activity的建立說起。
8.2.1 Activity的建立
我們已經知道了Activity的生命週期,如onCreate、onDestroy等,但大家是否考慮過這樣一個問題:
· 如果沒有建立Activity,那麼onCreate和onDestroy就沒有任何意義,可這個Activity究竟是在哪裡建立的?。
第4章中的“Zygote分裂”一節已講過,Zygote在響應請求後會fork一個子程式,這個子程式是App對應的程式,它的入口函式是ActivityThread類的main函式。ActivityThread類中有一個handleLaunchActivity函式,它就是建立Activity的地方。一起來看這個函式,程式碼如下所示:
[-->ActivityThread.java]
private final voidhandleLaunchActivity(ActivityRecord r, Intent customIntent) {
//①performLaunchActivity返回一個Activity
Activitya = performLaunchActivity(r, customIntent);
if(a != null) {
r.createdConfig = new Configuration(mConfiguration);
Bundle oldState = r.state;
//②呼叫handleResumeActivity
handleResumeActivity(r.token, false, r.isForward);
}
......
}
handleLaunchActivity函式中列出了兩個關鍵點,下面對其分別介紹。
1. 建立Activity
第一個關鍵函式performLaunchActivity返回一個Activity,這個Activity就是App中的那個Activity(僅考慮App中只有一個Activity的情況),它是怎麼建立的呢?其程式碼如下所示:
[-->ActivityThread.java]
private final ActivityperformLaunchActivity(ActivityRecord r,
Intent customIntent) {
ActivityInfo aInfo = r.activityInfo;
......//完成一些準備工作
//Activity定義在Activity.java中
Activity activity = null;
try {
java.lang.ClassLoader cl = r.packageInfo.getClassLoader();
/*
mInstrumentation為Instrumentation型別,原始檔為Instrumentation.java。
它在newActivity函式中根據Activity的類名通過Java反射機制來建立對應的Activity,
這個函式比較複雜,待會我們再分析它。
*/
activity = mInstrumentation.newActivity(
cl,component.getClassName(), r.intent);
r.intent.setExtrasClassLoader(cl);
if (r.state != null) {
r.state.setClassLoader(cl);
}
}catch (Exception e) {
......
}
try {
Application app =
r.packageInfo.makeApplication(false,mInstrumentation);
if (activity != null) {
//在Activity中getContext函式返回的就是這個ContextImpl型別的物件
ContextImpl appContext = new ContextImpl();
......
//下面這個函式會呼叫Activity的onCreate函式
mInstrumentation.callActivityOnCreate(activity, r.state);
......
return activity;
}
好了,performLaunchActivity函式的作用明白了吧?
· 根據類名以Java反射的方法建立一個Activity。
· 呼叫Activity的onCreate函式,開始SDK中大書特書Activity的生命週期。
那麼,在onCreate函式中,我們一般會做什麼呢?在這個函式中,和UI相關的重要工作就是呼叫setContentView來設定UI的外觀。接下去,需要看handleLaunchActivity中第二個關鍵函式handleResumeActivity。
2. 分析handleResumeActivity
上面已建立好了一個Activity,再來看handleResumeActivity。它的程式碼如下所示:
[-->ActivityThread.java]
final void handleResumeActivity(IBinder token,boolean clearHide,
boolean isForward) {
boolean willBeVisible = !a.mStartedActivity;
if (r.window == null && !a.mFinished&& willBeVisible) {
r.window= r.activity.getWindow();
//①獲得一個View物件
Viewdecor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//②獲得ViewManager物件
ViewManagerwm = a.getWindowManager();
......
//③把剛才的decor物件加入到ViewManager中
wm.addView(decor,l);
}
......//其他處理
}
上面有三個關鍵點。這些關鍵點似乎已經和UI部分(如View、Window)有聯絡了。那麼這些聯絡是在什麼時候建立的呢?在分析上面程式碼中的三個關鍵點之前,請大家想想在前面的過程中,哪些地方會和UI掛上鉤呢?
· 答案就在onCreate函式中,Activity一般都在這個函式中通過setContentView設定UI介面。
看來,必須先分析setContentView,才能繼續後面的征程。
3. 分析setContentView
setContentView有好幾個同名函式,現在只看其中的一個就可以了。程式碼如下所示:
[-->Activity.java]
public void setContentView(View view) {
//getWindow返回的是什麼呢?一起來看看。
getWindow().setContentView(view);
}
public Window getWindow() {
returnmWindow; //返回一個型別為Window的mWindow,它是什麼?
}
上面出現了兩個和UI有關係的類:View和Window[①]。來看SDK文件是怎麼描述這兩個類的。這裡先給出原文描述,然後進行對應翻譯:
· Window:abstract base class for a top-levelwindow look and behavior policy. An instance of this class should be used asthe top-level view added to the window manager. It provides standard UIpolicies such as a background, title area, default key processing, etc.
中文的意思是:Window是一個抽象基類,用於控制頂層視窗的外觀和行為。做為頂層視窗它有什麼特殊的職能呢?即繪製背景和標題欄、預設的按鍵處理等。
這裡面有一句比較關鍵的話:它將做為一個頂層的view加入到Window Manager中。
· View:This class represents the basicbuilding block for user interface components. A View occupies a rectangulararea on the screen and is responsible for drawing and event handling.
View的概念就比較簡單了,它是一個基本的UI單元,佔據螢幕的一塊矩形區域,可用於繪製,並能處理事件。
從上面的View和Window的描述,再加上setContentView的程式碼,我們能想象一下這三者的關係,如圖8-2所示:
圖8-2 Window/View的假想關係圖
根據上面的介紹,大家可能會產生兩個疑問:
· Window是一個抽象類,它實際的物件到底是什麼型別?
· Window Manager究竟是什麼?
如果能有這樣的疑問,就說明我們非常細心了。下面試來解決這兩個問題。
(1)Activity的Window
據上文講解可知,Window是一個抽象類。它實際的物件到底屬於什麼型別?先回到Activity建立的地方去看看。下面正是建立Activity時的程式碼,可當時沒有深入地分析。
activity = mInstrumentation.newActivity(
cl,component.getClassName(), r.intent);
程式碼中呼叫了Instrumentation的newActivity,再去那裡看看。
[-->Instrumentation.java]
public Activity newActivity(Class<?>clazz, Context context,
IBinder token, Application application, Intent intent,
ActivityInfo info, CharSequencetitle, Activity parent,
String id,Object lastNonConfigurationInstance)
throws InstantiationException, IllegalAccessException{
Activity activity = (Activity)clazz.newInstance();
ActivityThread aThread = null;
//關鍵函式attach!!
activity.attach(context, aThread, this, token, application, intent,
info, title,parent, id, lastNonConfigurationInstance,
new Configuration());
return activity;
}
看到關鍵函式attach了吧?Window的真相馬上就要揭曉了,讓我們用咆哮體②來表達內心的激動之情吧!!!!
[-->Activity.java]
final void attach(Context context,ActivityThread aThread,
Instrumentation instr, IBinder token, int ident,
Application application, Intent intent, ActivityInfo info,
CharSequence title, Activity parent, String id,
Object lastNonConfigurationInstance,
HashMap<String,Object> lastNonConfigurationChildInstances,
Configuration config) {
......
//利用PolicyManager來建立Window物件
mWindow = PolicyManager.makeNewWindow(this);
mWindow.setCallback(this);
......
//建立WindowManager物件
mWindow.setWindowManager(null, mToken, mComponent.flattenToString());
if(mParent != null) {
mWindow.setContainer(mParent.getWindow());
}
//儲存這個WindowManager物件
mWindowManager = mWindow.getWindowManager();
mCurrentConfig = config;
}
此刻又有一點失望吧?這裡冒出了個PolicyManager類,Window是由它的makeNewWindow函式所建立,因此還必須再去看看這個PolicyManager。
(2)水面下的冰山——PolicyManager
PolicyManager定義於PolicyManager.java檔案,該檔案在一個非常獨立的目錄下,現將其單獨列出來:
· frameworks/policies/base/phone/com/android/internal/policy/impl
注意,上面路徑中的灰色目錄phone是針對智慧手機這種小螢幕的;另外還有一個平級的目錄叫mid,是針對Mid裝置的。mid目錄的程式碼比較少,可能目前還沒有開發完畢。
下面來看這個PolicyManager,它比較簡單。
[-->PolicyManager.java]
public final class PolicyManager {
private static final String POLICY_IMPL_CLASS_NAME =
"com.android.internal.policy.impl.Policy";
private static final IPolicy sPolicy;
static{
//
try {
Class policyClass = Class.forName(POLICY_IMPL_CLASS_NAME);
//建立Policy物件
sPolicy = (IPolicy)policyClass.newInstance();
}catch (ClassNotFoundException ex) {
......
}
private PolicyManager() {}
//通過Policy物件的makeNewWindow建立一個Window
publicstatic Window makeNewWindow(Context context) {
return sPolicy.makeNewWindow(context);
}
......
}
這裡有一個單例的sPolicy物件,它是Policy型別,請看它的定義。
(3)真正的Window
Policy型別的定義程式碼如下所示:
[-->Policy.java]
public class Policy implements IPolicy {
private static final String TAG = "PhonePolicy";
private static final String[] preload_classes = {
"com.android.internal.policy.impl.PhoneLayoutInflater",
"com.android.internal.policy.impl.PhoneWindow",
"com.android.internal.policy.impl.PhoneWindow$1",
"com.android.internal.policy.impl.PhoneWindow$ContextMenuCallback",
"com.android.internal.policy.impl.PhoneWindow$DecorView",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState",
"com.android.internal.policy.impl.PhoneWindow$PanelFeatureState$SavedState",
};
static{
//載入所有的類
for (String s : preload_classes) {
try {
Class.forName(s);
} catch (ClassNotFoundException ex) {
......
}
}
}
public PhoneWindow makeNewWindow(Contextcontext) {
//makeNewWindow返回的是PhoneWindow物件
return new PhoneWindow(context);
}
......
}
至此,終於知道了程式碼:
mWindow = PolicyManager.makeNewWindow(this);
返回的Window,原來是一個PhoneWindow物件。它的定義在PhoneWindow.java中。
mWindow的真實身份搞清楚了,還剩下個WindowManager。現在就來揭示其真面目。
(4)真正的WindowManager
先看WindowManager建立的程式碼,如下所示:
[-->Activity.java]
......//建立mWindow物件
//呼叫mWindow的setWindowManager函式
mWindow.setWindowManager(null, mToken,mComponent.flattenToString());
.....
上面的函式設定了PhoneWindow的WindowManager,不過第一個引數是null,這是什麼意思?在回答此問題之前,先來看PhoneWindow的定義,它是從Window類派生。
[-->PhoneWindow.java::PhoneWindow定義]
public class PhoneWindow extends Windowimplements MenuBuilder.Callback
前面呼叫的setWindowManager函式,其實是由PhoneWindow的父類Window類來實現的,來看其程式碼,如下所示:
[-->Window.java]
public void setWindowManager(WindowManagerwm,IBinder appToken, String appName) { //注意,傳入的wm值為null
mAppToken = appToken;
mAppName = appName;
if(wm == null) {
//如果wm為空的話,則建立WindowManagerImpl物件
wm = WindowManagerImpl.getDefault();
}
//mWindowManager是一個LocalWindowManager
mWindowManager = new LocalWindowManager(wm);
}
LocalWindowManager是在Window中定義的內部類,請看它的建構函式,其定義如下所示:
[-->Window.java::LocalWindowManager定義]
private class LocalWindowManager implementsWindowManager {
LocalWindowManager(WindowManager wm) {
mWindowManager = wm;//還好,只是簡單地儲存了傳入的wm引數
mDefaultDisplay = mContext.getResources().getDefaultDisplay(
mWindowManager.getDefaultDisplay());
}
......
如上面程式碼所示,LocalWindowManager將儲存一個WindowManager型別的物件,這個物件的實際型別是WindowManagerImpl。而WindowManagerImpl又是什麼呢?來看它的程式碼,如下所示:
[-->WindowManagerImpl.java]
public class WindowManagerImpl implementsWindowManager {
......
public static WindowManagerImpl getDefault()
{
return mWindowManager; //返回的就是WindowManagerImpl物件
}
private static WindowManagerImpl mWindowManager= new WindowManagerImpl();
}
看到這裡,是否有點頭暈眼花?很多朋友讀我的一篇與此內容相關的博文後,普遍也有如此反應。對此,試配製了一劑治暈藥方,如圖8-3所示:
圖8-3 Window和WindowManger的家族圖譜
根據上圖,可得出以下結論:
· Activity的mWindow成員變數其真實型別是PhoneWindow,而mWindowManager成員變數的真實型別是LocalWindowManager。
· LocalWindowManager和WindowManagerImpl都實現了WindowManager介面。這裡採用的是Proxy模式,表明LocalWindowManager將把它的工作委託WindowManagerImpl來完成。
(5)setContentView的總結
瞭解了上述知識後,重新回到setContentView函式。這次希望能分析得更深入些。
[-->Activity.java]
public void setContentView(View view) {
getWindow().setContentView(view);//getWindow返回的是PhoneWindow
}
一起來看PhoneWindow的setContentView函式,程式碼如下所示:
[-->PhoneWindow]
public void setContentView(View view) {
//呼叫另一個setContentView
setContentView(view,
new ViewGroup.LayoutParams(MATCH_PARENT,MATCH_PARENT));
}
public void setContentView(View view,ViewGroup.LayoutParams params) {
//mContentParent為ViewGroup型別,它的初值為null
if(mContentParent == null) {
installDecor();
}else {
mContentParent.removeAllViews();
}
//把view加入到ViewGroup中
mContentParent.addView(view, params);
......
}
mContentParent是一個ViewGroup型別,它從View中派生,所以也是一個UI單元。從它名字中“Group”所表達的意思分析,它還可以包含其他的View元素。這又是什麼意思呢?
· 也就是說,在繪製一個ViewGroup時,它不僅需要把自己的樣子畫出來,還需要把它包含的View元素的樣子也畫出來。讀者可將它想象成一個容器,容器中的元素就是View。
這裡採用的是23種設計模式中的Composite模式,它是UI程式設計中常用的模式之一。
再來看installDecor函式,其程式碼如下所示:
[-->PhoneWindow.java]
private void installDecor() {
if (mDecor == null) {
//建立mDecor,它為DecorView型別,從FrameLayout派生
mDecor= generateDecor();
......
}
if(mContentParent == null) {
//得到這個mContentParent
mContentParent = generateLayout(mDecor);
//建立標題欄
mTitleView= (TextView)findViewById(com.android.internal.R.id.title);
......
}
generateLayout函式的輸入引數為mDecor,輸出為mContentParent,程式碼如下所示:
[-->PhoneWindow]
protected ViewGroup generateLayout(DecorViewdecor){
......
intlayoutResource;
intfeatures = getLocalFeatures();
if((features & ((1 << FEATURE_LEFT_ICON) |(1 <<FEATURE_RIGHT_ICON))) != 0) {
if(mIsFloating) {
//根據情況取得對應標題欄的資源id
layoutResource = com.android.internal.R.layout.dialog_title_icons;
}
......
}
mDecor.startChanging();
View in =mLayoutInflater.inflate(layoutResource, null);
//加入標題欄
decor.addView(in,new ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT));
/*
ID_ANDROID_CONTENT的值為”com.android.internal.R.id.content”
這個contentParent由findViewById返回,實際上就是mDecorView的一部分。
*/
ViewGroupcontentParent = (ViewGroup)findViewById(ID_ANDROID_CONTENT);
......
mDecor.finishChanging();
returncontentParent;
}
下面看findViewById是如何實現的。它定義在Window.java中,程式碼如下所示:
[-->Window.java]
public View findViewById(int id) {
//getDecorView將返回mDecorView,所以contentParent確實是DecorView的一部分
returngetDecorView().findViewById(id);
}
大家還記得圖8-2嗎?介紹完上面的知識後,根據圖8-2,可繪製更細緻的圖8-4:
圖8-4 一個Activity中的UI元件
可從上圖中看出,在Activity的onCreate函式中,通過setContentView設定的View,其實只是DecorView的子View。DecorView還處理了標題欄顯示等一系列的工作。
注意,這裡使用了設計模式中的Decorator(裝飾)模式,它也是UI程式設計中常用的模式之一。
4. 重回handleResumeActivity
看完setContentView的分析後,不知大家是否還記得這樣一個問題:為什麼要分析這個setContentView函式?在繼續前行之前,先來回顧一下被setContentView打斷的流程。
當時,我們正在分析handleResumeActivity,程式碼如下所示:
[-->ActivityThread.java]
final void handleResumeActivity(IBinder token,boolean clearHide,
boolean isForward) {
booleanwillBeVisible = !a.mStartedActivity;
......
if (r.window == null && !a.mFinished&& willBeVisible) {
r.window= r.activity.getWindow();
//①獲得一個View物件。現在知道這個view就是DecorView
Viewdecor = r.window.getDecorView();
decor.setVisibility(View.INVISIBLE);
//②獲得ViewManager物件,這個wm就是LocalWindowManager
ViewManagerwm = a.getWindowManager();
WindowManager.LayoutParamsl = r.window.getAttributes();
a.mDecor= decor;
l.type =WindowManager.LayoutParams.TYPE_BASE_APPLICATION;
if(a.mVisibleFromClient) {
a.mWindowAdded= true;
//③把剛才的decor物件加入到ViewManager中
wm.addView(decor,l);
}
......//其他處理
}
在上面的程式碼中,由於出現了多個之前不熟悉的東西,如View、ViewManager等,而這些東西的來源又和setContentView有關,所以我們才轉而去分析setContentView了。想起來了吧?
由於程式碼比較長,跳轉關係也很多,在分析程式碼時,請讀者把握流程,在大腦中建立一個程式碼分析的堆疊。
下面就從addView的分析開始。如前面所介紹的,它的呼叫方法是:
wm.addView(decor, l);//wm型別實際是LocalWindowManager
來看這個addView函式,它的程式碼如下所示:
[-->Window.javaLocalWindowManager]
public final void addView(View view,ViewGroup.LayoutParams params) {
WindowManager.LayoutParams wp =(WindowManager.LayoutParams)params;
CharSequence curTitle = wp.getTitle();
...... //做一些操作,可以不管它
//還記得前面提到過的Proxy模式嗎?mWindowManager物件實際上是WindowManagerImpl型別
mWindowManager.addView(view, params);
}
看來,要搞清楚這個addView函式還是比較麻煩的,因為現在必須到WindowManagerImpl中去看看。它的程式碼如下所示:
[-->WindowManagerImpl.java]
private void addView(View view,ViewGroup.LayoutParams params, boolean nest)
{
ViewRootroot; //ViewRoot,幕後的主角終於登場了!
synchronized(this) {
//①建立ViewRoot
root =new ViewRoot(view.getContext());
root.mAddNesting = 1;
view.setLayoutParams(wparams);
if(mViews == null) {
index = 1;
mViews = new View[1];
mRoots= new ViewRoot[1];
mParams = new WindowManager.LayoutParams[1];
} else{
......
}
index--;
mViews[index]= view;
mRoots[index]= root;//儲存這個root
mParams[index]= wparams;
//②setView,其中view是剛才我們介紹的DecorView
root.setView(view,wparams, panelParentView);//
}
“ViewRoot,ViewRoot ....”,主角終於出場了!即使沒介紹它的真實身份,不禁也想歡呼幾聲。可為避免高興得過早,還是應該先冷靜地分析一下它。這裡,列出了ViewRoot的兩個重要關鍵點。
(1)ViewRoot是什麼?
ViewRoot是什麼?看起來好像和View有些許關係,至少名字非常像。事實上,它的確和View有關係,因為它實現了ViewParent介面。SDK的文件中有關於ViewParent的介紹。但它和Android基本繪圖單元中的View卻不太一樣,比如:ViewParent不處理繪畫,因為它沒有onDraw函式。
如上所述,ViewParent和繪畫沒有關係,那麼,它的作用是什麼?先來看它的程式碼,如下所示:
[-->ViewRoot.java::ViewRoot定義]
public final class ViewRoot extends Handlerimplements ViewParent,
View.AttachInfo.Callbacks //從Handler類派生
{
private final Surface mSurface = new Surface();//這裡建立了一個Surface物件
final W mWindow; //這個是什麼?
View mView;
}
上面這段程式碼傳達出了一些重要資訊:
· ViewRoot繼承了Handler類,看來它能處理訊息。ViewRoot果真重寫了handleMessage函式。稍侯再來看它。
· ViewRoot有一個成員變數叫mSurface,它是Surface型別。
· ViewRoot還有一個W型別的mWindow和一個View型別的mView變數。
其中,W是ViewRoot定義的一個靜態內部類:
static class W extends IWindow.Stub
這個類將參與Binder的通訊,以後對此再做講解,先來介紹Surface類。
(2)神筆馬良乎?
這裡冒出來一個Surface類。它是什麼?在回答此問題之前,先來考慮這樣一個問題:
· 前文介紹的View、DecorView等都是UI單元,這些UI單元的繪畫工作都在onDraw函式中完成。如果把onDraw想象成畫圖過程,那麼畫布是什麼?
Android肯定不是“馬良”,它也沒有那支可以在任何物體上作畫的“神筆”,所以我們需要一塊實實在在的畫布,這塊畫布就是Surface。SDK文件對Surface類的說明是:Handle on to a raw buffer thatis being managed by the screen compositor。這句話的意思是:
· 有一塊Raw buffer,至於是記憶體還是視訊記憶體,不必管它。
· Surface操作這塊Raw buffer。
· Screen compositor(其實就是SurfaceFlinger)管理這塊Raw buffer。
Surface和SF、ViewRoot有什麼關係呢?相信,聰明的你此時已經明白些了,這裡用圖8-5描繪一下心中的想法:
圖8-5 馬良的神筆工作原理
結合之前所講的知識,圖8-5清晰地傳達瞭如下幾條資訊:
· ViewRoot有一個成員變數mSurface,它是Surface型別,它和一塊Raw Buffer有關聯。
· ViewRoot是一個ViewParent,它的子View的繪畫操作,是在畫布Surface上展開的。
· Surface和SurfaceFlinger有互動,這非常類似AudioTrack和AudioFlinger之間的互動。
既然本章題目為“深入理解Surface系統”,那麼就需要重點關注Surface和SurfaceFlinger間的關係。建立這個關係需ViewRoot的參與,所以應先來分析ViewRoot的建立和它的setView函式。
(3)ViewRoot的建立和對setView的分析
來分析ViewRoot的構造。關於它所包含內容,程式碼如下所示:
[-->ViewRoot.java]
public ViewRoot(Context context) {
super();
....
// getWindowSession?我們進去看看
getWindowSession(context.getMainLooper());
......//ViewRoot的mWindow是一個W型別,注意它不是Window型別,而是IWindow型別
mWindow= new W(this, context);
}
getWindowsession函式,將建立Activity的ViewRoot和WindowManagerService的關係。程式碼如下所示:
[-->ViewRoot.java]
ublic static IWindowSessiongetWindowSession(Looper mainLooper) {
synchronized (mStaticInit) {
if(!mInitialized) {
try {
InputMethodManagerimm =
InputMethodManager.getInstance(mainLooper);
//下面這個函式先得到WindowManagerService的Binder代理,然後呼叫它的openSession
sWindowSession = IWindowManager.Stub.asInterface(
ServiceManager.getService("window"))
.openSession(imm.getClient(), imm.getInputContext());
mInitialized = true;
} catch (RemoteException e) {
}
}
return sWindowSession;
}
}
WindowSession?WindowManagerService?第一次看到這些東西時,我快瘋了。複雜,太複雜,無比複雜!要攻克這些難題,應先來回顧一下與Zygote相關的知識:
· WindowManagerService(以後簡稱WMS)由System_Server程式啟動,SurfaceFlinger服務也在這個程式中。
看來,Activity的顯示還不單純是它自己的事,還需要和WMS建立聯絡才行。繼續看。先看setView的處理。這個函式很複雜,注意其中關鍵的幾句。
openSession的操作是一個使用Binder通訊的跨程式呼叫,暫且記住這個函式,在精簡流程之後再來分析。
程式碼如下所示:
[-->ViewRoot.java]
public void setView(View view, WindowManager.LayoutParamsattrs,
View panelParentView){//第一個引數view是DecorView
......
mView= view;//儲存這個view
synchronized (this) {
requestLayout(); //待會先看看這個。
try {
//呼叫IWindowSession的add函式,第一個引數是mWindow
res =sWindowSession.add(mWindow, mWindowAttributes,
getHostVisibility(), mAttachInfo.mContentInsets);
}
......
}
ViewRoot的setView函式做了三件事:
· 儲存傳入的view引數為mView,這個mView指向PhoneWindow的DecorView。
· 呼叫requestLayout。
· 呼叫IWindowSession的add函式,這是一個跨程式的Binder通訊,第一個引數是mWindow,它是W型別,從IWindow.stub派生。
先來看這個requestLayout函式,它非常簡單,就是往handler中傳送了一個訊息。注意,ViewRoot是從Handler派生的,所以這個訊息最後會由ViewRoot自己處理,程式碼如下所示:
[-->ViewRoot.java]
public void requestLayout() {
checkThread();
mLayoutRequested = true;
scheduleTraversals();
}
public void scheduleTraversals() {
if(!mTraversalScheduled) {
mTraversalScheduled = true;
sendEmptyMessage(DO_TRAVERSAL); //傳送DO_TRAVERSAL訊息
}
}
好,requestLayout分析完畢。
從上面的程式碼中可發現,ViewRoot和遠端程式SystemServer的WMS有互動,先來總結一下它和WMS的互動流程:
· ViewRoot呼叫openSession,得到一個IWindowSession物件。
· 呼叫WindowSession物件的add函式,把一個W型別的mWindow物件做為引數傳入。
5. ViewRoot和WMS的關係
上面總結了ViewRoot和WMS的互動流程,其中一共有兩個跨程式的呼叫。一起去看。
(1)呼叫流程分析
WMS的程式碼在WindowManagerService.java中:
[-->WindowManagerService.java]
public IWindowSessionopenSession(IInputMethodClient client,
IInputContextinputContext) {
......
return new Session(client, inputContext);
}
Session是WMS定義的內部類。它支援Binder通訊,並且屬於Bn端,即響應請求的服務端。
再來看它的add函式。程式碼如下所示:
[-->WindowManagerService.java::Session]
public int add(IWindow window,WindowManager.LayoutParams attrs,
int viewVisibility, Rect outContentInsets) {
//呼叫外部類物件的addWindow,也就是WMS的addWindow
returnaddWindow(this, window, attrs, viewVisibility,
outContentInsets);
}
[-->WindowManagerService.java]
public int addWindow(Session session, IWindowclient,
WindowManager.LayoutParams attrs, int viewVisibility,
Rect outContentInsets) {
......
//建立一個WindowState
win = new WindowState(session, client, token,
attachedWindow, attrs,viewVisibility);
......
//呼叫attach函式
win.attach();
......
return res;
}
WindowState類也是在WMS中定義的內部類,直接看它的attach函式,程式碼如下所示:
[-->WMS.java::WindowState]
void attach() {
//mSession就是Session物件,呼叫它的windowAddedLocked函式
mSession.windowAddedLocked();
}
[-->WMS.java::Session]
void windowAddedLocked() {
if(mSurfaceSession == null) {
......
//建立一個SurfaceSession物件
mSurfaceSession= new SurfaceSession();
......
}
mNumWindow++;
}
這裡出現了另外一個重要的物件SurfaceSession。在講解它之前,急需理清一下現有的知識點,否則可能會頭暈。
(2)ViewRoot和WMS的關係梳理
ViewRoot和WMS之間的關係,可用圖8-6來表示:
圖8-6 ViewRoot和WMS的關係
總結一下圖8-6中的知識點:
· ViewRoot通過IWindowSession和WMS程式進行跨程式通訊。IWindowSession定義在IWindowSession.aidl檔案中。這個檔案在編譯時由aidl工具處理,最後會生成類似於Native Binder中Bn端和Bp端的程式碼,後文會介紹它。
· ViewRoot內部有一個W型別的物件,它也是一個基於Binder通訊的類,W是IWindow的Bn端,用於響應請求。IWindow定義在另一個aidl檔案IWindow.aidl中。
為什麼需要這兩個特殊的類呢?簡單介紹一下:
首先,來看IWindowSession.aidl對自己的描述:
· System private per-application interface to the window manager:也就是說每個App程式都會和WMS建立一個IWindowSession會話。這個會話被App程式用於和WMS通訊。後面會介紹它的requestLayout函式。
再看對IWindow.adil的描述:
· API back to a client window that the Window Manager uses to informit of interesting things happening:這句話的大意是IWindow是WMS用來做事件通知的。每當發生一些事情時,WMS就會把這些事告訴某個IWindow。可以把IWindow想象成一個回撥函式。
IWindow的描述表達了什麼意思呢?不妨看看它的內容,程式碼如下所示:
[-->IWindow.aidl定義]
void dispatchKey(in KeyEvent event);
void dispatchPointer(in MotionEvent event, longeventTime,
boolean callWhenDone);
void dispatchTrackball(in MotionEvent event,long eventTime,
boolean callWhenDone);
明白了?這裡的事件指的就是按鍵、觸屏等事件。那麼,一個按鍵事件是如何被分發的呢?下面是它大致的流程:
· WMS所在的SystemServer程式接收到按鍵事件。
· WMS找到UI位於螢幕頂端的程式所對應的IWindow物件,這是一個Bp端物件。
· 呼叫這個IWindow物件的dispatchKey。IWindow物件的Bn端位於ViewRoot中,ViewRoot再根據內部View的位置資訊找到真正處理這個事件的View,最後呼叫dispatchKey函式完成按鍵的處理。
其實這些按鍵事件的分發機制可以拿Windows的UI程式設計來做類比,在Windows中應用程式的按鍵處理流程是:
· 每一個按鍵事件都會轉化成一個訊息,這個訊息將由系統加入到對應程式的訊息佇列中。該程式的訊息在派發處理時,會根據訊息的控制程式碼找到對應的Window(視窗),繼而該訊息就由這個Window處理了。
注意:上面的描述實際上大大簡化了真實的處理流程,讀者可在瞭解大體知識後進行更深入的研究。
上面介紹的是ViewRoot和WMS的互動,但是我們最關心的Surface還沒有正式介紹,在此之前,還是先介紹Activity的流程。
8.2.2 Activity的UI繪製
ViewRoot的setView函式中,會有一個requestLayout。根據前面的分析可知,它會向ViewRoot傳送一個DO_TRAVERSAL訊息,來看它的handleMessage函式,程式碼如下所示:
[-->ViewRoot.java]
public void handleMessage(Message msg) {
switch (msg.what) {
......
case DO_TRAVERSAL:
......
performTraversals();//呼叫performTraversals函式
......
break;
......
}
}
再去看performTraversals函式,這個函式比較複雜,先只看它的關鍵部分,程式碼如下所示:
[-->ViewRoot.java]
private void performTraversals() {
finalView host = mView;//還記得這mView嗎?它就是DecorView喔
booleaninitialized = false;
booleancontentInsetsChanged = false;
booleanvisibleInsetsChanged;
try {
relayoutResult= //①關鍵函式relayoutWindow
relayoutWindow(params, viewVisibility,insetsPending);
}
......
draw(fullRedrawNeeded);// ②開始繪製
......
}
1. relayoutWindow的分析
performTraversals函式比較複雜,暫時只關注其中的兩個函式relayoutWindow和draw即可。先看第一個relayoutWindow,程式碼如下所示:
[-->ViewRoot.java]
private intrelayoutWindow(WindowManager.LayoutParams params,
int viewVisibility, boolean insetsPending)throws RemoteException {
//原來是呼叫IWindowSession的relayOut,暫且記住這個呼叫
int relayoutResult = sWindowSession.relayout(
mWindow, params,
(int) (mView.mMeasuredWidth * appScale + 0.5f),
(int) (mView.mMeasuredHeight * appScale + 0.5f),
viewVisibility, insetsPending, mWinFrame,
mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface); mSurface做為引數傳進去了。
}
......
}
relayoutWindow中會呼叫IWindowSession的relayout函式,暫且記住這個呼叫,在精簡流程後再進行分析。
2. draw的分析
再來看draw函式。這個函式非常重要,它可是Acitivity漂亮臉蛋的塑造大師啊,程式碼如下所示:
[-->ViewRoot.java]
private void draw(boolean fullRedrawNeeded) {
Surface surface = mSurface;//mSurface是ViewRoot的成員變數
......
Canvascanvas;
try {
int left = dirty.left;
int top = dirty.top;
int right = dirty.right;
int bottom = dirty.bottom;
//從mSurface中lock一塊Canvas
canvas = surface.lockCanvas(dirty);
......
mView.draw(canvas);//呼叫DecorView的draw函式,canvas就是畫布的意思啦!
......
//unlock畫布,螢幕上馬上就會見到漂亮寶貝的長相了。
surface.unlockCanvasAndPost(canvas);
}
......
}
UI的顯示好像很簡單嘛!真的是這樣的嗎?在揭露這個“驚天祕密”之前我們先總結一下Activity的顯示流程。
8.2.3 Activity總結
不得不承認的是前面幾節的內容很多也很繁雜,為了讓後面分析的過程更流暢輕鬆一些,所以我們必須要總結一下。關於Activity的建立和顯示,前面幾節的資訊可提煉成如下幾條:
· Activity的頂層View是DecorView,而我們在onCreate函式中通過setContentView設定的View只不過是這個DecorView中的一部分罷了。DecorView是一個FrameLayout型別的ViewGroup。
· Activity和UI有關,它包含一個Window(真實型別是PhoneWindow)和一個WindowManager(真實型別是LocalWindowManager)物件。這兩個物件將控制整個Activity的顯示。
· LocalWindowManager使用了WindowManagerImpl做為最終的處理物件(Proxy模式),這個WindowManagerImpl中有一個ViewRoot物件。
· ViewRoot實現了ViewParent介面,它有兩個重要的成員變數,一個是mView,它指向Activity頂層UI單元的DecorView,另外有一個mSurface,這個Surface包含了一個Canvas(畫布)。除此之外,ViewRoot還通過Binder系統和WindowManagerService進行了跨程式互動。
· ViewRoot能處理Handler的訊息,Activity的顯示就是由ViewRoot在它的performTraversals函式中完成的。
· 整個Activity的繪圖流程就是從mSurface中lock一塊Canvas,然後交給mView去自由發揮畫畫的才能,最後unlockCanvasAndPost釋放這塊Canvas。
這裡和顯示有關的就是最後三條了,其中最重要的內容都和Surface相關,既然mSurface是ViewRoot的本地變數,那就直接去看Surface。上面的程式碼分析一路走下來,真是比較流暢,波瀾不驚,可事實果真如此嗎?
8.3 初識Surface
本節將介紹Surface物件。它可是縱跨Java/JNI層的物件,想必讀者朋友已經摩拳擦掌,躍躍欲試了。
8.3.1 和Surface有關的流程總結
這裡,先總結一下前面講解中和Surface有關的流程:
· 在ViewRoot構造時,會建立一個Surface,它使用無參建構函式,程式碼如下所示:
private final Surface mSurface = new Surface();
· ViewRoot通過IWindowSession和WMS互動,而WMS中會呼叫的一個attach函式,會構造一個SurfaceSession,程式碼如下所示:
void windowAddedLocked() {
if(mSurfaceSession == null) {
mSurfaceSession = new SurfaceSession();
mNumWindow++;
}
}
· ViewRoot在performTransval的處理過程中會呼叫IWindowSession的relayout函式。這個函式還沒有分析。
· ViewRoot呼叫Surface的lockCanvas,得到一塊畫布。
· ViewRoot呼叫Surface的unlockCanvasAndPost釋放這塊畫布。
這裡從relayout函式開始分析,來看。
8.3.2 Surface之乾坤大挪移
1. 乾坤大挪移的表象
relayout的函式是一個跨程式的呼叫,由WMS完成實際處理。先到ViewRoot中看看呼叫方的用法,程式碼如下所示:
[-->ViewRoot.java]
private intrelayoutWindow(WindowManager.LayoutParams params,
int viewVisibility, boolean insetsPending)
throws RemoteException {
int relayoutResult = sWindowSession.relayout(
mWindow, params,
(int) (mView.mMeasuredWidth * appScale + 0.5f),
(int) (mView.mMeasuredHeight * appScale + 0.5f),
viewVisibility, insetsPending, mWinFrame,
mPendingContentInsets, mPendingVisibleInsets,
mPendingConfiguration, mSurface);//mSurface傳了進去
......
return relayoutResult;
}
再看接收方的處理。它在WMS的Session中,程式碼如下所示:
[-->WindowManagerService.java::Session]
public int relayout(IWindow window,WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewFlags,
boolean insetsPending, Rect outFrame, Rect outContentInsets,
Rect outVisibleInsets, Configuration outConfig,
Surface outSurface) {
//注意最後這個引數的名字,叫outSurface
//呼叫外部類物件的relayoutWindow
returnrelayoutWindow(this, window, attrs,
requestedWidth,requestedHeight, viewFlags, insetsPending,
outFrame, outContentInsets,outVisibleInsets, outConfig,
outSurface);
}
[-->WindowManagerService.java]
public int relayoutWindow(Session session,IWindow client,
WindowManager.LayoutParams attrs, int requestedWidth,
int requestedHeight, int viewVisibility, boolean insetsPending,
Rect outFrame, Rect outContentInsets, Rect outVisibleInsets,
Configuration outConfig, SurfaceoutSurface){
.....
try {
//win就是WinState,這裡將建立一個本地的Surface物件
Surfacesurface = win.createSurfaceLocked();
if(surface != null) {
//先建立一個本地surface,然後在outSurface的物件上呼叫copyFrom
//將本地Surface的資訊拷貝到outSurface中,為什麼要這麼麻煩呢?
outSurface.copyFrom(surface);
......
}
[-->WindowManagerService.java::WindowState]
Surface createSurfaceLocked() {
......
try {
//mSurfaceSession就是在Session上建立的SurfaceSession物件
//這裡,以它為引數,構造一個新的Surface物件
mSurface = new Surface(
mSession.mSurfaceSession, mSession.mPid,
mAttrs.getTitle().toString(),
0, w, h, mAttrs.format, flags);
}
Surface.openTransaction();//開啟一個事務處理
......
Surface.closeTransaction();//關閉一個事務處理。關於事務處理以後再分析
......
}
上面的程式碼段好像有點混亂。用圖8-7來表示一下這個流程:
圖8-7 複雜的Surface建立流程
根據圖8-7可知:
· WMS中的Surface是乾坤中的乾,它的構造使用了帶SurfaceSession引數的建構函式。
· ViewRoot中的Surface是乾坤中的坤,它的構造使用了無參建構函式。
· copyFrom就是挪移,它將乾中的Surface資訊,拷貝到坤中的Surface即outSurface裡。
要是覺得乾坤大挪移就是這兩三下,未免就太小看它了。為徹底揭示這期間的複雜過程,我們將使用必殺技——aidl工具。
2. 揭祕Surface的乾坤大挪移
aidl可以把XXX.aidl檔案轉換成對應的Java檔案。剛才所說的乾坤大挪移發生在ViewRoot呼叫IWindowSession的relayout函式中,它在IWindowSession.adil中的定義如下:
[-->IWindowSesson.aidl]
interface IWindowSession {
......
intrelayout(IWindow window, in WindowManager.LayoutParams attrs,
int requestedWidth, int requestedHeight, int viewVisibility,
boolean insetsPending, out Rect outFrame, out Rect outContentInsets,
out Rect outVisibleInsets, out Configuration outConfig,
out Surface outSurface);
下面,拿必殺技aidl來編譯一下這個aidl檔案,其使用方法如下:
在命令列下可以輸入:
aidl –Ie:\froyo\source\frameworks\base\core\java\ -Ie:\froyo\source\frameworks\base\Graphics\java e:\froyo\source\frameworks\base\core\java\android\view\IWindowSession.aidltest.java
新生成的Java檔案叫test.java。其中,-I引數指定include目錄,例如aidl檔案中使用了別的Java檔案中的類,所以需要指定這些Java檔案所在的目錄。
先看ViewRoot這個客戶端生成的程式碼,如下所示:
[-->test.java::Bp端::relayout]
public int relayout(android.view.IWindow window,
android.view.WindowManager.LayoutParams attrs,
int requestedWidth, intrequestedHeight,
int viewVisibility, boolean insetsPending,
android.graphics.Rect outFrame,
android.graphics.Rect outContentInsets,
android.graphics.Rect outVisibleInsets,
android.content.res.Configuration outConfig,
android.view.Surface outSurface)//outSurface是第11個引數
throwsandroid.os.RemoteException
{
android.os.Parcel_data = android.os.Parcel.obtain();
android.os.Parcel_reply = android.os.Parcel.obtain();
int_result;
try {
_data.writeInterfaceToken(DESCRIPTOR);
_data.writeStrongBinder((((window!=null))?(window.asBinder()):(null)));
if((attrs!=null)) {
_data.writeInt(1);
attrs.writeToParcel(_data,0);
}
else {
_data.writeInt(0);
}
_data.writeInt(requestedWidth);
_data.writeInt(requestedHeight);
_data.writeInt(viewVisibility);
_data.writeInt(((insetsPending)?(1):(0)));
//奇怪,outSurface的資訊沒有寫到請求包_data中,就直接傳送請求訊息了
mRemote.transact(Stub.TRANSACTION_relayout,_data, _reply, 0);
_reply.readException();
_result= _reply.readInt();
if((0!=_reply.readInt())) {
outFrame.readFromParcel(_reply);
}
....
if((0!=_reply.readInt())) {
outSurface.readFromParcel(_reply);//從Parcel中讀取資訊來填充outSurface
}
}
......
return_result;
}
奇怪!ViewRoot呼叫requestlayout竟然沒有把outSurface資訊傳進去,這麼說,服務端收到的Surface物件應該就是空吧?那怎麼能呼叫copyFrom呢?還是來看服務端的處理,先看首先收到訊息的onTransact函式,程式碼如下所示:
[-->test.java::Bn端::onTransact]
public boolean onTransact(int code,android.os.Parcel data,
android.os.Parcelreply, int flags)
throwsandroid.os.RemoteException
{
switch(code)
{
caseTRANSACTION_relayout:
{
data.enforceInterface(DESCRIPTOR);
android.view.IWindow_arg0;
android.view.Surface_arg10;
//剛才講了,Surface資訊並沒有傳過來,那麼在relayOut中看到的outSurface是怎麼
//出來的呢?看下面這句可知,原來在服務端這邊竟然new了一個新的Surface!!!
_arg10= new android.view.Surface();
int_result = this.relayout(_arg0, _arg1, _arg2, _arg3, _arg4,
_arg5,_arg6, _arg7, _arg8, _arg9, _arg10);
reply.writeNoException();
reply.writeInt(_result);
//_arg10就是呼叫copyFrom的那個outSurface,那怎麼傳到客戶端呢?
if((_arg10!=null)) {
reply.writeInt(1);
//呼叫Surface的writeToParcel,把資訊寫到reply包中。
//注意最後一個引數為PARCELABLE_WRITE_RETURN_VALUE
_arg10.writeToParcel(reply,
android.os.Parcelable.PARCELABLE_WRITE_RETURN_VALUE);
}
}
......
returntrue;
}
看完這個,會讓人有點毛骨悚然。我最開始一直在JNI檔案中尋找大挪移的蹤跡,但有幾個關鍵點始終不能明白,萬不得已就使用了這個aidl必殺技,於是終於揭露出其真相了。
3. 乾坤大挪移的真相
這裡,總結一下乾坤大挪移的整個過程,如圖8-8表示:
圖8-8 乾坤大挪移的真面目
上圖非常清晰地列出了乾坤大挪移的過程,我們可結合程式碼來加深理解。
注意,這裡,將BpWindowSession作為了IWindowSessionBinder在客戶端的代表。
8.3.3 分析乾坤大挪移的JNI層
前文講述的內容都集中在Java層,下面要按照流程順序分析JNI層的內容。
1. Surface的無參構造分析
在JNI層,第一個被呼叫的是Surface的無參建構函式,其程式碼如下所示:
[-->Surface.java]
public Surface() {
......
//CompatibleCanvas從Canvas類派生
mCanvas = new CompatibleCanvas();
}
Canvas是什麼?根據SDK文件的介紹可知,畫圖需要“四大金剛”相互合作,這四大金剛是:
· Bitmap:用於儲存畫素,也就是畫布。可把它當做一塊資料儲存區域。
· Canvas:用於記載畫圖的動作,比如畫一個圓,畫一個矩形等。Canvas類提供了這些基本的繪圖函式。
· Drawing primitive:繪圖基元,例如矩形、圓、弧線、文字、圖片等。
· Paint:它用來描述繪畫時使用的顏色、風格(如實線、虛線等)等。
在一般情況下,Canvas會封裝一塊Bitmap,而作圖就是基於這塊Bitmap的。前面說的畫布,其實指的就是Canvas中的這塊Bitmap。
這些知識稍瞭解即可,不必去深究。Surface的無參建構函式沒有什麼有價值的內容,接著看下面的內容。
2. SurfaceSession的構造
現在要分析的是SurfaceSession,其建構函式如下所示:
[-->SurfaceSession.java]
public SurfaceSession() {
init();//這是一個native函式
}
init是一個native函式。去看看它的JNI實現,它在android_view_Surface.cpp中,程式碼如下所示:
[-->android_view_Surface.cpp]
static void SurfaceSession_init(JNIEnv* env,jobject clazz)
{
//建立一個SurfaceComposerClient物件
sp<SurfaceComposerClient> client = new SurfaceComposerClient;
client->incStrong(clazz);
//在Java物件中儲存這個client物件的指標,型別為SurfaceComposerClient
env->SetIntField(clazz, sso.client, (int)client.get());
}
這裡先不討論SurfaceComposerClient的內容,擬繼續把乾坤大挪移的流程走完。
3. Surface的有參構造
下一個呼叫的是Surface的有參構造,其引數中有一個SurfaceSession。先看Java層的程式碼,如下所示:
[-->Surface.java]
publicSurface(SurfaceSession s,//傳入一個SurfaceSession物件
int pid, String name, int display, int w, int h, int format, int flags)
throws OutOfResourcesException {
......
mCanvas = new CompatibleCanvas();
//又一個native函式,注意傳遞的引數:display以後再說,w,h代表繪圖區域的寬高值
init(s,pid,name,display,w,h,format,flags);
mName = name;
}
Surface的native init函式的JNI實現,也在android_view_Surface.cpp中,一起來看:
[-->android_view_Surface.cpp]
static void Surface_init(
JNIEnv*env, jobject clazz,
jobject session,
jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jintflags)
{
//從SurfaceSession物件中取出之前建立的那個SurfaceComposerClient物件
SurfaceComposerClient* client =
(SurfaceComposerClient*)env->GetIntField(session, sso.client);
sp<SurfaceControl> surface;//注意它的型別是SurfaceControl
if (jname == NULL) {
/*
呼叫SurfaceComposerClient的createSurface函式,返回的surface是一個
SurfaceControl型別。
*/
surface = client->createSurface(pid, dpy, w, h, format, flags);
} else{
......
}
//把這個surfaceControl物件設定到Java層的Surface物件中,對這個函式就不再分析了
setSurfaceControl(env, clazz, surface);
}
4. copyFrom的分析
現在要分析的就是copyFrom了。它就是一個native函式。看它的JNI層程式碼:
[-->android_view_Surface.cpp]
static void Surface_copyFrom(JNIEnv* env,jobject clazz, jobject other)
{
//根據JNI函式的規則,clazz是copyFrom的呼叫物件,而other是copyFrom的引數。
//目標物件此時還沒有設定SurfaceControl,而源物件在前面已經建立了SurfaceControl
constsp<SurfaceControl>& surface = getSurfaceControl(env, clazz);
constsp<SurfaceControl>& rhs = getSurfaceControl(env, other);
if (!SurfaceControl::isSameSurface(surface, rhs)) {
//把源SurfaceControl物件設定到目標Surface中。
setSurfaceControl(env, clazz, rhs);
}
}
這一步還是比較簡單的,下面看第五步writeToParcel函式的呼叫。
5. writeToParcel的分析
多虧了必殺技aidl工具的幫忙,才挖出這個隱藏的writeToParcel函式呼叫,下面就來看看它,程式碼如下所示:
[-->android_view_Surface.cpp]
static void Surface_writeToParcel(JNIEnv* env,jobject clazz,
jobject argParcel, jint flags)
{
Parcel* parcel = (Parcel*)env->GetIntField(argParcel, no.native_parcel);
//clazz就是Surface物件,從這個Surface物件中取出儲存的SurfaceControl物件
const sp<SurfaceControl>&control(getSurfaceControl(env, clazz));
/*
把SurfaceControl中的資訊寫到Parcel包中,然後利用Binder通訊傳遞到對端,
對端通過readFromParcel來處理Parcel包。
*/
SurfaceControl::writeSurfaceToParcel(control, parcel);
if (flags & PARCELABLE_WRITE_RETURN_VALUE) {
//還記得PARCELABLE_WRITE_RETURN_VALUE嗎?flags的值就等於它
//所以本地Surface物件的SurfaceControl值被置空了
setSurfaceControl(env, clazz, 0);
}
}
6. readFromParcel的分析
再看作為客戶端的ViewRoot所呼叫的readFromParcel函式。它也是一個native函式,JNI層的程式碼如下所示:
[-->android_view_Surface.cpp]
static void Surface_readFromParcel(
JNIEnv* env, jobject clazz, jobject argParcel)
{
Parcel* parcel = (Parcel*)env->GetIntField( argParcel,no.native_parcel);
//注意下面定義的變數型別是Surface,而不是SurfaceControl
const sp<Surface>&control(getSurface(env, clazz));
//根據服務端傳遞的Parcel包來構造一個新的surface。
sp<Surface> rhs = new Surface(*parcel);
if (!Surface::isSameSurface(control, rhs)) {
//把這個新surface賦給ViewRoot中的mSurface物件。
setSurface(env,clazz, rhs);
}
}
7. Surface乾坤大挪移的小結
可能有人會問,乾坤大挪移怎麼這麼複雜?這期間出現了多少物件?來總結一下,在此期間一共有三個關鍵物件(注意我們這裡只考慮JNI層的Native物件),它們分別是:
· SurfaceComposerClient。
· SurfaceControl。
· Surface,這個Surface物件屬於Native層,和Java層的Surface相對應。
其中轉移到ViewRoot成員變數mSurface中的,就是最後這個Surface物件了。這一路走來,真是異常坎坷。來回顧並概括總結一下這段歷程。至於它的作用應該是很清楚了。以後要破解SurfaceFlinger,靠的就是這個精簡的流程。
· 建立一個SurfaceComposerClient。
· 呼叫SurfaceComposerClient的createSurface得到一個SurfaceControl物件。
· 呼叫SurfaceControl的writeToParcel把一些資訊寫到Parcel包中。
· 根據Parcel包的資訊構造一個Surface物件。這個Surface物件儲存到Java層的mSurface物件中。這樣,大挪移的結果是ViewRoot得到一個Native的Surface物件。
精簡流程後,寥寥數語就可把過程說清楚。以後我們在研究程式碼時,也可以採取這種方式。
這個Surface物件非常重要,可它到底有什麼用呢?這正是下一節要講的內容。
8.3.4 Surface和畫圖
下面,來看最後兩個和Surface相關的函式呼叫:一個是lockCanvas;另外一個是unlockCanvasAndPost。
1. lockCanvas的分析
要對lockCanvas進行分析,須先來看Java層的函式,程式碼如下所示:
[-->Surface.java::lockCanvas()]
public Canvas lockCanvas(Rect dirty)
throws OutOfResourcesException,IllegalArgumentException
{
return lockCanvasNative(dirty);//呼叫native的lockCanvasNative函式。
}
[-->android_view_Surface.cpp::Surface_lockCanvas()]
static jobject Surface_lockCanvas(JNIEnv* env,jobject clazz, jobject dirtyRect)
{
//從Java中的Surface物件中,取出費盡千辛萬苦得到的Native的Surface物件
constsp<Surface>& surface(getSurface(env, clazz));
......
// dirtyRect表示需要重繪的矩形塊,下面根據這個dirtyRect設定dirtyRegion
RegiondirtyRegion;
if(dirtyRect) {
Rect dirty;
dirty.left =env->GetIntField(dirtyRect, ro.l);
dirty.top =env->GetIntField(dirtyRect, ro.t);
dirty.right = env->GetIntField(dirtyRect, ro.r);
dirty.bottom=env->GetIntField(dirtyRect, ro.b);
if(!dirty.isEmpty()) {
dirtyRegion.set(dirty);
}
} else{
dirtyRegion.set(Rect(0x3FFF,0x3FFF));
}
//呼叫NativeSurface物件的lock函式,
//傳入了一個引數Surface::SurfaceInfo info和一塊表示髒區域的dirtyRegion
Surface::SurfaceInfo info;
status_t err = surface->lock(&info, &dirtyRegion);
......
//Java的Surface物件構造的時候會建立一個CompatibleCanvas。
//這裡就取出這個CompatibleCanvas物件
jobject canvas = env->GetObjectField(clazz, so.canvas);
env->SetIntField(canvas, co.surfaceFormat, info.format);
//從Canvas物件中取出SkCanvas物件
SkCanvas* nativeCanvas =(SkCanvas*)env->GetIntField(
canvas, no.native_canvas);
SkBitmap bitmap;
ssize_t bpr = info.s *bytesPerPixel(info.format);
bitmap.setConfig(convertPixelFormat(info.format), info.w, info.h, bpr);
......
if (info.w > 0 && info.h > 0) {
//info.bits指向一塊儲存區域。
bitmap.setPixels(info.bits);
} else{
bitmap.setPixels(NULL);
}
//給這個SkCanvas設定一個Bitmap,還記得前面說的,畫圖需要的四大金剛嗎?
//這裡將Bitmap設定到這個Canvas中,這樣進UI繪畫時就有畫布了。
nativeCanvas->setBitmapDevice(bitmap);
......
returncanvas;
}
lockCanvas還算比較簡單:
· 先獲得一塊儲存區域,然後將它和Canvas繫結到一起,這樣,UI繪畫的結果就記錄在這塊儲存區域裡了。
注意,本書不擬討論Android系統上Skia和OpenGL方面的知識,有興趣的讀者可自行研究。
接下來看unlockCanvasAndPost函式,它也是一個native函式:
2. unlockCanvasAndPost的分析
來看unlockCanvasAndPost的程式碼,如下所示:
[-->android_view_Surface.cpp]
static void Surface_unlockCanvasAndPost(JNIEnv*env, jobject clazz,
jobject argCanvas)
{
jobjectcanvas = env->GetObjectField(clazz, so.canvas);
//取出Native的Surface物件
const sp<Surface>& surface(getSurface(env,clazz));
//下面這些內容,不擬討論,讀者若有興趣,可結合Skia庫,自行研究。
SkCanvas* nativeCanvas =(SkCanvas*)env->GetIntField(canvas,
no.native_canvas);
intsaveCount = env->GetIntField(clazz, so.saveCount);
nativeCanvas->restoreToCount(saveCount);
nativeCanvas->setBitmapDevice(SkBitmap());
env->SetIntField(clazz, so.saveCount, 0);
//呼叫Surface物件的unlockAndPost函式。
status_t err = surface->unlockAndPost();
......
}
unlockCanvasAndPost也很簡單,這裡就不再多說了。
8.3.5 初識Surface總結
在本節的最後,我們來概括總結一下這一節所涉及到和Surface相關的呼叫流程,以備攻克下一個難關,如圖8-9所示 :
圖8-9 Surface的精簡流程圖
8.4 深入分析Surface
這一節,擬基於圖8-9中的流程,對Surface進行深入分析。在分析之前,還需要介紹一些Android平臺上圖形/影象顯示方面的知識,這裡統稱之為與Surface相關的基礎知識。
8.4.1 與Surface相關的基礎知識介紹
1. 顯示層(Layer)和螢幕組成
你瞭解螢幕顯示的漂亮介面是如何組織的嗎?來看圖8-10所展示的螢幕組成示意圖:
圖8-10 螢幕組成示意圖
從圖8-10中可以看出:
· 螢幕位於一個三維座標系中,其中Z軸從螢幕內指向螢幕外。
· 編號為①②③的矩形塊叫顯示層(Layer)。每一層有自己的屬性,例如顏色、透明度、所處螢幕的位置、寬、高等。除了屬性之外,每一層還有自己對應的顯示內容,也就是需要顯示的影象。
在Android中,Surface系統工作時,會由SurfaceFlinger對這些按照Z軸排好序的顯示層進行影象混合,混合後的影象就是在螢幕上看到的美妙畫面了。這種按Z軸排序的方式符合我們在日常生活中的體驗,例如前面的物體會遮擋住後面的物體。
注意,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把裝置中的視訊記憶體對映到使用者空間的,在這塊緩衝上寫資料,就相當於在螢幕上繪畫。
注意:上面所說的框架將引出另外一個概念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系統。
8.4.2 SurfaceComposerClient的分析
SurfaceComposerClient的出現是因為:
Java層SurfaceSession物件的建構函式會呼叫Native的SurfaceSession_init函式,而該函式的主要目的就是建立SurfaceComposerClient。
先回顧一下SurfaceSession_init函式,程式碼如下所示:
[-->android_view_Surface.cpp]
static void SurfaceSession_init(JNIEnv* env,jobject clazz)
{
//new 一個SurfaceComposerClient物件
sp<SurfaceComposerClient> client = newSurfaceComposerClient;
//sp的使用也有讓人煩惱的地方,有時需要顯式地增加強弱引用計數,要是忘記,可就麻煩了
client->incStrong(clazz);
env->SetIntField(clazz, sso.client,(int)client.get());
}
上面程式碼中,顯式地構造了一個SurfaceComposerClient物件。接下來看它是何方神聖。
1. 建立SurfaceComposerClient
SurfaceComposerClient這個名字隱含的意思是:
這個物件會和SurfaceFlinger進行互動,因為SurfaceFlinger派生於SurfaceComposer。
通過它的建構函式來看是否是這樣的。程式碼如下所示:
[-->SurfaceComposerClient.cpp]
SurfaceComposerClient::SurfaceComposerClient()
{
//getComposerService()將返回SF的Binder代理端的BpSurfaceFlinger物件
sp<ISurfaceComposer> sm(getComposerService());
//先呼叫SF的createConnection,再呼叫_init
_init(sm, sm->createConnection());
if(mClient != 0) {
Mutex::Autolock _l(gLock);
//gActiveConnections是全域性變數,把剛才建立的client儲存到這個map中去
gActiveConnections.add(mClient->asBinder(), this);
}
}
果然如此,SurfaceComposerClient建立了和SF的互動通道,下面直接轉到SF的createConnection函式去觀察。
(1)createConnection的分析
直接看程式碼,如下所示:
[-->SurfaceFlinger.cpp]
sp<ISurfaceFlingerClient>SurfaceFlinger::createConnection()
{
Mutex::Autolock _l(mStateLock);
uint32_t token = mTokens.acquire();
//先建立一個Client。
sp<Client> client = new Client(token, this);
//把這個Client物件儲存到mClientsMap中,token是它的標識。
status_t err = mClientsMap.add(token, client);
/*
建立一個用於Binder通訊的BClient,BClient派生於ISurfaceFlingerClient,
它的作用是接受客戶端的請求,然後把處理提交給SF,注意,並不是提交給Client。
Client會建立一塊共享記憶體,該記憶體由getControlBlockMemory函式返回
*/
sp<BClient> bclient =
new BClient(this, token,client->getControlBlockMemory());
returnbclient;
}
上面程式碼中提到,Client會建立一塊共享記憶體。熟悉Audio的讀者或許會認為,這可能是Surface的ControlBlock物件了!是的。CB物件在協調生產/消費步調時,起到了決定性的控制作用,所以非常重要,下面來看:
[-->SurfaceFlinger.cpp]
Client::Client(ClientID clientID, constsp<SurfaceFlinger>& flinger)
:ctrlblk(0), cid(clientID), mPid(0), mBitmap(0), mFlinger(flinger)
{
const int pgsize = getpagesize();
//下面這個操作會使cblksize為頁的大小,目前是4096位元組。
constint cblksize = ((sizeof(SharedClient)+(pgsize-1))&~(pgsize-1));
//MemoryHeapBase是我們的老朋友了,不熟悉的讀者可以回顧Audio系統中所介紹的內容
mCblkHeap = new MemoryHeapBase(cblksize, 0,
"SurfaceFlinger Clientcontrol-block");
ctrlblk = static_cast<SharedClient *>(mCblkHeap->getBase());
if(ctrlblk) {
new(ctrlblk) SharedClient; //再一次覺得眼熟吧?使用了placement new
}
}
原來,Surface的CB物件就是在共享記憶體中建立的這個SharedClient物件。先來認識一下這個SharedClient。
(2)SharedClient的分析
SharedClient定義了一些成員變數,程式碼如下所示:
class SharedClient
{
public:
SharedClient();
~SharedClient();
status_t validate(size_t token) const;
uint32_t getIdentity(size_t token) const;//取出標識本Client的token
private:
Mutexlock;
Condition cv; //支援跨程式的同步物件
//NUM_LAYERS_MAX為31,SharedBufferStack是什麼?
SharedBufferStack surfaces[ NUM_LAYERS_MAX ];
};
//SharedClient的建構函式,沒什麼新意,不如Audio的CB物件複雜
SharedClient::SharedClient()
:lock(Mutex::SHARED), cv(Condition::SHARED)
{
}
SharedClient的定義似乎簡單到極致了,不過不要高興得過早,在這個SharedClient的定義中,沒有發現和讀寫控制相關的變數,那怎麼控制讀寫呢?
答案就在看起來很彆扭的SharedBufferStack陣列中,它有31個元素。關於它的作用就不必賣關子了,答案是:
一個Client最多支援31個顯示層。每一個顯示層的生產/消費步調都由會對應的SharedBufferStack來控制。而它內部就用了幾個成員變數來控制讀寫位置。
認識一下SharedBufferStack的這幾個控制變數,如下所示:
[-->SharedBufferStack.h]
class SharedBufferStack{
......
//Buffer是按塊使用的,每個Buffer都有自己的編號,其實就是陣列中的索引號。
volatile int32_t head; //FrontBuffer的編號
volatile int32_t available; //空閒Buffer的個數
volatile int32_t queued; //髒Buffer的個數,髒Buffer表示有新資料的Buffer
volatile int32_t inUse; //SF當前正在使用的Buffer的編號
volatilestatus_t status; //狀態碼
......
}
注意,上面定義的SharedBufferStack是一個通用的控制結構,而不僅是針對於只有兩個Buffer的情況。根據前面介紹的PageFlipping知識,如果只有兩個FB,那麼,SharedBufferStack的控制就比較簡單了:
要麼SF讀1號Buffer,客戶端寫0號Buffer,要麼SF讀0號Buffer,客戶端寫1號Buffer。
圖8-13是展示了SharedClient的示意圖:
圖8-13 SharedClient的示意圖
從上圖可知:
· SF的一個Client分配一個跨程式共享的SharedClient物件。這個物件有31個SharedBufferStack元素,每一個SharedBufferStack對應於一個顯示層。
· 一個顯示層將建立兩個Buffer,後續的PageFlipping就是基於這兩個Buffer展開的。
另外,每一個顯示層中,其資料的生產和消費並不是直接使用SharedClient物件來進行具體控制的,而是基於SharedBufferServer和SharedBufferClient兩個結構,由這兩個結構來對該顯示層使用的SharedBufferStack進行操作,這些內容在以後的分析中還會碰到。
注意,這裡的顯示層指的是Normal型別的顯示層。
來接著分析後面的_init函式。
(3)_init函式的分析
先回顧一下之前的呼叫,程式碼如下所示:
[-->SurfaceComposerClient.cpp]
SurfaceComposerClient::SurfaceComposerClient()
{
......
_init(sm, sm->createConnection());
......
}
來看這個_init函式,程式碼如下所示:
[-->SurfaceComposerClient.cpp]
void SurfaceComposerClient::_init(
const sp<ISurfaceComposer>& sm, constsp<ISurfaceFlingerClient>& conn)
{
mPrebuiltLayerState = 0;
mTransactionOpen = 0;
mStatus = NO_ERROR;
mControl = 0;
mClient = conn;//mClient就是BClient的客戶端
mControlMemory =mClient->getControlBlock();
mSignalServer = sm;// mSignalServer就是BpSurfaceFlinger
//mControl就是那個建立於共享記憶體之中的SharedClient
mControl = static_cast<SharedClient*>(mControlMemory->getBase());
}
_init函式的作用,就是初始化SurfaceComposerClient中的一些成員變數。最重要的是得到了三個成員:
· mSignalServer ,它其實是SurfaceFlinger在客戶端的代理BpSurfaceFlinger,它的主要作用是,在客戶端更新完BackBuffer後(也就是重新整理了介面後),通知SF進行PageFlipping和輸出等工作。
· mControl,它是跨程式共享的SharedClient,是Surface系統的ControlBlock物件。
· mClient,它是BClient在客戶端的對應物。
2. 到底有多少種物件?
這一節,出現了好幾種型別的物件,通過圖8-14來看看它們:
圖8-14 類之間關係展示圖
從上圖中可以看出:
· SurfaceFlinger是從Thread派生的,所以它會有一個單獨執行的工作執行緒。
· BClient和SF之間採用了Proxy模式,BClient支援Binder通訊,它接收客戶端的請求,並派發給SF執行。
· SharedClient構建於一塊共享記憶體中,SurfaceComposerClient和Client物件均持有這塊共享記憶體。
在精簡流程中,關於SurfaceComposerClient就分析到這裡,下面分析第二個步驟中的SurfaceControl物件。
8.4.3 SurfaceControl的分析
1. SurfaceControl的來歷
根據精簡的流程可知,這一節要分析的是SurfaceControl物件。先回顧一下這個物件的建立過程,程式碼如下所示:
[-->android_view_Surface.cpp]
static void Surface_init(JNIEnv* env, jobjectclazz, jobject session,
jint pid, jstring jname, jint dpy, jint w, jint h, jint format, jintflags)
{
SurfaceComposerClient* client =
(SurfaceComposerClient*)env->GetIntField(session, sso.client);
//注意這個變數,型別是SurfaceControl,名字卻叫surface,稍不留神就出錯了。
sp<SurfaceControl>surface;
if (jname == NULL) {
//呼叫Client的createSurface函式,得到一個SurfaceControl物件。
surface= client->createSurface(pid, dpy, w, h, format, flags);
}
......
//將這個SurfaceControl物件設定到Java層的物件中儲存。
setSurfaceControl(env, clazz, surface);
}
通過上面的程式碼可知,SurfaceControl物件由createSurface得來,下面看看這個函式。
此時,讀者或許會被程式碼中隨意起的變數名搞糊塗,因為我的處理方法碰到了容易混淆的地方,儘量以物件型別來表示這個物件。
(1)分析createSurface的請求端
在createSurface內部會使用Binder通訊將請求發給SF,所以它分為請求和響應兩端,先看請求端,程式碼如下所示:
[-->SurfaceComposerClient.cpp]
sp<SurfaceControl>SurfaceComposerClient::createSurface(
int pid,
DisplayID display,//DisplayID是什麼意思?
uint32_t w,
uint32_t h,
PixelFormat format,
uint32_t flags)
{
String8 name;
constsize_t SIZE = 128;
charbuffer[SIZE];
snprintf(buffer, SIZE, "<pid_%d>", getpid());
name.append(buffer);
//呼叫另外一個createSurface,多一個name引數
returnSurfaceComposerClient::createSurface(pid, name, display,
w, h, format, flags);
}
在分析另外一個createSurface之前,應先介紹一下DisplayID的含義:
typedef int32_t DisplayID;
DisplayID是一個int整型,它的意義是螢幕編號,例如雙屏手機就有內屏和外屏兩塊螢幕。由於目前Android的Surface系統只支援一塊螢幕,所以這個變數的取值都是0。
再分析另外一個createSurface函式,它的程式碼如下所示:
[-->SurfaceComposerClient.cpp]
sp<SurfaceControl>SurfaceComposerClient::createSurface(
int pid,const String8& name,DisplayID display,uint32_t w,
uint32_t h,PixelFormat format,uint32_t flags)
{
sp<SurfaceControl> result;
if(mStatus == NO_ERROR) {
ISurfaceFlingerClient::surface_data_t data;
//呼叫BpSurfaceFlingerClient的createSurface函式
sp<ISurface> surface = mClient->createSurface(&data, pid,name,
display, w, h,format, flags);
if(surface != 0) {
if (uint32_t(data.token) < NUM_LAYERS_MAX) {
//以返回的ISurface物件建立一個SurfaceControl物件
result = new SurfaceControl(this, surface, data, w, h,
format, flags);
}
}
}
returnresult;//返回的是SurfaceControl物件
}
請求端的處理比較簡單:
· 呼叫跨程式的createSurface函式,得到一個ISurface物件,根據Binder一章的知識可知,這個物件的真實型別是BpSurface。不過以後統稱之為ISurface。
· 以這個ISurface物件為引數,構造一個SurfaceControl物件。
createSurface函式的響應端在SurfaceFlinger程式中,下面去看這個函式。
在Surface系統定義了很多型別,我們們也中途休息一下,不妨來看看和字串“Surface”有關的有多少個類,權當其為小小的娛樂:
Native層有Surface、ISurface、SurfaceControl、SurfaceComposerClient。
Java層有Surface、SurfaceSession。
上面列出的還只是一部分,後面還有呢!*&@&*%¥*
(2)分析createSurface的響應端
前面講過,可把BClient看作是SF的Proxy,它會把來自客戶端的請求派發給SF處理,通過程式碼來看看,是不是這樣的?如下所示:
[-->SurfaceFlinger.cpp]
sp<ISurface> BClient::createSurface(
ISurfaceFlingerClient::surface_data_t* params, int pid,
const String8& name,
DisplayID display, uint32_t w, uint32_t h, PixelFormat format,
uint32_t flags)
{
//果然是交給SF處理,以後我們將跳過BClient這個代理。
return mFlinger->createSurface(mId, pid,name, params, display, w, h,
format, flags);
}
來看createSurface函式,它的目的就是建立一個ISurface物件,不過這中間的玄機還挺多,程式碼如下所示:
[-->SurfaceFlinger.cpp]
sp<ISurface>SurfaceFlinger::createSurface(ClientID clientId, int pid,
const String8& name, ISurfaceFlingerClient::surface_data_t* params,
DisplayID d, uint32_t w, uint32_t h, PixelFormat format,
uint32_t flags)
{
sp<LayerBaseClient> layer;//LayerBaseClient是Layer家族的基類
//這裡又冒出一個LayerBaseClient的內部類,它也叫Surface,是不是有點頭暈了?
sp<LayerBaseClient::Surface> surfaceHandle;
Mutex::Autolock _l(mStateLock);
//根據clientId找到createConnection時加入的那個Client物件
sp<Client> client = mClientsMap.valueFor(clientId);
......
//注意這個id,它的值表示Client建立的是第幾個顯示層,根據圖8-14可以看出,這個id
//同時也表示將使用SharedBufferStatck陣列的第id個元素。
int32_t id = client->generateId(pid);
//一個Client不能建立多於NUM_LAYERS_MAX個的Layer。
if(uint32_t(id) >= NUM_LAYERS_MAX) {
return surfaceHandle;
}
//根據flags引數來建立不同型別的顯示層,我們在8.4.1節介紹過相關知識
switch(flags & eFXSurfaceMask) {
case eFXSurfaceNormal:
if (UNLIKELY(flags & ePushBuffers)) {
//建立PushBuffer型別的顯示層,我們將在擴充思考部分分析它
layer = createPushBuffersSurfaceLocked(client, d, id,
w, h, flags);
} else {
//①建立Normal型別的顯示層,我們分析待會這個
layer = createNormalSurfaceLocked(client, d, id,
w, h, flags, format);
}
break;
case eFXSurfaceBlur:
//建立Blur型別的顯示層
layer = createBlurSurfaceLocked(client, d, id, w, h, flags);
break;
case eFXSurfaceDim:
//建立Dim型別的顯示層
layer = createDimSurfaceLocked(client, d, id, w, h, flags);
break;
}
if(layer != 0) {
layer->setName(name);
setTransactionFlags(eTransactionNeeded);
//從顯示層物件中取出一個ISurface物件賦值給SurfaceHandle
surfaceHandle = layer->getSurface();
if(surfaceHandle != 0) {
params->token = surfaceHandle->getToken();
params->identity = surfaceHandle->getIdentity();
params->width = w;
params->height = h;
params->format = format;
}
}
returnsurfaceHandle;//ISurface的Bn端就是這個物件。
}
上面程式碼中的函式倒是很簡單,知識程式碼裡面冒出來的幾個新型別和它們的名字卻讓人有點頭暈。先用文字總結一下:
· LayerBaseClient:前面提到的顯示層在程式碼中的對應物,就是這個LayerBaseClient,不過這是一個大家族,不同型別的顯示層將建立不同型別的LayerBaseClient。
· LayerBaseClient中有一個內部類,名字叫Surface,這是一個支援Binder通訊的類,它派生於ISurface。
關於Layer的故事,後面會有單獨的章節來介紹。這裡先繼續分析createNormalSurfaceLocked函式。它的程式碼如下所示:
[-->SurfaceFlinger.cpp]
sp<LayerBaseClient>SurfaceFlinger::createNormalSurfaceLocked(
const sp<Client>& client, DisplayID display,
int32_t id, uint32_t w, uint32_t h, uint32_t flags,
PixelFormat& format)
{
switch(format) { //一些影象方面的引數設定,可以不去管它。
casePIXEL_FORMAT_TRANSPARENT:
casePIXEL_FORMAT_TRANSLUCENT:
format = PIXEL_FORMAT_RGBA_8888;
break;
casePIXEL_FORMAT_OPAQUE:
format = PIXEL_FORMAT_RGB_565;
break;
}
//①建立一個Layer型別的物件
sp<Layer> layer = new Layer(this, display,client, id);
//②設定Buffer
status_t err = layer->setBuffers(w, h, format, flags);
if (LIKELY(err == NO_ERROR)) {
//初始化這個新layer的一些狀態
layer->initStates(w, h, flags);
//③ 還記得在圖8-10中提到的Z軸嗎?下面這個函式把這個layer加入到Z軸大軍中。
addLayer_l(layer);
}
......
returnlayer;
}
createNormalSurfaceLocked函式有三個關鍵點,它們是:
· 構造一個Layer物件。
· 呼叫Layer物件的setBuffers函式。
· 呼叫SF的addLayer_l函式。
暫且記住這三個關鍵點,後文有單獨章節分析它們。先繼續分析SurfaceControl的流程。
(3)建立SurfaceControl物件
當跨程式的createSurface呼叫返回一個ISurface物件時,將通過下面的程式碼建立一個SurfaceControl物件:
result = new SurfaceControl(this, surface, data,w, h,format, flags);
下面來看這個SurfaceControl物件為何物。它的程式碼如下所示:
[-->SurfaceControl.cpp]
SurfaceControl::SurfaceControl(
const sp<SurfaceComposerClient>& client,
const sp<ISurface>& surface,
const ISurfaceFlingerClient::surface_data_t& data,
uint32_t w, uint32_t h, PixelFormat format, uint32_t flags)
//mClient為SurfaceComposerClient,而mSurface指向跨程式createSurface呼叫
//返回的ISurface物件。
:mClient(client), mSurface(surface),
mToken(data.token), mIdentity(data.identity),
mWidth(data.width), mHeight(data.height), mFormat(data.format),
mFlags(flags)
{
}
SurfaceControl類可以看作是一個wrapper類:
它封裝了一些函式,通過這些函式可以方便地呼叫mClient或ISurface提供的函式。
在SurfaceControl的分析過程中,還遺留了和Layer相關的部分,下面就來解決它們。
2. Layer和它的家族
我們在createSurface中建立的是Normal的Layer,下面先看這個Layer的建構函式。
(1)Layer的構造
Layer是從LayerBaseClient派生的,其程式碼如下所示:
[-->Layer.cpp]
Layer::Layer(SurfaceFlinger* flinger, DisplayIDdisplay,
const sp<Client>& c, int32_t i)//這個i表示SharedBufferStack陣列的索引
: LayerBaseClient(flinger, display, c, i),//先呼叫基類建構函式
mSecure(false),
mNoEGLImageForSwBuffers(false),
mNeedsBlending(true),
mNeedsDithering(false)
{
//getFrontBuffer實際取出的是FrontBuffer的位置
mFrontBufferIndex = lcblk->getFrontBuffer();
}
再來看基類LayerBaseClient的建構函式,程式碼如下所示:
[-->LayerBaseClient.cpp]
LayerBaseClient::LayerBaseClient(SurfaceFlinger*flinger, DisplayID display,
const sp<Client>& client, int32_t i)
:LayerBase(flinger, display), lcblk(NULL), client(client), mIndex(i),
mIdentity(uint32_t(android_atomic_inc(&sIdentity)))
{
/*
建立一個SharedBufferServer物件,注意它使用了SharedClient物件,
並且傳入了表示SharedBufferStack陣列索引的i和一個常量NUM_BUFFERS
*/
lcblk = new SharedBufferServer(
client->ctrlblk, i, NUM_BUFFERS,//該值為常量2,在Layer.h中定義
mIdentity);
}
SharedBufferServer是什麼?它和SharedClient有什麼關係?
其實,之前在介紹SharedClient時曾提過與此相關的內容,這裡再來認識一下,先看圖8-15:
圖8-15 ShardBufferServer的示意圖
根據上圖並結合前面的介紹,可以得出以下結論:
· 在SF程式中,Client的一個Layer將使用SharedBufferStack陣列中的一個成員,並通過SharedBufferServer結構來控制這個成員,我們知道SF是消費者,所以可由SharedBufferServer來控制資料的讀取。
· 與之相對應,客戶端的程式也會有一個物件來使用這個SharedBufferStatck,可它是通過另外一個叫SharedBufferClient的結構來控制的。客戶端為SF提供資料,所以可由SharedBufferClient控制資料的寫入。在後文的分析中還會碰到SharedBufferClient。
注意,在擴充思考部分,會有單獨章節來分析生產/消費過程中的讀寫控制。
通過前面的程式碼可知,Layer物件被new出來後,傳給了一個sp物件,讀者還記得sp中的onFirstRef函式嗎?Layer家族在這個函式中還有一些處理。一起去看看,但這個函式由基類LayerBaseClient實現。
[-->LayerBase.cpp]
void LayerBaseClient::onFirstRef()
{
sp<Client> client(this->client.promote());
if (client != 0) {
//把自己加入client物件的mLayers陣列中,這部分內容比較簡單,讀者可以自行研究
client->bindLayer(this, mIndex);
}
}
好,Layer建立完畢,下面來看第二個重要的函式setBuffers。
(2)setBuffers的分析
setBuffers,Layer類以及Layer的基類都有實現。由於建立的是Layer型別的物件,所以請讀者直接到Layer.cpp中尋找setBuffers函式。這個函式的目的就是建立用於PageFlipping的FrontBuffer和BackBuffer。一起來看,程式碼如下所示:
[-->Layer.cpp]
status_t Layer::setBuffers( uint32_t w, uint32_th,
PixelFormat format,uint32_t flags)
{
PixelFormatInfo info;
status_t err = getPixelFormatInfo(format, &info);
if(err) return err;
//DisplayHardware是代表顯示裝置的HAL物件,0代表第一塊螢幕的顯示裝置。
//這裡將從HAL中取出一些和顯示相關的資訊。
constDisplayHardware& hw(graphicPlane(0).displayHardware());
uint32_t const maxSurfaceDims = min(
hw.getMaxTextureSize(), hw.getMaxViewportDims());
PixelFormatInfo displayInfo;
getPixelFormatInfo(hw.getFormat(),&displayInfo);
constuint32_t hwFlags = hw.getFlags();
......
/*
建立Buffer,這裡將建立兩個GraphicBuffer。這兩個GraphicBuffer就是我們前面
所說的FrontBuffer和BackBuffer。
*/
for (size_t i=0 ; i<NUM_BUFFERS ; i++) {
//注意,這裡呼叫的是GraphicBuffer的無參建構函式,mBuffers是一個二元陣列。
mBuffers[i] = new GraphicBuffer();
}
//又冒出來一個SurfaceLayer型別,#¥%……&*!@
mSurface = new SurfaceLayer(mFlinger, clientIndex(), this);
returnNO_ERROR;
}
setBuffers函式的工作內容比較簡單,就是:
· 建立一個GraphicBuffer緩衝陣列,元素個數為2,即FrontBuffer和BackBuffer。
· 建立一個SurfaceLayer,關於它的身世我們後續再介紹。
GraphicBuffer是Android提供的顯示記憶體管理類,關於它的故事,將在8.4.7節中介紹。我們暫把它當做普通的Buffer即可。
setBuffers中出現的SurfaceLayer類是什麼?讀者可能對此感覺有些暈乎。待把最後一個關鍵函式addLayer_l介紹完,或許就不太暈了。
(3)addLayer_l的分析
addLayer_l把這個新建立的layer加入自己的Z軸大軍,下面來看:
[-->SurfaceFlinger.cpp]
status_t SurfaceFlinger::addLayer_l(constsp<LayerBase>& layer)
{
/*
mCurrentState是SurfaceFlinger定義的一個結構,它有一個成員變數叫
layersSortedByZ,其實就是一個排序陣列。下面這個add函式將把這個新的layer按照
它在Z軸的位置加入到排序陣列中。mCurrentState儲存了所有的顯示層。
*/
ssize_t i = mCurrentState.layersSortedByZ.add(
layer,&LayerBase::compareCurrentStateZ);
sp<LayerBaseClient> lbc =
LayerBase::dynamicCast< LayerBaseClient*>(layer.get());
if(lbc != 0) {
mLayerMap.add(lbc->serverIndex(), lbc);
}
returnNO_ERROR;
}
對Layer的三個關鍵函式都已分析過了,下面正式介紹Layer家族。
(4)Layer家族的介紹
前面的內容確讓人頭暈眼花,現在應該幫大家恢復清晰的頭腦。先來“一劑猛藥”,見圖8-16:
圖8-16 Layer家族
通過上圖可知:
· LayerBaseClient從LayerBase類派生。
· LayerBaseClient還有四個派生類,分別是Layer、LayerBuffer、LayerDim和LayerBlur。
· LayerBaseClient定義了一個內部類Surface,這個Surface從ISurface類派生,它支援Binder通訊。
· 針對不同的型別,Layer和LayerBuffer分別有一個內部類SurfaceLayer和SurfaceLayerBuffer,它們繼承了LayerBaseClient的Surface類。所以對於Normal型別的顯示層來說,getSurface返回的ISurface物件的真正型別是SurfaceLayer。
· LayerDim和LayerBlur類沒有定義自己的內部類,所以對於這兩種型別的顯示層來說,它們直接使用了LayerBaseClient的Surface。
· ISurface介面提供了非常簡單的函式,如requestBuffer、postBuffer等。
這裡大量使用了內部類。我們知道,內部類最終都會把請求派發給外部類物件來處理,既然如此,在以後分析中,如果沒有特殊情況,就會直接跳到外部類的處理函式中。
強烈建議Google把Surface相關程式碼好好整理一下,至少讓型別名取得更直觀些,現在這樣確實有點讓人頭暈。好,來小小娛樂一下。看之前介紹的和“Surface”有關的名字:
Native層有Surface、ISurface、SurfaceControl、SurfaceComposerClient。
Java層有Surface、SurfaceSession。
在介紹完Layer家族後,與它相關的名字又多了幾個,它們是
LayerBaseClient::Surface、Layer::SurfaceLayer、LayerBuffer::SurfaceLayerBuffer。
3. SurfaceControl總結
SurfaceControl建立後得到了什麼呢?可用圖8-17來表示:
圖8-17 SurfaceControl建立後的結果圖
通過上圖可以知道:
· mClient成員變數指向SurfaceComposerClient。
· mSurface的Binder通訊響應端為SurfaceLayer。
· SurfaceLayer有一個變數mOwner指向它的外部類Layer,而Layer有一個成員變數mSurface指向SurfaceLayer。這個SurfaceLayer物件由getSurface函式返回。
注意,mOwner變數由SurfaceLayer的基類Surface(LayBaseClient的內部類)定義。
接下來就是writeToParcel分析和Native Surface物件的建立了。注意,這個Native的Surface可不是LayBaseClient的內部類Surface。
8.4.4 writeToParcel和Surface物件的建立
從乾坤大挪移的知識可知,前面建立的所有物件都在WindowManagerService所在的程式system_server中,而writeToParcel則需要把一些資訊打包到Parcel後,傳送到Activity所在的程式。到底哪些內容需要回傳給Activity所在的程式呢?
後文將Activity所在的程式簡稱為Activity端。
1. writeToParcel分析
writeToParcel比較簡單,就是把一些資訊寫到Parcel中去。程式碼如下所示:
[-->SurfaceControl.cpp]
status_t SurfaceControl::writeSurfaceToParcel(
const sp<SurfaceControl>& control, Parcel* parcel)
{
uint32_t flags = 0;
uint32_t format = 0;
SurfaceID token = -1;
uint32_t identity = 0;
uint32_t width = 0;
uint32_t height = 0;
sp<SurfaceComposerClient> client;
sp<ISurface> sur;
if(SurfaceControl::isValid(control)) {
token = control->mToken;
identity= control->mIdentity;
client = control->mClient;
sur = control->mSurface;
width = control->mWidth;
height = control->mHeight;
format = control->mFormat;
flags = control->mFlags;
}
//SurfaceComposerClient的資訊需要傳遞到Activity端,這樣客戶端那邊會構造一個
//SurfaceComposerClient物件
parcel->writeStrongBinder(client!=0 ? client->connection() : NULL);
//把ISurface物件資訊也寫到Parcel中,這樣Activity端那邊也會構造一個ISurface物件
parcel->writeStrongBinder(sur!=0?sur->asBinder(): NULL);
parcel->writeInt32(token);
parcel->writeInt32(identity);
parcel->writeInt32(width);
parcel->writeInt32(height);
parcel->writeInt32(format);
parcel->writeInt32(flags);
returnNO_ERROR;
}
Parce包發到Activity端後,readFromParcel將根據這個Parcel包構造一個Native的Surface物件,一起來看相關程式碼。
2. 分析Native的Surface建立過程
[-->android_view_Surface.cpp]
static void Surface_readFromParcel(
JNIEnv* env, jobject clazz, jobject argParcel)
{
Parcel* parcel = (Parcel*)env->GetIntField( argParcel, no.native_parcel);
const sp<Surface>& control(getSurface(env,clazz));
//根據服務端的parcel資訊來構造客戶端的Surface
sp<Surface> rhs = new Surface(*parcel);
if(!Surface::isSameSurface(control, rhs)) {
setSurface(env, clazz, rhs);
}
}
Native的Surface是怎麼利用這個Parcel包的?程式碼如下所示:
[-->Surface.cpp]
Surface::Surface(const Parcel& parcel)
:mBufferMapper(GraphicBufferMapper::get()),
mSharedBufferClient(NULL)
{
/*
Surface定義了一個mBuffers變數,它是一個sp<GraphicBuffer>的二元陣列,也就是說Surface也存在二個GraphicBuffer,而之前在建立Layer的時候也有兩個GraphicBuffer,難道一共有四個GraphicBuffer?這個問題,後面再解答。
*/
sp<IBinder> clientBinder =parcel.readStrongBinder();
//得到ISurface的Bp端BpSurface。
mSurface =interface_cast<ISurface>(parcel.readStrongBinder());
mToken = parcel.readInt32();
mIdentity = parcel.readInt32();
mWidth = parcel.readInt32();
mHeight = parcel.readInt32();
mFormat = parcel.readInt32();
mFlags = parcel.readInt32();
if (clientBinder != NULL) {
/*
根據ISurfaceFlingerClient物件構造一個SurfaceComposerClient物件,注意我們
現在位於Activity端,這裡還沒有建立SurfaceComposerClient物件,所以需要建立一個
*/
mClient = SurfaceComposerClient::clientForConnection(clientBinder);
//SharedBuffer家族的最後一員ShardBufferClient終於出現了。
mSharedBufferClient = new SharedBufferClient(
mClient->mControl, mToken, 2,mIdentity);
}
init();//做一些初始化工作。
}
在Surface建立完後,得到什麼了呢?看圖8-18就可知道:
圖8-18 Native Surface的示意圖
上圖很清晰地說明:
· ShardBuffer家族依託共享記憶體結構SharedClient與它共同組成了Surface系統生產/消費協調的中樞控制機構,它在SF端的代表是SharedBufferServer,在Activity端的代表是SharedBufferClient。
· Native的Surface將和SF中的SurfaceLayer建立Binder聯絡。
另外,圖中還特意畫出了承載資料的GraphicBuffer陣列,在程式碼的註釋中也針對GraphicBuffer提出了一個問題:Surface中有兩個GraphicBuffer,Layer也有兩個,一共就有四個GraphicBuffer了,可是為什麼這裡只畫出兩個呢?
答案是,我們們不是有共享記憶體嗎?這四個GraphicBuffer其實操縱的是同一段共享記憶體,所以為了簡單,就只畫了兩個GraphicBuffer。在8.4.7節再介紹GraphicBuffer的故事。
下面,來看中樞控制機構的SharedBuffer家族。
3. SharedBuffer家族介紹
(1)SharedBuffer家族成員
SharedBuffer是一個家族名稱,它包括多少成員呢?來看SharedBuffer的家族圖譜,如圖8-19所示:
圖8-19 SharedBuffer家族介紹
從上圖可以知道:
· XXXCondition、XXXUpdate等都是內部類,它們主要是用來更新讀寫位置的。不過這些操作,為什麼要通過類來封裝呢?因為SharedBuffer的很多操作都使用了C++中的Function Object(函式物件),而這些內部類的例項就是函式物件。函式物件是什麼?它怎麼使用?對此,在以後的分析中會介紹。
(2)SharedBuffer家族和SharedClient的關係
前面介紹過,SharedBufferServer和SharedBufferClient控制的其實只是SharedBufferStack陣列中的一個,下面通過SharedBufferBase的建構函式,來看是否如此。
[-->SharedBufferStack.cpp]
SharedBufferBase::SharedBufferBase(SharedClient*sharedClient,
int surface, int num, int32_t identity)
: mSharedClient(sharedClient),
mSharedStack(sharedClient->surfaces+ surface),
mNumBuffers(num), //根據前面PageFlipping的知識可知,num值為2
mIdentity(identity)
{
/*
上面的賦值語句中最重要的是第二句:
mSharedStack(sharedClient->surfaces +surface)
這條語句使得這個SharedBufferXXX物件,和SharedClient中SharedBufferStack陣列
的第surface個元素建立了關係
*/
}
4. Native Surface總結
至此,Activity端Java的Surface物件,終於和一個Native Surface物件掛上了鉤,並且這個Native Surface還準備好了繪圖所需的一切,其中包括:
· 兩個GraphicBuffer,這就是PageFlipping所需要的FrontBuffer和BackBuffer。
· SharedBufferServer和SharedBufferClient結構,這兩個結構將用於生產/消費的過程控制。
· 一個ISurface物件,這個物件連線著SF中的一個SurfaceLayer物件。
· 一個SurfaceComposerClient物件,這個物件連線著SF中的一個BClient物件。
資源都已經準備好了,可以開始繪製UI了。下面,分析兩個關鍵的函式lockCanvas和unlockCanvasAndPost。
8.4.5 lockCanvas和unlockCanvasAndPost的分析
這一節,分析精簡流程中的最後兩個函式lockCanvas和unlockCanvasAndPost。
1. lockCanvas分析
據前文分析可知,UI在繪製前都需要通過lockCanvas得到一塊儲存空間,也就是所說的BackBuffer。這個過程中最終會呼叫Surface的lock函式。其程式碼如下所示:
[-->Surface.cpp]
status_t Surface::lock(SurfaceInfo* other,Region* dirtyIn, bool blocking)
{
//傳入的引數中,other用來接收一些返回資訊,dirtyIn表示需要重繪的區域
......
if (mApiLock.tryLock() != NO_ERROR) {//多執行緒的情況下要鎖住
......
returnWOULD_BLOCK;
}
//設定usage標誌,這個標誌在GraphicBuffer分配緩衝時有指導作用
setUsage(GRALLOC_USAGE_SW_READ_OFTEN | GRALLOC_USAGE_SW_WRITE_OFTEN);
//定義一個GraphicBuffer,名字就叫backBuffer。
sp<GraphicBuffer>backBuffer;
//①還記得我們說的2個元素的緩衝佇列嗎?下面的dequeueBuffer將取出一個空閒緩衝
status_terr = dequeueBuffer(&backBuffer);
if (err== NO_ERROR) {
//② 鎖住這塊buffer
err = lockBuffer(backBuffer.get());
if(err == NO_ERROR) {
const Rect bounds(backBuffer->width, backBuffer->height);
Region scratch(bounds);
Region& newDirtyRegion(dirtyIn ? *dirtyIn : scratch);
......
//mPostedBuffer是上一次繪畫時使用的Buffer,也就是現在的frontBuffer
const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
if (frontBuffer !=0 &&
backBuffer->width ==frontBuffer->width &&
backBuffer->height == frontBuffer->height &&
!(mFlags & ISurfaceComposer::eDestroyBackbuffer))
{
const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));
if (!copyback.isEmpty() && frontBuffer!=0) {
/③把frontBuffer中的資料拷貝到BackBuffer中,這是為什麼?
copyBlt(backBuffer,frontBuffer, copyback);
}
}
mDirtyRegion = newDirtyRegion;
mOldDirtyRegion = newDirtyRegion;
void* vaddr;
//呼叫GraphicBuffer的lock得到一塊記憶體,記憶體地址被賦值給了vaddr,
//後續的作畫將在這塊記憶體上展開
status_t res = backBuffer->lock(
GRALLOC_USAGE_SW_READ_OFTEN |GRALLOC_USAGE_SW_WRITE_OFTEN,
newDirtyRegion.bounds(),&vaddr);
mLockedBuffer = backBuffer;
//other用來接收一些資訊。
other->w =backBuffer->width; //寬度資訊
other->h =backBuffer->height;
other->s =backBuffer->stride;
other->usage =backBuffer->usage;
other->format = backBuffer->format;
other->bits = vaddr; //最重要的是這個記憶體地址
}
}
mApiLock.unlock();
returnerr;
}
在上面的程式碼中,列出了三個關鍵點:
· 呼叫dequeueBuffer得到一個空閒緩衝,也可以叫空閒緩衝出隊。
· 呼叫lockBuffer。
· 呼叫copyBlt函式,把frontBuffer資料拷貝到backBuffer中,這是為什麼?
來分析這三個關鍵點。
(1)dequeueBuffer的分析
dequeueBuffer的目的很簡單,就是選取一個空閒的GraphicBuffer,其程式碼如下所示:
[-->Surface.cpp]
status_tSurface::dequeueBuffer(sp<GraphicBuffer>* buffer) {
android_native_buffer_t* out;
status_t err = dequeueBuffer(&out);//呼叫另外一個dequeueBuffer
if(err == NO_ERROR) {
*buffer = GraphicBuffer::getSelf(out);
}
returnerr;
}
這其中又呼叫了另外一個dequeueBuffer函式。它的程式碼如下所示:
[-->Surface.cpp]
intSurface::dequeueBuffer(android_native_buffer_t** buffer)
{
sp<SurfaceComposerClient> client(getClient());
//①呼叫SharedBufferClient的dequeue函式,它返回當前空閒的緩衝號
ssize_tbufIdx = mSharedBufferClient->dequeue();
const uint32_t usage(getUsage());
/*
mBuffers就是我們前面在Surface建立中介紹的那個二元sp<GraphicBuffer>陣列。
這裡定義的backBuffer是一個引用型別,也就是說如果修改backBuffer的資訊,
就相當於修改了mBuffers[bufIdx]
*/
const sp<GraphicBuffer>&backBuffer(mBuffers[bufIdx]);
//mBuffers定義的GraphicBuffer使用的也是無參建構函式,所以此時還沒有真實的儲存被建立
if(backBuffer == 0 || //第一次進來滿足backBuffer為空這個條件
((uint32_t(backBuffer->usage) & usage) != usage) ||
mSharedBufferClient->needNewBuffer(bufIdx))
{
//呼叫getBufferLocked,需要進去看看。
err = getBufferLocked(bufIdx, usage);
if(err == NO_ERROR) {
mWidth =uint32_t(backBuffer->width);
mHeight = uint32_t(backBuffer->height);
}
}
......
}
上面列出了一個關鍵點,就是SharedBufferClient的dequeue函式,暫且記住這個呼叫,後面會有單獨章節分析生產/消費步調控制。先看getBufferLocked函式,其程式碼如下所示:
[-->Surface.cpp]
tatus_t Surface::getBufferLocked(int index, intusage)
{
sp<ISurface> s(mSurface);
status_t err = NO_MEMORY;
//注意這個currentBuffer也被定義為引用型別
sp<GraphicBuffer>¤tBuffer(mBuffers[index]);
//終於用上了ISurface物件,呼叫它的requestBuffer得到指定索引index的Buffer
sp<GraphicBuffer> buffer =s->requestBuffer(index, usage);
if (buffer != 0) {
err = mSharedBufferClient->getStatus();
if(!err && buffer->handle != NULL) {
//getBufferMapper返回GraphicBufferMapper物件
//呼叫它的registerBuffer幹什麼?這個問題我們在8.4.7節回答
err = getBufferMapper().registerBuffer(buffer->handle);
if (err == NO_ERROR) {
//把requestBuffer得到的值賦給currentBuffer,由於currentBuffer是引用型別,
//實際上相當於mBuffers[index]=buffer
currentBuffer = buffer;
//設定currentBuffer的編號
currentBuffer->setIndex(index);
mNeedFullUpdate = true;
}
}else {
err = err<0 ? err : NO_MEMORY;
}
return err;
}
至此,getBufferLocked的目的,已比較清晰了:
· 呼叫ISurface的requestBuffer得到一個GraphicBuffer物件,這個GraphicBuffer物件被設定到本地的mBuffers陣列中。看來Surface定義的這兩個GraphicBuffer和Layer定義的兩個GraphicBuffer是有聯絡的,所以圖8-18中只畫了兩個GraphicBuffer。
我們已經知道,ISurface的Bn端實際上是定義在Layer.類中的SurfaceLayer,下面來看它實現的requestBuffer。由於SurfaceLayer是Layer的內部類,它的工作最終都會交給Layer來處理,所以這裡可直接看Layer的requestBuffer函式:
[-->Layer.cpp]
sp<GraphicBuffer> Layer::requestBuffer(intindex, int usage)
{
sp<GraphicBuffer> buffer;
sp<Client> ourClient(client.promote());
//lcblk就是那個SharedBufferServer物件,下面這個呼叫確保index號GraphicBuffer
//沒有被SF當做FrontBuffer使用。
status_t err = lcblk->assertReallocate(index);
......
if(err != NO_ERROR) {
return buffer;
}
uint32_t w, h;
{
Mutex::Autolock _l(mLock);
w= mWidth;
h= mHeight;
/*
mBuffers是SF端建立的一個二元陣列,這裡取出第index個元素,之前說過,
mBuffers使用的也是GraphicBuffer的無參建構函式,所以此時也沒有真實儲存被建立。
*/
buffer = mBuffers[index];
mBuffers[index].clear();
}
constuint32_t effectiveUsage = getEffectiveUsage(usage);
if(buffer!=0 && buffer->getStrongCount() == 1) {
//①分配物理儲存,後面會分析這個。
err = buffer->reallocate(w, h, mFormat, effectiveUsage);
} else{
buffer.clear();
//使用GraphicBuffer的有參構造,這也使得物理儲存被分配
buffer = new GraphicBuffer(w, h, mFormat, effectiveUsage);
err = buffer->initCheck();
}
......
if(err == NO_ERROR && buffer->handle != 0) {
Mutex::Autolock _l(mLock);
if(mWidth && mHeight) {
mBuffers[index] = buffer;
mTextures[index].dirty = true;
}else {
buffer.clear();
}
}
returnbuffer;//返回
}
不管怎樣,此時跨程式的這個requestBuffer返回的GraphicBuffer,已經和一塊物理儲存繫結到一起了。所以dequeueBuffer順利返回了它所需的東西。接下來則需呼叫lockBuffer。
(2)lockBuffer的分析
lockBuffer的程式碼如下所示:
[-->Surface.cpp]
int Surface::lockBuffer(android_native_buffer_t*buffer)
{
sp<SurfaceComposerClient> client(getClient());
status_t err = validate();
int32_t bufIdx = GraphicBuffer::getSelf(buffer)->getIndex();
err =mSharedBufferClient->lock(bufIdx); //呼叫SharedBufferClient的lock
return err;
}
來看這個lock函式:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::lock(int buf)
{
LockCondition condition(this, buf);//這個buf是BackBuffer的索引號
status_t err = waitForCondition(condition);
returnerr;
}
注意,給waitForCondition函式傳遞的是一個LockCondition型別的物件,前面所說的函式物件的作用將在這裡見識到,先看waitForCondition函式:
[-->SharedBufferStack.h]
template <typename T> //這是一個模板函式
status_t SharedBufferBase::waitForCondition(Tcondition)
{
constSharedBufferStack& stack( *mSharedStack );
SharedClient& client( *mSharedClient );
constnsecs_t TIMEOUT = s2ns(1);
Mutex::Autolock _l(client.lock);
while((condition()==false) && //注意這個condition()的用法
(stack.identity == mIdentity) &&
(stack.status == NO_ERROR))
{
status_t err = client.cv.waitRelative(client.lock, TIMEOUT);
if(CC_UNLIKELY(err != NO_ERROR)) {
if (err == TIMED_OUT) {
if (condition()) {//注意這個:condition(),condition是一個物件
break;
} else {
}
} else {
return err;
}
}
}
return(stack.identity != mIdentity) ? status_t(BAD_INDEX) : stack.status;
}
waitForCondition函式比較簡單,就是等待一個條件為真,這個條件是否滿足由condition()這條語句來判斷。但這個condition不是一個函式,而是一個物件,這又是怎麼回事?
這就是Funcition Object(函式物件)的概念。函式物件的本質是一個物件,不過是過載了操作符(),這和過載操作符+、-等沒什麼區別。可以把它當作是一個函式來看待。
為什麼需要函式物件呢?因為物件可以儲存資訊,所以呼叫這個物件的()函式就可以利用這個物件的資訊了。
來看condition物件的()函式。剛才傳進來的是LockCondition,它的()定義如下:
[-->SharedBufferStack.cpp]
boolSharedBufferClient::LockCondition::operator()() {
//stack、buf等都是這個物件的內部成員,這個物件的目的就是根據讀寫位置判斷這個buffer是
//否空閒。
return(buf != stack.head ||
(stack.queued > 0 && stack.inUse != buf));
}
SharedBufferStack的讀寫控制,比Audio中的環形緩衝看起來要簡單,實際上它卻比較複雜。本章會在擴充套件部分進行分析。這裡給讀者準備一個問題,也是我之前百思不得其解的問題:
既然已經呼叫dequeue得到了一個空閒緩衝,為什麼這裡還要lock呢?
(3)拷貝舊資料
在第三個關鍵點中,可看到這樣的程式碼:
[-->Surface.cpp]
status_t Surface::lock(SurfaceInfo* other,Region* dirtyIn, bool blocking)
{
......
const sp<GraphicBuffer>& frontBuffer(mPostedBuffer);
if (frontBuffer !=0 &&
backBuffer->width ==frontBuffer->width &&
backBuffer->height == frontBuffer->height &&
!(mFlags & ISurfaceComposer::eDestroyBackbuffer))
{
const Region copyback(mOldDirtyRegion.subtract(newDirtyRegion));
if (!copyback.isEmpty() && frontBuffer!=0) {
//③把frontBuffer中的資料拷貝到BackBuffer中,這是為什麼?
copyBlt(backBuffer,frontBuffer, copyback);
}
}
......
}
上面這段程式碼所解決的,其實是下面這個問題:
在大部分情況下,UI只有一小部分會發生變化(例如一個按鈕被按下去,導致顏色發生變化),這一小部分UI只對應整個GraphicBuffer中的一小塊儲存(就是在前面程式碼中見到的dirtyRegion),如果整塊儲存都更新,則會極大地浪費資源。怎麼辦?
這就需要將變化的影象和沒有發生變化的影象進行疊加。上一次繪製的資訊儲存在mPostedBuffer中,而這個mPostedBuffer則要在unLockAndPost函式中設定。這裡將根據需要,把mPostedBuffer中的舊資料拷貝到BackBuffer中。後續的繪畫只要更新髒區域就可以了,這會節約不少資源。
OK,lockCanvas返回後,應用層將在這塊畫布上盡情作畫。假設現在已經在BackBuffer上繪製好了影象,下面就要通過unlockCanvasAndPost進行後續工作了。一起來看。
2. unlockCanvasAndPost的分析
進入精簡流程的最後一步,就是unlockCanvasAndPost函式,它的程式碼如下所示:
[-->Surface.cpp]
status_t Surface::unlockAndPost()
{
//呼叫GraphicBuffer的unlock函式
status_t err = mLockedBuffer->unlock();
//get返回這個GraphicBuffer的編號,queueBuffer將含有新資料的緩衝加入隊中。
err =queueBuffer(mLockedBuffer.get());
mPostedBuffer = mLockedBuffer; //儲存這個BackBuffer為mPostedBuffer
mLockedBuffer = 0;
returnerr;
}
來看queueBuffer呼叫,程式碼如下所示:
[-->Surface.cpp]
intSurface::queueBuffer(android_native_buffer_t* buffer)
{
sp<SurfaceComposerClient> client(getClient());
int32_t bufIdx =GraphicBuffer::getSelf(buffer)->getIndex();
//設定髒Region
mSharedBufferClient->setDirtyRegion(bufIdx,mDirtyRegion);
//更新寫位置。
err =mSharedBufferClient->queue(bufIdx);
if (err== NO_ERROR) {
//client是BpSurfaceFlinger,呼叫它的signalServer,這樣SF就知道新資料準備好了
client->signalServer();
}
returnerr;
}
這裡,與讀寫控制有關的是queue函式,其程式碼如下所示:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::queue(int buf)
{
//QueueUpdate也是一個函式物件
QueueUpdate update(this);
//呼叫updateCondition函式。
status_t err = updateCondition( update );
SharedBufferStack& stack( *mSharedStack );
constnsecs_t now = systemTime(SYSTEM_TIME_THREAD);
stack.stats.totalTime = ns2us(now - mDequeueTime[buf]);
returnerr;
}
這個updateCondition函式的程式碼如下所示:
[-->SharedBufferStack.h]
template <typename T>
status_t SharedBufferBase::updateCondition(Tupdate) {
SharedClient& client( *mSharedClient );
Mutex::Autolock _l(client.lock);
ssize_t result = update();//呼叫update物件的()函式
client.cv.broadcast(); //觸發同步物件
returnresult;
}
updateCondition函式和前面介紹的waitForCondition函式一樣,都是使用的函式物件。queue操作使用的是QueueUpdate類,關於它的故事,將在擴充部分討論。
3. lockCanvas和unlockCanvasAndPost的總結
總結一下lockCanvas和unlockCanvasAndPost這兩個函式的工作流程,用圖8-20表示:
圖8-20 lockCanvas和unlockCanvasAndPost流程總結
8.4.6 GraphicBuffer的介紹
GraphicBuffer是Surface系統中一個高層次的顯示記憶體管理類,它封裝了和硬體相關的一些細節,簡化了應用層的處理邏輯。先來認識一下它。
1. 初識GraphicBuffer
GraphicBuffer的程式碼如下所示:
[-->GraphicBuffer.h]
class GraphicBuffer
:public EGLNativeBase<android_native_buffer_t,
GraphicBuffer,LightRefBase<GraphicBuffer>>,
public Flattenable
其中,EGLNativeBase是一個模板類。它的定義,程式碼如下所示:
[-->Android_natives.h]
template <typename NATIVE_TYPE, typenameTYPE, typename REF>
class EGLNativeBase : public NATIVE_TYPE, publicREF
通過替換,可得到GraphicBuffer的派生關係,如圖8-21所示:
圖8-21 GraphicBuffer派生關係的示意圖
從圖中可以看出:
· 從LightRefBase派生使GraphicBuffer支援輕量級的引用計數控制。
· 從Flattenable派生使GraphicBuffer支援序列化,它的flatten和unflatten函式用於序列化和反序列化,這樣,GraphicBuffer的資訊就可以儲存到Parcel包中並被Binder傳輸了。
另外,圖中的android_native_buffer_t是GraphicBuffer的父類,它是一個struct結構體。可以將C++語言中的struct和class當作同一個東西,所以GraphicBuffer能從它派生。其程式碼如下所示:
[-->android_native_buffer.h]
typedef struct android_native_buffer_t
{
#ifdef __cplusplus
android_native_buffer_t() {
common.magic = ANDROID_NATIVE_BUFFER_MAGIC;
common.version = sizeof(android_native_buffer_t);
memset(common.reserved, 0, sizeof(common.reserved));
}
#endif
//這個android_native_base_t是struct的第一個成員,根據C/C++編譯的特性,這個成員
//在它的派生類物件所佔有的記憶體中也是排第一個。
structandroid_native_base_t common;
intwidth;
intheight;
intstride;
intformat;
intusage;
void* reserved[2];
//這是一個關鍵成員,儲存一些和顯示記憶體分配/管理相關的內容
buffer_handle_t handle;
void*reserved_proc[8];
} android_native_buffer_t;
GraphicBuffer和顯示記憶體分配相關的部分主要集中在buffer_handle_t這個變數上,它實際上是一個指標,定義如下:
[-->gralloc.h]
typedef const native_handle* buffer_handle_t;
native_handle的定義如下:
[-->native_handle.h]
typedef struct
{
intversion; /* version值為sizeof(native_handle_t) */
intnumFds;
intnumInts;
intdata[0]; /* data是資料儲存空間的首地址 */
} native_handle_t;
typedef native_handle_t native_handle;
讀者可能要問,一個小小的GraphicBuffer為什麼這麼複雜?要回答這個問題,應先對GraphicBuffer有比較全面的瞭解。按照圖8-20中的流程來看GraphicBuffer。
2. GraphicBuffer和儲存的分配
GraphicBuffer的建構函式最有可能分配儲存了。注意,流程中使用的是無參建構函式,所以應先看無參建構函式。
(1)無參建構函式的分析
程式碼如下所示:
[-->GraphicBuffer.cpp]
GraphicBuffer::GraphicBuffer()
:BASE(), mOwner(ownData), mBufferMapper(GraphicBufferMapper::get()),
mInitCheck(NO_ERROR), mVStride(0), mIndex(-1)
{
/*
其中mBufferMapper為GraphicBufferMapper型別,它的建立採用的是單例模式,也就是每個
程式只有一個GraphicBufferMapper物件,讀者可以去看看get的實現。
*/
width =
height=
stride=
format=
usage = 0;
handle= NULL; //handle為空
}
在無參建構函式中沒有發現和儲存分配有關的操作。那麼,根據流程,下一個有可能的地方就是reallocate函式了。
(2)reallocate的分析
Reallocate的程式碼如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::reallocate(uint32_t w,uint32_t h, PixelFormat f,
uint32_t reqUsage)
{
if(mOwner != ownData)
return INVALID_OPERATION;
if(handle) {//handle值在無參建構函式中初始化為空,所以不滿足if的條件
GraphicBufferAllocator& allocator(GraphicBufferAllocator::get());
allocator.free(handle);
handle = 0;
}
returninitSize(w, h, f, reqUsage);//呼叫initSize函式
}
InitSize函式的程式碼如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::initSize(uint32_t w,uint32_t h, PixelFormat format,
uint32_t reqUsage)
{
if(format == PIXEL_FORMAT_RGBX_8888)
format = PIXEL_FORMAT_RGBA_8888;
/*
GraphicBufferAllocator才是真正的儲存分配的管理類,它的建立也是採用的單例模式,
也就是每個程式只有一個GraphicBufferAllocator物件
*/
GraphicBufferAllocator& allocator =GraphicBufferAllocator::get();
//呼叫GraphicBufferAllocator的alloc來分配儲存,注意handle作為指標
//被傳了進去,看來handle的值會被修改
status_t err = allocator.alloc(w, h, format, reqUsage, &handle,&stride);
if(err == NO_ERROR) {
this->width = w;
this->height = h;
this->format = format;
this->usage = reqUsage;
mVStride = 0;
}
returnerr;
}
(3)GraphicBufferAllocator的介紹
從上面的程式碼中可以發現,GraphicBuffer的儲存分配和GraphicBufferAllocator有關。一個小小的儲存分配為什麼需要經過這麼多道工序呢?還是先來看GraphicBufferAllocator,程式碼如下所示:
[-->GraphicBufferAllocator.cpp]
GraphicBufferAllocator::GraphicBufferAllocator()
:mAllocDev(0)
{
hw_module_t const* module;
//呼叫hw_get_module,得到hw_module_t
interr = hw_get_module(GRALLOC_HARDWARE_MODULE_ID, &module);
if (err == 0) {
//呼叫gralloc_open函式,注意我們把module引數傳了進去。
gralloc_open(module, &mAllocDev);
}
}
GraphicBufferAllocator在建立時,會首先呼叫hw_get_module取出一個hw_module_t型別的物件。從名字上看,它和硬體平臺有關係。它會載入一個叫libgralloc.硬體平臺名.so的動態庫。比如,我的HTC G7手機上載入的庫是/system/lib/hw/libgraolloc.qsd-8k.so。這個庫的原始碼在hardware/msm7k/libgralloc-qsd8k目錄下。
這個庫有什麼用呢?簡言之,就是為了分配一塊用於顯示的記憶體,但為什麼需要這種層層封裝呢?答案很簡單:
封裝的目的就是為了遮蔽不同硬體平臺的差別。
讀者可通過執行adb getprop ro.board.platform命令,得到具體手機上硬體平臺的名字。圖8-22總結了GraphicBufferAllocator分配記憶體的途徑。這部分程式碼,讀者可參考hardware/libhardware/hardware.c和hardware/msm7k/libgralloc-qsd8k/gralloc.cpp,後文將不再深入探討和硬體平臺有關的知識。
圖8-22 GraphicBufferAllocator記憶體的分配途徑
注意,這裡是以G7的libgralloc.qsk-8k.so為示例的。其中pmem裝置用來建立一塊連續的記憶體,因為有些硬體裝置(例如Camera)工作時需要使用一塊連續的記憶體,對於這種情況,一般就會使用pmem裝置來分配記憶體。
這裡,僅討論圖8-22中與硬體無關的分配方式。在這種情況下,將使用ashmem分配共享記憶體。下面看GraphicBufferAllocator的alloc函式,其程式碼如下所示:
[-->GraphicBufferAllocator.cpp]
status_t GraphicBufferAllocator::alloc(uint32_tw, uint32_t h, PixelFormat format,int usage, buffer_handle_t* handle, int32_t*stride)
{
//根據前面的定義可知buffer_handle_t為native_handle_t*型別
status_t err;
if (usage & GRALLOC_USAGE_HW_MASK) {
err =mAllocDev->alloc(mAllocDev, w, h, format, usage, handle, stride);
} else {
//SW分配,可以做到和HW無關了。
err = sw_gralloc_handle_t::alloc(w, h, format, usage, handle, stride);
}
......
returnerr;
}
下面,來看軟體分配的方式:
[-->GraphicBufferAllocator.cpp]
status_t sw_gralloc_handle_t::alloc(uint32_t w,uint32_t h, int format,
int usage, buffer_handle_t* pHandle, int32_t*pStride)
{
intalign = 4;
intbpp = 0;
......//格式轉換
size_tbpr = (w*bpp + (align-1)) & ~(align-1);
size_tsize = bpr * h;
size_tstride = bpr / bpp;
size =(size + (PAGE_SIZE-1)) & ~(PAGE_SIZE-1);
//直接使用了ashmem建立共享記憶體
int fd= ashmem_create_region("sw-gralloc-buffer", size);
......
//進行記憶體對映,得到共享記憶體起始地址
void*base = mmap(0, size, prot, MAP_SHARED, fd, 0);
sw_gralloc_handle_t* hnd = new sw_gralloc_handle_t();
hnd->fd = fd;//儲存檔案描述符
hnd->size = size;//儲存共享記憶體的大小
hnd->base = intptr_t(base);//intptr_t將void*型別轉換成int*型別
hnd->prot = prot;//儲存屬性
*pStride = stride;
*pHandle = hnd; //pHandle就是傳入的那個handle變數的指標,這裡對它進行賦值
returnNO_ERROR;
}
我們知道,呼叫GraphicBuffer的reallocate函式後,會導致物理儲存被分配。前面曾說過,Layer會建立兩個GraphicBuffer,而Native Surface端也會建立兩個GraphicBuffer,那麼這兩個GraphicBuffer是怎麼建立聯絡的呢?為什麼說native_handle_t是GraphicBuffer的精髓呢?
3. flatten和unflatten的分析
試想,Native Surface的GraphicBuffer是怎麼和Layer的GraphicBuffer建立聯絡的:
先通過requestBuffer函式返回一個GraphicBuffer,然後這個GraphicBuffer被Native Surface儲存。
這中間的過程其實是一個mini版的乾坤挪移,來看看,程式碼如下所示:
[-->ISurface.cpp]
//requestBuffer的響應端
status_t BnSurface::onTransact(
uint32_t code, const Parcel& data, Parcel* reply, uint32_t flags)
{
switch(code) {
case REQUEST_BUFFER: {
CHECK_INTERFACE(ISurface, data, reply);
int bufferIdx = data.readInt32();
int usage = data.readInt32();
sp<GraphicBuffer> buffer(requestBuffer(bufferIdx, usage));
......
/*
requestBuffer的返回值被寫到Parcel包中,由於GraphicBuffer從
Flattenable類派生,這將導致它的flatten函式被呼叫
*/
return reply->write(*buffer);
}
.......
}
//再來看請求端的處理,在BpSurface中
virtual sp<GraphicBuffer> requestBuffer(intbufferIdx, int usage)
{
Parcel data, reply;
data.writeInterfaceToken(ISurface::getInterfaceDescriptor());
data.writeInt32(bufferIdx);
data.writeInt32(usage);
remote()->transact(REQUEST_BUFFER, data, &reply);
sp<GraphicBuffer> buffer = new GraphicBuffer();
reply.read(*buffer);//Parcel呼叫unflatten函式把資訊反序列化到這個buffer中。
return buffer;//requestBuffer實際上返回的是本地new出來的這個GraphicBuffer
}
通過上面的程式碼可以發現,挪移的關鍵體現在flatten和unflatten函式上。請看:
(1)flatten的分析
flatten的程式碼如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::flatten(void* buffer,size_t size,
int fds[], size_t count) const
{
//buffer是裝載資料的緩衝區,由Parcel提供
......
if(handle) {
buf[6] = handle->numFds;
buf[7] = handle->numInts;
native_handle_t const* const h = handle;
//把handle的資訊也寫到buffer中
memcpy(fds, h->data, h->numFds*sizeof(int));
memcpy(&buf[8], h->data + h->numFds,h->numInts*sizeof(int));
}
returnNO_ERROR;
}
flatten的工作就是把GraphicBuffer的handle變數資訊寫到Parcel包中。那麼接收端如何使用這個包呢?這就是unflatten的工作了。
(2)unflatten分析
unflatten的程式碼如下所示:
[-->GraphicBuffer.cpp]
status_t GraphicBuffer::unflatten(void const*buffer, size_t size,
int fds[], size_t count)
{
......
if(numFds || numInts) {
width = buf[1];
height = buf[2];
stride = buf[3];
format = buf[4];
usage = buf[5];
native_handle* h =native_handle_create(numFds, numInts);
memcpy(h->data, fds, numFds*sizeof(int));
memcpy(h->data + numFds, &buf[8],numInts*sizeof(int));
handle = h;//根據Parcel包中的資料還原一個handle
} else{
width = height = stride = format = usage = 0;
handle = NULL;
}
mOwner= ownHandle;
returnNO_ERROR;
}
unflatten最重要的工作是,根據Parcel包中native_handle的資訊,在Native Surface端構造一個對等的GraphicBuffer。這樣,Native Surface端的GraphicBuffer實際上就和Layer端的GraphicBuffer管理著同一塊共享記憶體。
3. registerBuffer的分析
registerBuffer有什麼用呢?上一步呼叫unflatten後得到了代表共享記憶體的檔案控制程式碼,regiserBuffer的目的就是對它進行記憶體對映,程式碼如下所示:
[-->GraphicBufferMapper.cpp]
status_tsw_gralloc_handle_t::registerBuffer(sw_gralloc_handle_t* hnd)
{
if (hnd->pid != getpid()) {
//原來是做一次記憶體對映操作
void* base = mmap(0, hnd->size, hnd->prot, MAP_SHARED, hnd->fd,0);
......
//base儲存著共享記憶體的起始地址
hnd->base = intptr_t(base);
}
returnNO_ERROR;
}
4. lock和unlock的分析
GraphicBuffer在使用前需要通過lock來得到記憶體地址,使用完後又會通過unlock釋放這塊地址。在SW分配方案中,這兩個函式實現卻非常簡單,如下所示:
[-->GraphicBufferMapper.cpp]
//lock操作
int sw_gralloc_handle_t::lock(sw_gralloc_handle_t*hnd, int usage,
int l, int t, int w, int h, void** vaddr)
{
*vaddr= (void*)hnd->base;//得到共享記憶體的起始地址,後續作畫就使用這塊記憶體了。
returnNO_ERROR;
}
//unlock操作
status_tsw_gralloc_handle_t::unlock(sw_gralloc_handle_t* hnd)
{
returnNO_ERROR;//沒有任何操作
}
對GraphicBuffer的介紹就到這裡。雖然採用的是SW方式,但是相信讀者也能通過樹木領略到森林的風采。從應用層角度看,可以把GraphicBuffer當做一個構架在共享記憶體之上的資料緩衝。對想深入研究的讀者,我建議可按圖8-20中的流程來分析。因為流程體現了呼叫順序,表達了呼叫者的意圖和目的,只有把握了流程,分析時才不會迷失在茫茫的原始碼海洋中,才不會被不熟悉的知識阻攔前進的腳步。
8.4.7 深入分析Surface總結
Surface系統最難的部分,是這個Native Surface的建立和使用,它包括三個方面:
· Activity的UI和Surface的關係是怎樣的?這是8.2節回答的問題。
· Activity中所使用的Surface是怎麼和SurfaceFlinger掛上關係的?這是8.3節回答的問題。
· 本節對第2個問題進行了較深入的研究,分析了Surface和SurfaceFlinger之間的關係,以及生產/消費步調的中樞控制機構SharedBuffer家族和資料的承載者GraphicBuffer。
從上面分析可看出,本章前四節均圍繞著這個Surface講解,一路下來確實遇到了不少曲折和坎坷,望讀者跟著原始碼反覆閱讀,體會。
8.5 SurfaceFlinger的分析
這一節要對SurfaceFlinger進行分析。相比較而言,SurfaceFlinger不如AudioFlinger複雜。
8.5.1 SurfaceFlinger的誕生
SurfaceFlinger駐留於system_server程式,這一點和Audio系統的幾個Service不太一樣。它建立的位置在SystemServer的init1函式中(第4章4.3.2節的第3點)。雖然位於SystemServer這個重要程式中,但是SF建立的程式碼卻略顯波瀾不驚,沒有什麼特別之處。SF的建立首先會呼叫instantiate函式,程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::instantiate() {
defaultServiceManager()->addService(
String16("SurfaceFlinger"), new SurfaceFlinger());
}
前面在圖8-14中指出了SF,同時從BnSurfaceComposer和Thread類中派生,相關程式碼如下所示:
class SurfaceFlinger : public BnSurfaceComposer,protected Thread
從Thread派生這件事給了我們一個很明確的提示:
· SurfaceFlinger會單獨啟動一個工作執行緒。
我們知道,Thread類的工作執行緒要通過呼叫它的run函式來建立,那這個run函式是在什麼地方呼叫的呢?當然,最有可能的就是在建構函式中:
[-->SurfaceFlinger.cpp]
SurfaceFlinger::SurfaceFlinger()
: BnSurfaceComposer(), Thread(false),
mTransactionFlags(0),
mTransactionCount(0),
mResizeTransationPending(false),
mLayersRemoved(false),
mBootTime(systemTime()),
mHardwareTest("android.permission.HARDWARE_TEST"),
mAccessSurfaceFlinger("android.permission.ACCESS_SURFACE_FLINGER"),
mDump("android.permission.DUMP"),
mVisibleRegionsDirty(false),
mDeferReleaseConsole(false),
mFreezeDisplay(false),
mFreezeCount(0),
mFreezeDisplayTime(0),
mDebugRegion(0),
mDebugBackground(0),
mDebugInSwapBuffers(0),
mLastSwapBufferTime(0),
mDebugInTransaction(0),
mLastTransactionTime(0),
mBootFinished(false),
mConsoleSignals(0),
mSecureFrameBuffer(0)
{
init();//上面沒有呼叫run。必須到init去檢查一番。
}
//init函式更簡單了。
void SurfaceFlinger::init()
{
charvalue[PROPERTY_VALUE_MAX];
property_get("debug.sf.showupdates", value, "0");
mDebugRegion = atoi(value);
property_get("debug.sf.showbackground", value, "0");
mDebugBackground = atoi(value);
}
嗯?上面的程式碼竟然沒有建立工作執行緒?難道在其他地方?讀者別急著在檔案中搜尋“run”,先猜測一下答案。
· 根據之前所學的知識,另外一個最有可能的地方就是onFirstRef函式了,這個函式在物件第一次被sp化後呼叫,很多初始化的工作也可以在這個函式中完成。
事實是這樣嗎?一起來看。
1. onFirstRef的分析
onFirstRef的程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::onFirstRef()
{
//真是夢裡尋他千百度,果然是在onFirstRef中建立了工作執行緒
run("SurfaceFlinger",PRIORITY_URGENT_DISPLAY);
/*
mReadyToRunBarrier型別為Barrier,這個類就是封裝了一個Mutex物件和一個Condition
物件。如果讀者還記得第5章有關同步類的介紹,理解這個Barrier就非常簡單了。下面呼叫的
wait函式表示要等待一個同步條件的滿足。
*/
mReadyToRunBarrier.wait();
}
onFirstRef建立工作執行緒後,將等待一個同步條件,那麼這個同步條件在哪裡被觸發呢?相信不用多說 大家也知道:
在工作執行緒中被觸發,而且極有可能是在readyToRun函式中。
不清楚Thread類的讀者可以複習一下與第5章有關的Thread類的知識。
2. readyToRun的分析
SF的readyToRun函式將完成一些初始化工作,程式碼如下所示:
[-->SurfaceFlinger.cpp]
status_t SurfaceFlinger::readyToRun()
{
intdpy = 0;
{
//①GraphicPlane是什麼?
GraphicPlane& plane(graphicPlane(dpy));
//②為這個GraphicPlane設定一個HAL物件——DisplayHardware
DisplayHardware* const hw = new DisplayHardware(this, dpy);
plane.setDisplayHardware(hw);
}
//建立Surface系統中的“CB”物件,按照老規矩,應該先建立一塊共享記憶體,然後使用placment new
mServerHeap = new MemoryHeapBase(4096,
MemoryHeapBase::READ_ONLY,
"SurfaceFlingerread-only heap");
/*
注意這個“CB“物件的型別是surface_flinger_cblk_t。為什麼在CB上打引號呢?因為這個物件
談不上什麼控制,只不過被用來儲存一些資訊罷了。其控制作用完全達不到audio_track_cblk_t
的程度。基於這樣的事實,我們把前面提到的SharedBuffer家族稱之為CB物件。
*/
mServerCblk=
static_cast<surface_flinger_cblk_t*>(mServerHeap->getBase());
//placementnew建立surface_flinger_cblk_t
new(mServerCblk) surface_flinger_cblk_t;
constGraphicPlane& plane(graphicPlane(dpy));
constDisplayHardware& hw = plane.displayHardware();
constuint32_t w = hw.getWidth();
constuint32_t h = hw.getHeight();
constuint32_t f = hw.getFormat();
hw.makeCurrent();
//當前只有一塊屏
mServerCblk->connected|= 1<<dpy;
//螢幕在“CB”物件中的代表是display_cblk_t
display_cblk_t* dcblk = mServerCblk->displays + dpy;
memset(dcblk, 0, sizeof(display_cblk_t));
dcblk->w =plane.getWidth();
dcblk->h =plane.getHeight();
......//獲取螢幕資訊
//還用上了內聯彙編語句。
asmvolatile ("":::"memory");
/*
下面是一些和OpenGL相關的函式呼叫。讀者如感興趣,可以研究一下,
至少SurfaceFlinger.cpp中所涉及的相關程式碼還不算難懂
*/
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, 0);
......
glOrthof(0, w, h, 0, 0, 1);
//LayerDim是Dim型別的Layer
LayerDim::initDimmer(this, w, h);
//還記得在onFirstRef函式中的wait嗎?下面的open將觸發這個同步條件
mReadyToRunBarrier.open();
//資源準備好後,init將啟動bootanim程式,這樣就見到開機動畫了。
property_set("ctl.start", "bootanim");
returnNO_ERROR;
}
在上面的程式碼中,列出了兩個關鍵點,下面一一進行分析。
(1)GraphicPlane的介紹
GraphicPlane是螢幕在SF程式碼中的對應物,根據前面的介紹,目前Android只支援一塊螢幕,所以SF定義了一個一元陣列:
GraphicPlane mGraphicPlanes[1];
GraphicPlane雖無什麼特別之處,但它有一個重要的函式,叫setDisplayHardware,這個函式把代表顯示裝置的HAL物件和GraphicPlane關聯起來。這也是下面要介紹的第二個關鍵點DisplayHardware。
(2)DisplayHardware的介紹
從程式碼上看,這個和顯示相關的HAL物件是在工作執行緒中new出來的,先看它的建構函式,程式碼如下所示:
[-->DisplayHardware.cpp]
DisplayHardware::DisplayHardware(
const sp<SurfaceFlinger>& flinger,
uint32_t dpy)
:DisplayHardwareBase(flinger, dpy)
{
init(dpy); //最重要的是這個init函式。
}
init函式非常重要,應進去看看。下面先思考一個問題。
前面在介紹FrameBuffer時說過,顯示這一塊需要使用FrameBuffer,但在GraphicBuffer中用的卻是ashmem建立的共享記憶體。也就是說,之前在共享記憶體中繪製的影象和FrameBuffer沒有什麼關係。那麼FrameBuffer是在哪裡建立的呢?
答案就在init函式中,程式碼如下所示:
[-->DisplayHardware.cpp]
void DisplayHardware::init(uint32_t dpy)
{
//FrameBufferNativeWindow實現了對FrameBuffer的管理和操作,該類中建立了兩個
//FrameBuffer,分別起到FrontBuffer和BackBuffer的作用。
mNativeWindow = new FramebufferNativeWindow();
framebuffer_device_t const * fbDev = mNativeWindow->getDevice();
mOverlayEngine = NULL;
hw_module_t const* module;//Overlay相關
if(hw_get_module(OVERLAY_HARDWARE_MODULE_ID, &module) == 0) {
overlay_control_open(module, &mOverlayEngine);
}
......
EGLint w, h, dummy;
EGLintnumConfigs=0;
EGLSurface surface;
EGLContext context;
mFlags= CACHED_BUFFERS;
//EGLDisplay在EGL中代表螢幕
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
......
/*
surface是EGLSurface型別,下面這個函式會將EGL和Android中的Display系統繫結起來,
後續就可以利用OpenGL在這個Surface上繪畫,然後通過eglSwappBuffers輸出影象了。
*/
surface= eglCreateWindowSurface(display, config,
mNativeWindow.get(),NULL);
......
mDisplay = display;
mConfig = config;
mSurface = surface;
mContext = context;
mFormat = fbDev->format;
mPageFlipCount = 0;
}
根據上面的程式碼,現在可以回答前面的問題了:
· SF建立FrameBuffer,並將各個Surface傳輸的資料(通過GraphicBuffer)混合後,再由自己傳輸到FrameBuffer中進行顯示。
本節的內容,實際上涉及另外一個比Surface更復雜的Display系統,出於篇幅和精力的原因,本書目前不打算討論它。
8.5.2 SF工作執行緒的分析
SF中的工作執行緒就是來做影象混合的,比起AudioFlinger來,它相當簡單,下面是它的程式碼:
[-->SurfaceFlinger.cpp]
bool SurfaceFlinger::threadLoop()
{
waitForEvent();//① 等待什麼事件呢?
if (UNLIKELY(mConsoleSignals)) {
handleConsoleEvents();
}
if(LIKELY(mTransactionCount == 0)) {
const uint32_t mask = eTransactionNeeded | eTraversalNeeded;
uint32_t transactionFlags = getTransactionFlags(mask);
if(LIKELY(transactionFlags)) {
//Transaction(事務)處理,放到本節最後來討論
handleTransaction(transactionFlags);
}
}
//②處理PageFlipping工作
handlePageFlip();
constDisplayHardware& hw(graphicPlane(0).displayHardware());
if (LIKELY(hw.canDraw() && !isFrozen())) {
//③處理重繪
handleRepaint();
hw.compositionComplete();
//④投遞BackBuffer
unlockClients();
postFramebuffer();
} else{
unlockClients();
usleep(16667);
}
returntrue;
}
ThreadLoop一共有四個關鍵點,這裡,分析除Transaction外的三個關鍵點。
1. waitForEvent
SF工作執行緒一上來就等待事件,它會是什麼事件呢?來看程式碼:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::waitForEvent()
{
while(true) {
nsecs_t timeout = -1;
const nsecs_t freezeDisplayTimeout = ms2ns(5000);
......
MessageList::value_type msg = mEventQueue.waitMessage(timeout);
......//還有一些和凍屏相關的內容
if(msg != 0) {
switch (msg->what) {
//千辛萬苦就等這一個重繪訊息
case MessageQueue::INVALIDATE:
return;
}
}
}
}
SF收到重繪訊息後,將退出等待。那麼,是誰傳送的這個重繪訊息呢?還記得在unlockCanvasAndPost函式中呼叫的signal嗎?它在SF端的實現程式碼如下:
[-->SurfaceFlinger]
void SurfaceFlinger::signal() const {
const_cast<SurfaceFlinger*>(this)->signalEvent();
}
void SurfaceFlinger::signalEvent() {
mEventQueue.invalidate(); //往訊息佇列中加入INVALIDATE訊息
}
2. 分析handlePageFlip
SF工作執行緒從waitForEvent中返回後,下一步要做的就是處理事務和handlePageFlip了。先看handlePageFlip,從名字上可知,它和PageFlipping工作有關。
注意:事務處理將在8.5.3節中介紹。
程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handlePageFlip()
{
bool visibleRegions = mVisibleRegionsDirty;
/*
還記得前面所說的mCurrentState嗎?它儲存了所有顯示層的資訊,而繪製的時候使用的
mDrawingState則儲存了當前需要顯示的顯示層資訊。
*/
LayerVector& currentLayers =
const_cast<LayerVector&>(mDrawingState.layersSortedByZ);
//①呼叫lockPageFlip
visibleRegions |= lockPageFlip(currentLayers);
const DisplayHardware& hw =graphicPlane(0).displayHardware();
//取得螢幕的區域
const Region screenRegion(hw.bounds());
if (visibleRegions) {
Region opaqueRegion;
computeVisibleRegions(currentLayers, mDirtyRegion,opaqueRegion);
mWormholeRegion = screenRegion.subtract(opaqueRegion);
mVisibleRegionsDirty = false;
}
//② 呼叫unlockPageFlip
unlockPageFlip(currentLayers);
mDirtyRegion.andSelf(screenRegion);
}
hanldePageFlip呼叫了兩個看起來是一對的函式:lockPageFlip和unlockPageFlip。這兩個函式會幹些什麼呢?
(1)lockPageFlip的分析
先看lockPageFlip函式,程式碼如下所示:
[-->SurfaceFlinger.cpp]
bool SurfaceFlinger::lockPageFlip(constLayerVector& currentLayers)
{
boolrecomputeVisibleRegions = false;
size_tcount = currentLayers.size();
sp<LayerBase> const* layers = currentLayers.array();
for(size_t i=0 ; i<count ; i++) {
const sp<LayerBase>& layer = layers[i];
//呼叫每個顯示層的lockPageFlip
layer->lockPageFlip(recomputeVisibleRegions);
}
returnrecomputeVisibleRegions;
}
假設當前的顯示層是Layer型別,那麼得轉到Layer類去看它的lockPageFlip函式,程式碼如下所示:
[-->Layer.cpp]
void Layer::lockPageFlip(bool&recomputeVisibleRegions)
{
//lcblk是SharedBufferServer型別,呼叫retireAndLock函式將返回FrontBuffer的
//索引號
ssize_tbuf = lcblk->retireAndLock();
......
mFrontBufferIndex = buf;
//得到FrontBuffer對應的GraphicBuffer
sp<GraphicBuffer> newFrontBuffer(getBuffer(buf));
if (newFrontBuffer != NULL) {
//取出髒區域
const Region dirty(lcblk->getDirtyRegion(buf));
//和GraphicBuffer所表示的區域進行裁剪,得到一個髒區域
mPostedDirtyRegion = dirty.intersect( newFrontBuffer->getBounds() );
const Layer::State& front(drawingState());
if(newFrontBuffer->getWidth() ==front.requested_w &&
newFrontBuffer->getHeight() ==front.requested_h)
{
if ((front.w != front.requested_w) ||
(front.h != front.requested_h))
{
...... //需要重新計算可見區域
recomputeVisibleRegions = true;
}
mFreezeLock.clear();
}
} else{
mPostedDirtyRegion.clear();
}
if(lcblk->getQueuedCount()) {
mFlinger->signalEvent();
}
/*
如果髒區域不為空,則需要繪製成紋理,reloadTexture將繪製一張紋理儲存在
mTextures陣列中,裡邊涉及很多OpenGL的操作,讀者有興趣可以自己研究。
*/
if(!mPostedDirtyRegion.isEmpty()) {
reloadTexture( mPostedDirtyRegion );
}
}
我們知道,Layer的lockPageFlip將根據FrontBuffer的內容生成一張紋理。那麼,unlockPageFlip會做些什麼呢?
(2)unlockPageFlip的分析
unlockPageFlip的程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::unlockPageFlip(constLayerVector& currentLayers)
{
constGraphicPlane& plane(graphicPlane(0));
constTransform& planeTransform(plane.transform());
size_tcount = currentLayers.size();
sp<LayerBase> const* layers = currentLayers.array();
for(size_t i=0 ; i<count ; i++) {
const sp<LayerBase>& layer = layers[i];
//呼叫每個顯示層的unlockPageFlip,Layer的unlockPageFlip主要做一些
//區域的清理工作,讀者可以自己看看。
layer->unlockPageFlip(planeTransform, mDirtyRegion);
}
}
(3)handlePageFlip的總結
handlePageFlip的工作其實很簡單,以Layer型別為例來總結一下:
各個Layer需要從FrontBuffer中取得新資料,並生成一張OpenGL中的紋理。紋理可以看做是一個圖片,這個圖片的內容就是FrontBuffer中的影象。
現在每一個Layer都準備好了新資料,下一步的工作當然就是繪製了。來看handleRepaint函式。
3. 分析handleRepaint函式
handleRepaint函式的程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handleRepaint()
{
mInvalidRegion.orSelf(mDirtyRegion);
if(mInvalidRegion.isEmpty()) {
return;
}
......
constDisplayHardware& hw(graphicPlane(0).displayHardware());
glMatrixMode(GL_MODELVIEW);
glLoadIdentity();
uint32_t flags = hw.getFlags();
if((flags & DisplayHardware::SWAP_RECTANGLE) ||
(flags & DisplayHardware::BUFFER_PRESERVED))
{
......//計算mDirtyRegion
}
// 在髒區域上進行繪製
composeSurfaces(mDirtyRegion);
mDirtyRegion.clear();
}
其中,composeSurfaces將不同的顯示層內容進行混合,其實就是按Z軸的順序由裡到外依次繪製。當然,最後繪製的資料有可能遮蓋前面繪製的資料,程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::composeSurfaces(constRegion& dirty)
{
constSurfaceFlinger& flinger(*this);
constLayerVector& drawingLayers(mDrawingState.layersSortedByZ);
constsize_t count = drawingLayers.size();
sp<LayerBase> const* const layers = drawingLayers.array();
for(size_t i=0 ; i<count ; ++i) {
const sp<LayerBase>& layer = layers[i];
const Region&visibleRegion(layer->visibleRegionScreen);
if(!visibleRegion.isEmpty()) {
const Region clip(dirty.intersect(visibleRegion));
if (!clip.isEmpty()) {
layer->draw(clip); //呼叫各個顯示層的layer函式
}
}
}
}
draw函式在LayerBase類中實現,程式碼如下所示:
[-->LayerBase.cpp]
void LayerBase::draw(const Region& inClip)const
{
......
glEnable(GL_SCISSOR_TEST);
onDraw(clip);//呼叫子類的onDraw函式
}
至於Layer是怎麼實現這個onDraw函式的,程式碼如下所示:
[-->Layer.cpp]
void Layer::onDraw(const Region& clip) const
{
intindex = mFrontBufferIndex;
if(mTextures[index].image == EGL_NO_IMAGE_KHR)
index = 0;
GLuint textureName = mTextures[index].name;
....
Region holes(clip.subtract(under));
if(!holes.isEmpty()) {
clearWithOpenGL(holes);
}
return;
}
//index是FrontBuffer對應生成的紋理,在lockPageFlip函式中就已經生成了。
drawWithOpenGL(clip,mTextures[index]);//將紋理畫上去,裡面有很多和OpenGL相關內容
}
drawWithOpenGL函式由LayerBase實現,看它是不是使用了這張紋理,程式碼如下所示:
[-->LayerBase.cpp]
void LayerBase::drawWithOpenGL(const Region&clip, const Texture& texture) const
{
constDisplayHardware& hw(graphicPlane(0).displayHardware());
constuint32_t fbHeight = hw.getHeight();
constState& s(drawingState());
//validateTexture函式內部將繫結指定的紋理
validateTexture(texture.name);
//下面就是OpenGL操作函式了
glEnable(GL_TEXTURE_2D);
......
glMatrixMode(GL_TEXTURE);
glLoadIdentity();
//座標旋轉
switch(texture.transform) {
case HAL_TRANSFORM_ROT_90:
glTranslatef(0, 1, 0);
glRotatef(-90, 0, 0, 1);
break;
case HAL_TRANSFORM_ROT_180:
glTranslatef(1, 1, 0);
glRotatef(-180, 0, 0, 1);
break;
case HAL_TRANSFORM_ROT_270:
glTranslatef(1, 0, 0);
glRotatef(-270, 0, 0, 1);
break;
}
if (texture.NPOTAdjust) {
//縮放處理
glScalef(texture.wScale, texture.hScale, 1.0f);
}
//使能紋理座標
glEnableClientState(GL_TEXTURE_COORD_ARRAY);
//設定頂點座標
glVertexPointer(2, GL_FIXED, 0, mVertices);
//設定紋理座標
glTexCoordPointer(2, GL_FIXED, 0, texCoords);
while(it != end) {
const Rect& r = *it++;
const GLint sy = fbHeight - (r.top + r.height());
//裁剪
glScissor(r.left, sy, r.width(), r.height());
//畫矩形
glDrawArrays(GL_TRIANGLE_FAN, 0, 4);
}
//禁止紋理座標
glDisableClientState(GL_TEXTURE_COORD_ARRAY);
}
紋理繫結是OpenGL的常用函式,其程式碼如下所示。
[-->LayerBase.cpp]
void LayerBase::validateTexture(GLinttextureName) const
{
//下面這個函式將繫結紋理
glBindTexture(GL_TEXTURE_2D, textureName);
......//其他一些設定
}
handleRepaint這個函式基本上就是按Z軸的順序對每一層進行重繪,重繪的方法就是使用OpenGL。
我在Android平臺上有幾個月的OpenGL開發經歷,還談不上很深刻,其中的一些資料,希望能夠給感興趣的讀者提供參考。
1)OpenGL的入門教材當選NeHe的資料,大略看前幾章即可。
2) Android平臺上關於OpenGL ES的開發,有一篇很詳細的Word文件叫《OpenGL ESTutorial for Android》。該文詳細描述了在Android平臺上進行OpenGL開發的流程。大家可跟著這篇教材,在模擬器上做一些練習。那裡面涉及到的一些基礎知識,從前面介紹的入門教材中可以學到。
3)有了前面兩點的基礎後,就需要對整個OpenGL有比較完整深入的瞭解了。我在那時所看的書是《OpenGL Programming Guide (7th Edition)》。該書很厚,有1000多頁。裡面有一些內容可能與工作無涉,只要大概知道有那回事就行了,暫時不必深入學習,等需要時再進一步學習並運用。我在開發的專案中曾用到的光照、霧化等效果,都是之前先知道有這個東西,後來在專案中才逐漸學習運用的。
4)嵌入式平臺上用的其實是OpenGL ES。這裡,還有一本書叫《OpenGL ES 2.0 Programming Guide》,它介紹了OpenGL ES的開發,讀者可認真修習。
5)在Android SDK文件中,對OpenGL API的描述只寥寥數語。怎麼辦?由於它使用了J2ME中的javax.microedition.khronos.opengles包,所以J2ME的SDK文件中對OpenGL的API有著非常詳細的描述,讀者手頭應該要有一個J2ME的文件。
6)如果想做深入開發,就不得不學習計算機圖形學了。我後來買了書,可惜沒時間學了。
4. unlockClients和postFrameBuffer的分析
在繪製完圖後,還有兩項工作需要做,一個涉及unlockClients函式,另外一個涉及postFrameBuffer函式,這兩個函式分別幹了什麼呢?unlockClients的程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::unlockClients()
{
constLayerVector& drawingLayers(mDrawingState.layersSortedByZ);
constsize_t count = drawingLayers.size();
sp<LayerBase> const* const layers = drawingLayers.array();
for (size_t i=0 ; i<count ; ++i) {
const sp<LayerBase>& layer = layers[i];
layer->finishPageFlip();
}
}
再看Layer的finishPageFlip函式,程式碼如下所示:
[-->Layer.cpp]
void Layer::finishPageFlip()
{
//釋放FrontBufferIndex
status_t err = lcblk->unlock( mFrontBufferIndex );
}
原來,unlockClients會釋放之前佔著的FrontBuffer的索引號。下面看最後一個函式postFrameBuffer,程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::postFramebuffer()
{
if(!mInvalidRegion.isEmpty()) {
const DisplayHardware& hw(graphicPlane(0).displayHardware());
const nsecs_t now = systemTime();
mDebugInSwapBuffers = now;
//呼叫這個函式後,混合後的影象就會傳遞到螢幕中顯示了
hw.flip(mInvalidRegion);
mLastSwapBufferTime = systemTime() - now;
mDebugInSwapBuffers = 0;
mInvalidRegion.clear();
}
}
flip將呼叫在DisplayHardware一節中提到的eglSwapBuffer函式,來完成FrameBuffer的PageFlip操作,程式碼如下所示:
[-->DisplayHardware.cpp]
void DisplayHardware::flip(const Region&dirty) const
{
checkGLErrors();
EGLDisplay dpy = mDisplay;
EGLSurface surface = mSurface;
......
if(mFlags & PARTIAL_UPDATES) {
mNativeWindow->setUpdateRectangle(dirty.getBounds());
}
mPageFlipCount++;
eglSwapBuffers(dpy, surface);//PageFlipping,此後影象終於顯示在螢幕上了!
}
8.5.3 Transaction的分析
Transaction是“事務”的意思。在我腦海中,關於事務的知識來自於資料庫。在資料庫操作中,事務意味著一次可以提交多個SQL語句,然後一個commit就可讓它們集中執行,而且資料庫中的事務還可以回滾,即恢復到事務提交前的狀態。
SurfaceFlinger為什麼需要事務呢?從上面對資料庫事務的描述來看,是不是意味著一次執行多個請求呢?如直接盯著SF的原始碼來分析,可能不太容易搞清楚事務的前因後果,我想還是用老辦法,從一個例子入手吧。
在WindowManagerService.java中,有一個函式之前分析過,現在再看看,程式碼如下所示:
[-->WindowManagerService.java::WinState]
Surface createSurfaceLocked() {
Surface.openTransaction(); //開始一次transaction
try {
try {
mSurfaceX = mFrame.left + mXOffset;
mSurfaceY = mFrame.top + mYOffset;
//設定Surface的位置
mSurface.setPosition(mSurfaceX, mSurfaceY);
......
}
}finally {
Surface.closeTransaction(); //關閉這次事務
}
這個例子很好地展示了事務的呼叫流程,它會依次呼叫:
· openTransaction
· setPosition
· closeTransaction
下面就來分析這幾個函式的呼叫。
1. openTransaction的分析
看JNI對應的函式,程式碼如下所示:
[-->android_View_Surface.cpp]
static void Surface_openTransaction(JNIEnv* env,jobject clazz)
{
//呼叫SurfaceComposerClient的openGlobalTransaction函式
SurfaceComposerClient::openGlobalTransaction();
}
下面轉到SurfaceComposerClient,程式碼如下所示:
[-->SurfaceComposerClient.cpp]
voidSurfaceComposerClient::openGlobalTransaction()
{
Mutex::Autolock _l(gLock);
......
constsize_t N = gActiveConnections.size();
for(size_t i=0; i<N; i++) {
sp<SurfaceComposerClient>client(gActiveConnections.valueAt(i).promote());
//gOpenTransactions儲存當前提交事務請求的Client
if(client != 0 && gOpenTransactions.indexOf(client) < 0) {
//Client是儲存在全域性變數gActiveConnections中的SurfaceComposerClient
//物件,呼叫它的openTransaction。
if (client->openTransaction() == NO_ERROR) {
if (gOpenTransactions.add(client) < 0) {
client->closeTransaction();
}
}
......
}
}
}
上面是一個靜態函式,內部呼叫了各個SurfaceComposerClient物件的openTranscation,程式碼如下所示:
[-->SurfaceComposerClient.cpp]
status_tSurfaceComposerClient::openTransaction()
{
if(mStatus != NO_ERROR)
return mStatus;
Mutex::Autolock _l(mLock);
mTransactionOpen++; //一個計數值,用來控制事務的提交。
if(mPrebuiltLayerState == 0) {
mPrebuiltLayerState = new layer_state_t;
}
returnNO_ERROR;
}
layer_state_t是用來儲存Surface的一些資訊的,比如位置、寬、高等資訊。實際上,呼叫的setPosition等函式,就是為了改變這個layer_state_t中的值。
2. setPosition的分析
上文說過,SFC中有一個layer_state_t物件用來儲存Surface的各種資訊。這裡以setPosition為例,來看它的使用情況。這個函式是用來改變surface在螢幕上的位置的,程式碼如下所示:
[-->android_View_Surface.cpp]
static void Surface_setPosition(JNIEnv* env,jobject clazz, jint x, jint y)
{
constsp<SurfaceControl>& surface(getSurfaceControl(env, clazz));
if(surface == 0) return;
status_t err = surface->setPosition(x, y);
}
[-->Surface.cpp]
status_t SurfaceControl::setPosition(int32_t x,int32_t y) {
constsp<SurfaceComposerClient>& client(mClient);
status_t err = validate();
if (err < 0) return err;
//呼叫SurfaceComposerClient的setPosition函式
returnclient->setPosition(mToken, x, y);
}
[-->SurfaceComposerClient.cpp]
status_tSurfaceComposerClient::setPosition(SurfaceID id, int32_t x, int32_t y)
{
layer_state_t* s = _lockLayerState(id); //找到對應的layer_state_t
if(!s) return BAD_INDEX;
s->what |= ISurfaceComposer::ePositionChanged;
s->x = x;
s->y = y; //上面幾句修改了這塊layer的引數
_unlockLayerState(); //該函式將unlock一個同步物件,其他沒有做什麼工作
returnNO_ERROR;
}
setPosition就是修改了layer_state_t中的一些引數,那麼,這個狀態是什麼時候傳遞到SurfaceFlinger中的呢?
3. 分析closeTransaction
相信讀者此時已明白為什麼叫“事務”了。原來,在openTransaction和closeTransaction中可以有很多操作,然後由closeTransaction一次性地把這些修改提交到SF上,來看程式碼:
[-->android_View_Surface.cpp]
static void Surface_closeTransaction(JNIEnv*env, jobject clazz)
{
SurfaceComposerClient::closeGlobalTransaction();
}
[-->SurfaceComposerClient.cpp]
voidSurfaceComposerClient::closeGlobalTransaction()
{
......
const size_t N = clients.size();
sp<ISurfaceComposer>sm(getComposerService());
//①先呼叫SF的openGlobalTransaction
sm->openGlobalTransaction();
for (size_t i=0; i<N; i++) {
//②然後呼叫每個SurfaceComposerClient物件的closeTransaction
clients[i]->closeTransaction();
}
//③最後呼叫SF的closeGlobalTransaction
sm->closeGlobalTransaction();
}
上面一共列出了三個函式,它們都是跨程式的呼叫,下面對其一一進行分析。
(1)SurfaceFlinger的openGlobalTransaction分析
這個函式其實很簡單,略看就行了。
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::openGlobalTransaction()
{
android_atomic_inc(&mTransactionCount);//又是一個計數控制
}
(2)SurfaceComposerClient的closeTransaction分析
程式碼如下所示:
[-->SurfaceComposerClient.cpp]
status_tSurfaceComposerClient::closeTransaction()
{
if(mStatus != NO_ERROR)
return mStatus;
Mutex::Autolock _l(mLock);
......
constssize_t count = mStates.size();
if (count) {
//mStates是這個SurfaceComposerClient中儲存的所有layer_state_t陣列,也就是
//每個Surface一個。然後呼叫跨程式的setState
mClient->setState(count, mStates.array());
mStates.clear();
}
returnNO_ERROR;
}
BClient的setState,最終會轉到SF的setClientState上,程式碼如下所示:
[-->SurfaceFlinger.cpp]
status_t SurfaceFlinger::setClientState(ClientIDcid, int32_t count,
const layer_state_t*states)
{
Mutex::Autolock _l(mStateLock);
uint32_t flags = 0;
cid<<= 16;
for(int i=0 ; i<count ; i++) {
const layer_state_t& s = states[i];
sp<LayerBaseClient> layer(getLayerUser_l(s.surface | cid));
if(layer != 0) {
const uint32_t what = s.what;
if (what & ePositionChanged) {
if (layer->setPosition(s.x, s.y))
//eTraversalNeeded表示需要遍歷所有顯示層
flags |= eTraversalNeeded;
}
....
if(flags) {
setTransactionFlags(flags);//這裡將會觸發threadLoop的事件。
}
returnNO_ERROR;
}
[-->SurfaceFlinger.cpp]
uint32_tSurfaceFlinger::setTransactionFlags(uint32_t flags, nsecs_t delay)
{
uint32_t old = android_atomic_or(flags, &mTransactionFlags);
if((old & flags)==0) {
if(delay > 0) {
signalDelayedEvent(delay);
}else {
signalEvent(); //設定完mTransactionFlags後,觸發事件。
}
}
returnold;
}
(3)SurfaceFlinger的closeGlobalTransaction分析
來看程式碼:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::closeGlobalTransaction()
{
if (android_atomic_dec(&mTransactionCount) ==1) {
//注意下面語句的執行條件,當mTransactionCount變為零時才執行,這意味著
//openGlobalTransaction兩次的話,只有最後一個closeGlobalTransaction呼叫
//才會真正地提交事務
signalEvent();
Mutex::Autolock _l(mStateLock);
//如果這次事務涉及尺寸調整,則需要等一段時間
while (mResizeTransationPending) {
status_t err = mTransactionCV.waitRelative(mStateLock, s2ns(5));
if (CC_UNLIKELY(err != NO_ERROR)) {
mResizeTransationPending = false;
break;
}
}
}
}
關於事務的目的,相信讀者已經比較清楚了:
· 就是將一些控制操作(例如setPosition)的修改結果,一次性地傳遞給SF進行處理。
那麼,哪些操作需要通過事務來傳遞呢?通過檢視Surface.h可以知道,下面這些操作需要通過事務來傳遞(這裡只列出了幾個經常用的函式):setPosition、setAlpha、show/hide、setSize、setFlag等。
由於這些修改不像重繪那麼簡單,有時它會涉及其他的顯示層,例如在顯示層A的位置調整後,之前被A遮住的顯示層B,現在可能變得可見了。對於這種情況,所提交的事務會設定eTraversalNeeded標誌,這個標誌表示要遍歷所有顯示層進行處理。關於這一點,來看工作執行緒中的事務處理。
4. 工作執行緒中的事務處理
還是從程式碼入手分析,如下所示:
[-->SurfaceFlinger.cpp]
bool SurfaceFlinger::threadLoop()
{
waitForEvent();
if(LIKELY(mTransactionCount == 0)) {
const uint32_t mask = eTransactionNeeded | eTraversalNeeded;
uint32_ttransactionFlags = getTransactionFlags(mask);
if(LIKELY(transactionFlags)) {
handleTransaction(transactionFlags);
}
}
...
}
getTransactionFlags函式的實現蠻有意思,不妨看看其程式碼,如下所示:
[-->SurfaceFlinger.cpp]
uint32_t SurfaceFlinger::getTransactionFlags(uint32_tflags)
{
//先通過原子操作去掉mTransactionFlags中對應的位。
//而後原子操作返回的舊值和flags進行與操作
return android_atomic_and(~flags,&mTransactionFlags) & flags;
}
getTransactionFlags所做的工作不僅僅是get那麼簡單,它還設定了mTransactionFlags,從這個角度來看,getTransactionFlags這個名字有點名不副實。
接著來看最重要的handleTransaction函式,程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handleTransaction(uint32_ttransactionFlags)
{
Vector< sp<LayerBase> > ditchedLayers;
{
Mutex::Autolock _l(mStateLock);
//呼叫handleTransactionLocked函式處理
handleTransactionLocked(transactionFlags, ditchedLayers);
}
constsize_t count = ditchedLayers.size();
for(size_t i=0 ; i<count ; i++) {
if(ditchedLayers[i] != 0) {
//ditch是丟棄的意思,有些顯示層可能被hide了,所以這裡做些收尾的工作
ditchedLayers[i]->ditch();
}
}
}
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::handleTransactionLocked(
uint32_t transactionFlags, Vector< sp<LayerBase> >&ditchedLayers)
{
//這裡使用了mCurrentState,它的layersSortedByZ陣列儲存了SF中所有的顯示層
constLayerVector& currentLayers(mCurrentState.layersSortedByZ);
constsize_t count = currentLayers.size();
constbool layersNeedTransaction = transactionFlags & eTraversalNeeded;
//如果需要遍歷所有顯示的話。
if(layersNeedTransaction) {
for (size_t i=0 ; i<count ; i++) {
const sp<LayerBase>& layer = currentLayers[i];
uint32_t trFlags = layer->getTransactionFlags(eTransactionNeeded);
if (!trFlags) continue;
//呼叫各個顯示層的doTransaction函式。
constuint32_t flags = layer->doTransaction(0);
if (flags & Layer::eVisibleRegion)
mVisibleRegionsDirty = true;
}
}
if(transactionFlags & eTransactionNeeded) {
if(mCurrentState.orientation != mDrawingState.orientation) {
//橫豎屏如果發生切換,需要對應變換設定。
const int dpy = 0;
const int orientation = mCurrentState.orientation;
const uint32_t type = mCurrentState.orientationType;
GraphicPlane& plane(graphicPlane(dpy));
plane.setOrientation(orientation);
......
}
/*
mLayersRemoved變數在顯示層被移除的時候設定,例如removeLayer函式,這些函式
也會觸發handleTranscation函式的執行
*/
if(mLayersRemoved) {
mLayersRemoved = false;
mVisibleRegionsDirty = true;
const LayerVector& previousLayers(mDrawingState.layersSortedByZ);
const size_t count = previousLayers.size();
for (size_t i=0 ; i<count ; i++) {
const sp<LayerBase>& layer(previousLayers[i]);
if (currentLayers.indexOf( layer ) < 0) {
ditchedLayers.add(layer);
mDirtyRegionRemovedLayer.orSelf(layer->visibleRegionScreen);
}
}
}
free_resources_l();
}
//提交事務處理,有必要進去看看。
commitTransaction();
}
每個顯示層對事務的具體處理,都在它們的doTranscation函式中,讀者若有興趣,可進去看看。需要說明的是,每個顯示層內部也有一個狀態變數,doTransaction會更新這些狀態變數。
回到上面的函式,最後它將呼叫commitTransaction提交事務,程式碼如下所示:
[-->SurfaceFlinger.cpp]
void SurfaceFlinger::commitTransaction()
{
//mDrawingState將使用更新後的mCurrentState
mDrawingState = mCurrentState;
mResizeTransationPending = false;
//觸發一個條件變數,這樣等待在closeGlobalTransaction函式中的執行緒可以放心地返回了。
mTransactionCV.broadcast();
}
8.5.4 SurfaceFlinger的總結
通過前面的分析,使我們感受了SurfaceFlinger的風采。從整體上看,SurfaceFlinger不如AudioFlinger複雜,它的工作集中在工作執行緒中,下面用圖8-23來匯流排一下SF工作執行緒:
圖8-23 SF工作執行緒的流程總結
8.6 擴充思考
本章的擴充思考分三個部分:
· 介紹SharedBufferServer和SharedBufferClient的工作流程。
· 關於ViewRoot一些問題的總結。
· LayerBuffer的工作原理分析。
8.6.1 Surface系統的CB物件分析
根據前文分析可知,Surface系統中的CB,其實是指SharedBuffer家族,它們是Surface系統中對生產者和消費者進行步調控制的中樞機構。先通過圖8-24來觀察整體的工作流程是怎樣的。
圖8-24 SharedBuffer家族使用流程
為書寫方便起見,我們簡稱:
· SharedBufferServer為SBS。
· SharedBufferClient為SBC。
· SharedBufferStack為SBT。
其中SBC和SBS都是建立在同一個SBT上的,所以應先看SBT,下面程式碼列出了其中幾個與讀寫控制有關的成員變數:
[-->SharedBufferStack.h]
class SharedBufferStack{
......
/*
雖然PageFlipping使用Front和Back兩個Buffer就可以了,但是SBT的結構和相關演算法
是支援多個緩衝的。另外,緩衝是按照塊來獲取的,也就是一次獲得一塊緩衝,每塊緩衝用
一個編號表示(這一點在之前的分析已經介紹過了)。
*/
int32_t head;
int32_tavailable; //當前可用的空閒緩衝個數
int32_t queued; //SBC投遞的髒緩衝個數
int32_tinUse; //SBS當前正在使用的緩衝編號
......//上面這幾個引數聯合SBC中的tail,我稱之為控制引數。
}
SBT建立好後,下面就是SBS和SBC的建立了,它們會做什麼特殊工作嗎?
1. SBS和SBC的建立
下面分別看SBS和SBC的建立,程式碼如下所示:
[-->SharedBufferStack.cpp]
SharedBufferServer::SharedBufferServer(SharedClient*sharedClient,
int surface, int num, int32_t identity)
:SharedBufferBase(sharedClient, surface, num, identity)
{
mSharedStack->init(identity);//這個函式將設定inUse為-1
//下面設定SBT中的引數,我們關注前三個
mSharedStack->head = num-1;
mSharedStack->available = num;
mSharedStack->queued = 0;
//設定完後,head=2-1=1,available=2,queued=0,inUse=-1
mSharedStack->reallocMask = 0;
memset(mSharedStack->dirtyRegion, 0,sizeof(mSharedStack->dirtyRegion));
}
再看SBC的建立,程式碼如下所示:
[-->SharedBufferStack.cpp]
SharedBufferClient::SharedBufferClient(SharedClient*sharedClient,
int surface, int num, int32_t identity)
:SharedBufferBase(sharedClient, surface, num, identity), tail(0)
{
tail =computeTail(); //tail是SBC定義的變數,注意它不是SBT定義的。
}
看computeTail函式的程式碼:
[-->SharedBufferStack.cpp]
int32_t SharedBufferClient::computeTail() const
{
SharedBufferStack& stack( *mSharedStack );
int32_t newTail;
int32_t avail;
int32_t head;
do {
avail = stack.available; //available=2,head=1
head = stack.head;
}while (stack.available != avail);
newTail = head - avail + 1;//newTail=1-2+1=0
if(newTail < 0) {
newTail += mNumBuffers;
} elseif (newTail >= mNumBuffers) {
newTail -= mNumBuffers;
}
return newTail;//計算得到newTail=0
}
來看在SBC和SBS建立後,控制引數的變化,如圖8-25所示:
圖8-25 SBC/SBS建立後的示意圖
2. SBC端流程的分析
下面看SBC端的工作流程。
(1)dequeue分析
先看SBC的dequeue函式:
[-->SharedBufferStack.cpp]
ssize_t SharedBufferClient::dequeue()
{
SharedBufferStack& stack( *mSharedStack );
......
//DequeueCondition函式物件
DequeueCondition condition(this);
status_t err = waitForCondition(condition);
//成功以後,available減1,表示當前可用的空閒buffer只有1個
if (android_atomic_dec(&stack.available) == 0) {
......
}
int dequeued = tail; //tail值為0,所以dequeued的值為0。
//tail加1。如果超過2,則重新置為0,這表明tail的值在0,1間迴圈。
tail =((tail+1 >= mNumBuffers) ? 0 : tail+1);
......
//返回的這個dequeued值為零,也就是tail加1操作前的舊值。這一點請讀者務必注意。
returndequeued;
}
其中DequeueCondition的操作函式很簡單,程式碼如下所示:
bool SharedBufferClient::DequeueCondition::operator()(){
returnstack.available > 0;//只要available大於0就算滿足條件,第一次進來肯定滿足
}
用圖8-26來表示dequeue的結果:
圖8-26 dequeue結果圖
注意,在上圖中,0號緩衝用虛線表示,SBC的dequeue函式的返回值用dequeued表示,它指向這個0號緩衝。正如程式碼中註釋的那樣,由於dequeued的值用的是tail的舊值,而tail是SBC定義的變數,不是SBT定義的變數,所以tail在SBS端是不可見的。這就帶來了一個潛在危險,即0號緩衝不能保證當前是真正空閒的,因為SBS可能正在用它,怎麼辦?試看下面的lock。
(2)lock的分析
lock使用了LockCondition,其中傳入的引數buf的值為0,也就是上圖中的dequeue的值,程式碼如下所示:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::lock(int buf)
{
LockCondition condition(this, buf);
status_terr = waitForCondition(condition);
returnerr;
}
看LockCondition的()函式:
boolSharedBufferClient::LockCondition::operator()() {
/*
這個條件其實就是判斷編號為buf的Buffer是不是被使用了。
buf值為0,head值為1,queued為0,inUse為-1
*/
return(buf != stack.head ||
(stack.queued > 0 && stack.inUse!= buf));
}
現在可以知道為什麼SBC需要呼叫dequeue和lock函式了嗎?原來:
· dequeue只是根據本地變數tail計算一個本次應當使用的Buffer編號,其實也就是在0,1之間迴圈。上次用0號緩衝,那麼這次就用1號緩衝。
· lock函式要確保這個編號的Buffer沒有被SF當做FrontBuffer使用。
(3)queue的分析
Activity端在繪製完UI後,將把BackBuffer投遞出去以顯示。接著上面的流程,這個BackBuffer的編號是0。待Activity投遞完後,才會呼叫signal函式觸發SF消費,所以在此之前格局不會發生變化。試看投遞用的queue函式,注意傳入的buf引數為0,程式碼如下所示:
[-->SharedBufferStack.cpp]
status_t SharedBufferClient::queue(int buf)
{
QueueUpdate update(this);
status_t err = updateCondition( update );
......
returnerr;
}
//直接看這個QueueUpdate函式物件
ssize_tSharedBufferClient::QueueUpdate::operator()() {
android_atomic_inc(&stack.queued);//queued增加1,現在該值由零變為1
returnNO_ERROR;
}
至此,SBC端走完一個流程了,結果是什麼?如圖8-27所示:
圖8-27 queue結果圖
0號緩衝被移到queue的區域了,可目前還沒有變數指向它。假設SBC端此後沒有繪製UI的需求,那麼它就會沉默一段時間。
3. SBS端的分析
SBS的第一個函式是retireAndLock,它使用了RetireUpdate函式物件,程式碼如下所示:
[-->SharedBufferStack.cpp]
ssize_t SharedBufferServer::retireAndLock()
{
RetireUpdate update(this, mNumBuffers);
ssize_t buf = updateCondition( update );
returnbuf;
}
這個RetireUpdate物件的程式碼如下所示:
ssize_tSharedBufferServer::RetireUpdate::operator()() {
//先取得head值,為1
int32_t head = stack.head;
//inUse被設定為1。表明要使用1嗎?目前的髒緩衝應該是0才對
android_atomic_write(head, &stack.inUse);
int32_tqueued;
do {
queued = stack.queued; //queued目前為1
if(queued == 0) {
return NOT_ENOUGH_DATA;
}
//下面這個原子操作使得stack.queued減1.
}while (android_atomic_cmpxchg(queued, queued-1, &stack.queued));
//while迴圈退出後,queued減1,又變為0。
//head值也在0,1間迴圈,現在head值變為0了
head =((head+1 >= numBuffers) ? 0 : head+1);
//inUse被設定為0
android_atomic_write(head, &stack.inUse);
// head值被設為0
android_atomic_write(head, &stack.head);
// available加1,變成2.
android_atomic_inc(&stack.available);
returnhead;//返回0
}
retireAndLock的結果是什麼呢?看看圖8-28就知道了。
圖8-28 retireAndLock結果圖
注意上面的available區域,1號緩衝右邊的0號緩衝是用虛線表示的,這表示該0號緩衝實際上並不存在於available區域,但available的個數卻變成2了。這樣不會出錯嗎?當然不會,因為SBC的lock函式要確保這個緩衝沒有被SBS使用。
我們來看SBS端最後一個函式,它呼叫了SBS的unlock,這個unlock使用了UnlockUpdate函式物件,就直接瞭解它好了,程式碼如下所示:
[-->SharedBufferStack.cpp]
ssize_tSharedBufferServer::UnlockUpdate::operator()() {
......
android_atomic_write(-1, &stack.inUse);//inUse被設定為-1
returnNO_ERROR;
}
unlock後最終的結果是什麼呢?如圖8-29所示:
圖8-29 unlock結果圖
比較一下圖8-29和圖8-25,可能會發現兩圖中tail和head剛好反了,這就是PageFlip。另外,上面的函式大量使用了原子操作。原子操作的目的就是為了避免鎖的使用。值得指出的是,updateConditon函式和waitForCondition函式都使用了Mutex,也就是說,上面這些函式物件又都是在Mutex鎖的保護下執行的,為什麼會這樣呢?先來看一段程式碼:
像下面這樣的程式碼,如果有鎖控制的話根本用不著一個while迴圈,因為有鎖的保護,沒有其他執行緒
能夠修改stack.queued的值,所以用while來迴圈判斷android_atomic_cmpxchg沒有什麼意義。
int32_tqueued;
do {
queued = stack.queued;
if(queued == 0) {
return NOT_ENOUGH_DATA;
}
}while (android_atomic_cmpxchg(queued, queued-1, &stack.queued));
對於上面這個問題,我目前還不知道答案,但對其也進行了修改,把函式物件放在鎖外執行,結果在真機上執行沒有出現任何異常現象。也許Google或哪位讀者能給這個問題一個較好的解釋。
為什麼我對生產/消費的同步控制如此感興趣呢?這和自己工作的經歷有些關係。因為之前曾做過一個單寫多讀的跨程式緩衝類,也就是一個生產者,多個消費者。為了保證正確性和一定的效率,我們在演算法上曾做了很多改進,但還是大量使用了鎖,所以我很好奇Google是怎麼做到的,這也體現了一個高手的內功修養。要是由讀者自己來實現,結果會怎樣呢?
8.6.2 ViewRoot的你問我答
ViewRoot是Surfac系統甚至UI系統中一個非常關鍵的類,下面把網上一些關於ViewRoot的問題做個總結,希望這樣能幫助讀者對ViewRoot有更加清楚的認識。
· ViewRoot和View類的關係是什麼?
ViewRoot是View檢視體系的根。每一個Window(注意是Window,比如PhoneWindow)有一個ViewRoot,它的作用是處理layout和View檢視體系的繪製。那麼檢視體系又是什麼呢?它包括Views和ViewGroups,也就是SDK中能看到的View類都屬於檢視體系。根據前面的分析可知,這些View是需要通過draw畫出來的。而ViewRoot就是用來draw它們的,ViewRoot本身沒有draw/onDraw函式。
· ViewRoot和它所控制的View及其子View使用同一個Canvas嗎?
這個問題的答案就很簡單了,我們在ViewRoot的performTraversals中見過。ViewRoot提供Canvas給它所控制的View,所以它們使用同一個Canvas。但Canvas使用的記憶體卻不是固定的,而是通過Surface的lockCanvas得到的。
· View、Surface和Canvas之間的關係是怎樣的?我認為,每一個view將和一個canvas,以及一個surface繫結到一起(這裡的“我”表示提問人)。
這個問題的答案也很簡單。一個Window將和一個Surface繫結在一起,繪製前ViewRoot會從Surface中lock出一個Canvas。
· Canvas有一個bitmap,那麼繪製UI時,資料是畫在Canvas的這個bitmap中嗎?
答案是肯定的,bitmap實際上包括了一塊記憶體,繪製的資料最終都在這塊記憶體上。
· 同一個ViewRoot下,不同型別的View(不同型別指不同的UI單元,例如按鈕、文字框等)使用同一個Surface嗎?
是的,但是SurfaceView要除外。因為SurfaceView的繪製一般在單獨的執行緒上,並且由應用層主動呼叫lockCanvas、draw和unlockCanvasAndPost來完成繪製流程。應用層相當於拋開了ViewRoot的控制,直接和螢幕打交道,這在camera、video方面用得最多。
8.6.3 LayerBuffer的分析
前面介紹了Normal屬性顯示層中的第一類Layer,這裡將介紹其中的第二類LayerBuffer。LayerBuffer會在視訊播放和攝像機預覽等場景中用到,就以Camera的preView(預覽)為例,來分析LayerBuffer的工作原理。
1. LayerBuffer的建立
先看LayerBuffer的建立,它通過SF的createPushBuffersSurfaceLocked得到,程式碼如下所示:
[-->SurfaceFlinger.cpp]
sp<LayerBaseClient> SurfaceFlinger::createPushBuffersSurfaceLocked(
const sp<Client>& client, DisplayID display,
int32_t id, uint32_t w, uint32_t h, uint32_t flags)
{
sp<LayerBuffer> layer = new LayerBuffer(this, display, client,id);
layer->initStates(w, h, flags);
addLayer_l(layer);
returnlayer;
}
LayerBuffer的派生關係,如圖8-30所示:
圖8-30 LayerBuffer的派生關係示意圖
從上圖中可以發現:
· LayerBuffer定義了一個內部類Source類,它有兩個派生類BufferSource和OverlaySource。根據它們的名字,可以猜測到Source代表資料的提供者。
· LayerBuffer中的mSurface其真實型別是SurfaceLayerBuffer。
LayerBuffer建立好了,不過該怎麼用呢?和它相關的呼叫流程是怎樣的呢?下面來分析Camera。
2. Camera preView的分析
Camera是一個單獨的Service,全稱是CameraService,先看CameraService的registerPreviewBuffers函式。這個函式會做什麼呢?程式碼如下所示:
[-->CameraService.cpp]
status_tCameraService::Client::registerPreviewBuffers()
{
int w, h;
CameraParameters params(mHardware->getParameters());
params.getPreviewSize(&w, &h);
/*
①mHardware代表Camera裝置的HAL物件。本書討論CameraHardwareStub裝置,它其實是
一個虛擬的裝置,不過其程式碼卻具有參考價值。
BufferHeap定義為ISurface的內部類,其實就是對IMemoryHeap的封裝
*/
ISurface::BufferHeapbuffers(w, h, w, h,
HAL_PIXEL_FORMAT_YCrCb_420_SP,
mOrientation,
0,
mHardware->getPreviewHeap());
//②呼叫SurfaceLayerBuffer的registerBuffers函式。
status_t ret = mSurface->registerBuffers(buffers);
returnret;
}
上面程式碼中列出了兩個關鍵點,逐一來分析它們。
(1)建立BufferHeap
BufferHeap是ISurface定義的一個內部類,它的宣告如下所示:
[-->ISurface.h]
classBufferHeap {
public:
......
//使用這個建構函式
BufferHeap(uint32_t w, uint32_t h,
int32_t hor_stride, int32_t ver_stride,
PixelFormat format, const sp<IMemoryHeap>& heap);
......
~BufferHeap();
uint32_t w;
uint32_t h;
int32_t hor_stride;
int32_t ver_stride;
PixelFormat format;
uint32_t transform;
uint32_t flags;
sp<IMemoryHeap> heap; //heap指向真實的儲存物件
};
從上面程式碼中可發現,BufferHeap基本上就是封裝了一個IMemoryHeap物件,根據我們對IMemoryHeap的瞭解,它應該包含了真實的儲存物件,這個值由CameraHardwareStub物件的getPreviewHeap得到,這個函式的程式碼如下所示:
[-->CameraHardwareStub.cpp]
sp<IMemoryHeap>CameraHardwareStub::getPreviewHeap() const
{
returnmPreviewHeap;//返回一個成員變數,它又是在哪建立的呢?
}
//上面的mPreivewHeap物件由initHeapLocked函式建立,該函式在HAL物件建立的時候被呼叫
void CameraHardwareStub::initHeapLocked()
{
......
/*
建立一個MemoryHeapBase物件,大小是mPreviewFrameSize * kBufferCount,其中
kBufferCount為4。注意這是一段連續的緩衝。
*/
mPreviewHeap= new MemoryHeapBase(mPreviewFrameSize * kBufferCount);
//mBuffer為MemoryBase陣列,元素為4
for (inti = 0; i < kBufferCount; i++) {
mBuffers[i] = new MemoryBase(mPreviewHeap,
i * mPreviewFrameSize, mPreviewFrameSize);
}
}
從上面這段程式碼中可以發現,CameraHardwareStub物件建立的用於preView的記憶體結構是按圖8-31所示的方式來組織的:
圖8-31 CameraHardwareStub用於preView的記憶體結構圖
其中:
· BufferHeap的heap變數指向一塊MemoryHeap,這就是mPreviewHeap。
· 在這塊MemoryHeap上構建了4個MemoryBase。
(2)registerBuffers的分析
BufferHeap準備好後,要呼叫ISurface的registerBuffers函式,ISurface在SF端的真實型別是SurfaceLayerBuffer,所以要直接看它的實現,程式碼如下所示:
[-->LayerBuffer.cpp]
status_t LayerBuffer::SurfaceLayerBuffer::registerBuffers(
const ISurface::BufferHeap& buffers)
{
sp<LayerBuffer> owner(getOwner());
if (owner != 0)
//呼叫外部類物件的registerBuffers,所以SurfaceLayerBuffer也是一個Proxy哦。
return owner->registerBuffers(buffers);
returnNO_INIT;
}
//外部類是LayerBuffer,呼叫它的registerBuffers函式
status_t LayerBuffer::registerBuffers(constISurface::BufferHeap& buffers)
{
Mutex::Autolock _l(mLock);
//建立資料的來源BufferSource,注意我們其實把MemoryHeap設定上去了
sp<BufferSource> source = new BufferSource(*this, buffers);
status_t result = source->getStatus();
if(result == NO_ERROR) {
mSource = source;//儲存這個資料來源為mSource。
}
returnresult;
}
BufferSource,曾在圖8-30中見識過,它內部有一個成員變數mBufferHeap指向傳入的buffers引數,所以registerBuffers過後,就得到了圖8-32:
圖8-32 registerBuffers的結果示意圖
請注意上圖的箭頭指向,不論中間有多少層封裝,最終的資料儲存區域還是mPreivewHeap。
2.資料的傳輸
至此,Buffer在SF和Camera兩端都準備好了,那麼資料是怎麼從Camera傳遞到SF的呢?先來看資料來源是怎麼做的。
(1)資料傳輸的分析
CameraHardwareStub有一個preview執行緒,這個執行緒會做什麼呢?程式碼如下所示:
[-->CameraHardwareStub.cpp]
//preview執行緒從Thread類派生,下面這個函式在threadLoop中迴圈呼叫
int CameraHardwareStub::previewThread()
{
mLock.lock();
//每次進來mCurrentPreviewFrame都會加1
ssize_t offset = mCurrentPreviewFrame * mPreviewFrameSize;
sp<MemoryHeapBase> heap = mPreviewHeap;
FakeCamera* fakeCamera = mFakeCamera;//虛擬的攝像機裝置
//從mBuffers中取一塊記憶體,用於接收來自硬體的資料
sp<MemoryBase>buffer = mBuffers[mCurrentPreviewFrame];
mLock.unlock();
if(buffer != 0) {
intdelay = (int)(1000000.0f / float(previewFrameRate));
void *base = heap->base();//base是mPreviewHeap的起始位置
//下面這個frame代表buffer在mPreviewHeap中的起始位置,還記得圖8-31嗎?
//四塊MemoryBase的起始位置由下面這個程式碼計算得來
uint8_t *frame = ((uint8_t *)base) + offset;
//取出一幀資料,放到對應的MemoryBase中
fakeCamera->getNextFrameAsYuv422(frame);
//①把含有幀資料的buffer傳遞到上層
if(mMsgEnabled & CAMERA_MSG_PREVIEW_FRAME)
mDataCb(CAMERA_MSG_PREVIEW_FRAME, buffer, mCallbackCookie);
//mCurrentPreviewFrame 遞增,在0到3之間迴圈
mCurrentPreviewFrame = (mCurrentPreviewFrame + 1) % kBufferCount;
usleep(delay);//模擬真實硬體的延時
}
returnNO_ERROR;
}
讀者是否明白Camera preview的工作原理了?就是從四塊記憶體中取一塊出來接收資料,然後再把這塊記憶體傳遞到上層去處理。從緩衝使用的角度來看,mBuffers陣列構成了一個成員個數為四的緩衝佇列。preview通過mData這個回撥函式,把資料傳遞到上層,而CameraService實現了mData這個回撥函式,這個回撥函式最終會呼叫handlePreviewData,直接看handlePreviewData即可,程式碼如下所示:
[-->CameraService.cpp]
voidCameraService::Client::handlePreviewData(const sp<IMemory>& mem)
{
ssize_t offset;
size_t size;
//注意傳入的mem引數,它實際上是Camera HAL建立的mBuffers陣列中的一個
//offset返回的是這個陣列在mPreviewHeap中的偏移量
sp<IMemoryHeap> heap = mem->getMemory(&offset, &size);
if (!mUseOverlay)
{
Mutex::Autolock surfaceLock(mSurfaceLock);
if(mSurface != NULL) {
//呼叫ISurface的postBuffer,注意我們傳入的引數是offset。
mSurface->postBuffer(offset);
}
}
......
}
上面的程式碼是什麼意思?我們到底給ISurface傳什麼了?答案很明顯:
· handlePreviewData就是傳遞了一個偏移量,這個偏移量是mBuffers陣列成員的首地址。可用圖8-33來表示:
圖8-33 handlePreviewData示意圖
有了圖8-33,讀者明白資料傳遞的工作原理了嗎?
下面看SurfaceLayerBuffer的postBuffer函式,不過它只是一個小小的代理,真正的工作由外部類LayerBuffer完成,直接看它好了,程式碼如下所示:
[-->LayerBuffer.cpp]
void LayerBuffer::postBuffer(ssize_t offset)
{
sp<Source> source(getSource());//getSource返回mSource,為BufferSource型別
if(source != 0)
source->postBuffer(offset);//呼叫BufferSource的postBuffer函式。
}
[-->LayerBuffer.cpp]
voidLayerBuffer::BufferSource::postBuffer(ssize_t offset)
{
ISurface::BufferHeap buffers;
{
Mutex::Autolock _l(mBufferSourceLock);
buffers = mBufferHeap;//還記得圖8-32嗎?
if(buffers.heap != 0) {
//BufferHeap的heap變數指向MemoryHeap,下面取出它的大小
const size_t memorySize = buffers.heap->getSize();
//做一下檢查,判斷這個offset是不是有問題
if ((size_t(offset) + mBufferSize) > memorySize) {
LOGE("LayerBuffer::BufferSource::postBuffer() "
"invalid buffer(offset=%d, size=%d, heap-size=%d",
int(offset),int(mBufferSize), int(memorySize));
return;
}
}
}
sp<Buffer> buffer;
if (buffers.heap != 0) {
//建立一個LayerBuffer::Buffer
buffer = new LayerBuffer::Buffer(buffers, offset, mBufferSize);
if(buffer->getStatus() != NO_ERROR)
buffer.clear();
setBuffer(buffer);//setBuffer?我們要看看
//mLayer就是外部類LayerBuffer,呼叫它的invalidate函式將觸發SF的重繪
mLayer.invalidate();
}
}
void LayerBuffer::BufferSource::setBuffer(
const sp<LayerBuffer::Buffer>& buffer)
{
//setBuffer函式就是簡單地將new出來的Buffer設定給成員變數mBuffer,這麼做會有問題嗎?Mutex::Autolock_l(mBufferSourceLock);
mBuffer = buffer; //將新的buffer設定為mBuffer,mBuffer原來指向的那個被delete
}
從資料生產者角度看,postBuffer函式將不斷地new一個Buffer出來,然後將它賦值給成員變數mBuffer,也就是說,mBuffer會不斷變化。現在從緩衝的角度來思考一下這種情況的結果:
· 資料生產者有一個含四個成員的緩衝佇列,也就是mBuffers陣列。
· 而資料消費者只有一個mBuffer。
這種情況會有什麼後果呢?請記住這個問題,我們到最後再來揭示。下面先看mBuffer的型別Buffer是什麼。
(2)資料使用的分析
Buffer被定義成LayerBuffer的內部類,程式碼如下所示:
[-->LayerBuffer.cpp]
LayerBuffer::Buffer::Buffer(constISurface::BufferHeap& buffers,
ssize_t offset, size_t bufferSize)
:mBufferHeap(buffers), mSupportsCopybit(false)
{
//注意,這個src被定義為引用,所以修改src的資訊相當於修改mNativeBuffer的資訊
NativeBuffer& src(mNativeBuffer);
src.crop.l = 0;
src.crop.t = 0;
src.crop.r = buffers.w;
src.crop.b = buffers.h;
src.img.w =buffers.hor_stride ?: buffers.w;
src.img.h =buffers.ver_stride ?: buffers.h;
src.img.format = buffers.format;
//這個base將指向對應的記憶體起始地址
src.img.base =(void*)(intptr_t(buffers.heap->base()) + offset);
src.img.handle = 0;
gralloc_module_tconst * module = LayerBuffer::getGrallocModule();
//做一些處理,有興趣的讀者可以去看看。
if(module && module->perform) {
int err = module->perform(module,
GRALLOC_MODULE_PERFORM_CREATE_HANDLE_FROM_BUFFER,
buffers.heap->heapID(), bufferSize,
offset, buffers.heap->base(),
&src.img.handle);
mSupportsCopybit = (err == NO_ERROR);
}
}
上面是Buffer的定義,其中最重要的就是這個mNativeBuffer了,它實際上儲存了mBuffers陣列成員的首地址。
下面看繪圖函式,也就是LayerBuffer的onDraw函式,這個函式由SF的工作執行緒呼叫,程式碼如下所示:
[-->LayerBuffer.cpp]
void LayerBuffer::onDraw(const Region& clip)const
{
sp<Source> source(getSource());
if(LIKELY(source != 0)) {
source->onDraw(clip);//source實際型別是BufferSource,我們去看看。
} else{
clearWithOpenGL(clip);
}
}
void LayerBuffer::BufferSource::onDraw(constRegion& clip) const
{
sp<Buffer> ourBuffer(getBuffer());
......//使用這個Buffer,注意使用的時候沒有鎖控制
mLayer.drawWithOpenGL(clip, mTexture);//生成一個貼圖,然後繪製它
}
其中getBuffer函式返回mBuffer,程式碼如下所示:
sp<LayerBuffer::Buffer>LayerBuffer::BufferSource::getBuffer() const
{
Mutex::Autolock_l(mBufferSourceLock);
returnmBuffer;
}
從上面的程式碼中能發現,mBuffer的使用並沒有鎖的控制,這會導致什麼問題發生呢?請再次回到前面曾強調要記住的那個問題。此時生產者的佇列有四個元素,而消費者的佇列只有一個元素,它可用圖8-34來表示:
圖8-34 資料傳遞的問題示意圖
從上圖可以知道:
· 使用者使用mBuffer,這是在SF的工作執行緒中做到的。假設mBuffer實際指向的記憶體為mBuffers[0]。
· 資料生產者迴圈更新mBuffers陣列各個成員的資料內容,這是在另外一個執行緒中完成的。由於這兩個執行緒之間沒有鎖同步,這就造成了當使用者還在使用mBuffers[0]時,生產者又更新了mBuffers[0]。這會在螢幕上產生混雜的影象。
經過實際測試得知,如果給資料使用端加上一定延時,螢幕就會出現不連續的畫面,即前一幀和後一幀的資料混雜在一起輸出。
從程式碼的分析來看,這種方式確實有問題。我在真實裝置上測試的結果,也在一定程度上驗證了這一點。通過修改LayerBuffer來解決這問題的難度比較大,是否可在讀寫具體快取時加上同步控制呢(例如使用mBuffers[0]的時候呼叫一下lock,用完後呼叫unlock)?這樣就不用修改LayerBuffer了。讀者可再深入研究這個問題。
8.7 本章小結
本章可能是全書難度最大的一章了。在這一章的講解中,我們把打通任督二脈做為破解Surface系統的突破口:
· 應用程式和Surface的關係,這是任脈。
· Surface和SurfaceFlinger的關係,這是督脈。
其中,打通任脈的過程是比較曲折的,從應用程式的Activity開始,一路追蹤到ViewRoot、WindowManagerService。任脈被打通後,還只是解決了Java層的問題,而督脈則集中在Native層。在必殺技aidl工具的幫助下,我們首先成功找到了Surface乾坤大挪移的蹤跡。此後在精簡流程方法的幫助下,乘勝追擊,對Surface以及SurfaceFlinger進行了深入分析。我希望讀者在閱讀過程中,也要把握流程,這樣就不至於迷失在程式碼中了。
在擴充部分,對Surface系統中CB物件的工作流程、ViewRoot的一些問題、以及LayerBuffer進行了較為詳細的介紹。
相關文章
- 深入理解Flutter UI系統FlutterUI
- 深入理解分散式系統分散式
- 深入理解計算機系統計算機
- Android 深入理解 Notification 機制Android
- 深入理解計算機系統:程式計算機
- 深入理解Android訊息機制Android
- 深入理解Android逆向除錯原理Android除錯
- Linux作業系統分析 | 深入理解系統呼叫Linux作業系統
- 深入理解Java反射(一)Java反射
- 推薦系統一——深入理解YouTube推薦系統演算法演算法
- 深入理解 Android 中的各種 ContextAndroidContext
- 深入理解Android 之 Activity啟動流程(Android 10)Android
- 深入理解margin
- 深入理解ReactReact
- 深入理解KVO
- 深入理解 ReentrantLockReentrantLock
- 深入理解 PWA
- 深入理解BFC
- 深入理解volatile
- 深入理解MVCMVC
- 深入理解 TypeScriptTypeScript
- 深入理解JSCoreJS
- 深入理解JavaScriptCoreJavaScript
- 深入理解Isolate
- 深入理解 JVMJVM
- 深入理解HashMapHashMap
- 深入理解ThreadLocalthread
- 深入理解TransformORM
- 深入理解 GitGit
- 深入理解reduxRedux
- BFC深入理解
- 深入理解paddingpadding
- 深入理解JSXJS
- 深入理解 SynchronizationContextContext
- 深入理解JVMJVM
- 深入理解AQSAQS
- 深入理解Plasma(一)Plasma 框架ASM框架
- 深入理解 tcp 協議(一)TCP協議
- 深入理解蘋果系統(Unicode)字串的排序方法蘋果Unicode字串排序