論文第5章:Android繪圖平臺的實現

面向移動設備的矢量繪圖平臺設計與實現html

Design and Implementation of Mobile Device-oriented Vector Drawing Platformjava

引用本論文: 張雲貴. 面向移動設備的矢量繪圖平臺設計與實現[D]. 北京:北京理工大學軟件學院, 2013.android

本論文的類似度爲0%,是源創論文。歡迎評閱討論,請勿抄襲,如需更多資料請在博客留言。git

若是在研究或論文中使用到,歡迎回復或私信你的學校、姓名、研究領域,並在論文中添加引用或致謝。感謝你對開放成果的尊重和鼓勵。github

 

第5章 Android繪圖平臺的實現

本章闡述了Android繪圖平臺的實現方法,主要是在跨平臺內核的基礎上實現Android畫布適配器和視圖適配器,對圖形顯示優化技術進行了實驗研究。緩存

5.1 開發環境

5.1.1 SWIG的工做原理分析

如圖5‑1所示,藉助於SWIG實現Android程序的Java代碼經過JNI訪問C++的類。在編譯階段SWIG工具從C++生成JNI的Java類文件和相應的C++實現文件,該實現文件與原有的C++實現文件一塊兒經過NDK編譯爲本地動態庫。框架

51_thumb5

圖5‑1 Android程序調用C++類的原理異步

如圖5‑2所示,利用SWIG的Director特性,指定某個具備虛函數的C++類可重定位,而後再生成C++導出函數文件和JNI的Java類文件。在應用層中從對應的JNI類繼承並實現其函數,在執行該C++類的虛函數時對應的Android函數就被執行。函數

52_thumb2

圖5‑2 Android類從C++類的虛函數重載的原理工具

圖5‑2中,SwigDirector_SomeClass從C++的SomeClass派生,Android類從JNI的SomeClass類派生,在SwigDirector_SomeClass中經過調用JNIEnv類的CallStaticVoidMethod等函數實如今C++中調用Java的類函數,這樣Android類中相應的重載函數便獲得調用,實現使用Android SDK的Java類來擴展C++類。

5.1.2 SWIG的運行性能分析

TouchVG平臺使用SWIG實現Android程序的Java類與內核的C++類之間的雙向調用,即Java類經過JNI調用C++類、C++類利用Director特性回調Java類。

本文對這兩種調用方式進行評測,結果見圖5‑3。

53_thumb4

圖5‑3 SWIG在Android中的性能評測結果

圖5‑3包含下列四個評測項目:

(1)回調繪圖:Android程序經過JNI調用跨平臺內核的一個測試函數,在該測試函數中屢次回調畫布適配器的繪製直線段的函數,在該繪製函數中使用android.graphics包繪圖。其性能影響因素有JNI調用、回調和繪圖。

(2)回調不繪圖:與上一項目的差異是將畫布適配器的繪製函數改成空實現,不受圖形庫的影響。其性能影響因素有JNI調用和回調。

(3)直接繪圖:Android程序直接調用繪製直線段的函數,與JNI無關。

(4)正向調用:Android程序經過JNI屢次調用跨平臺內核的一個測試函數。其性能影響因素是JNI調用,與JNI回調及繪圖無關。

評測結果代表,基於虛函數重定位技術的回調方式的性能與普通的JNI調用方式的差異較小,SWIG所增長的封裝函數並不會使繪圖性能明顯降低。

5.1.3 開發方式

Android繪圖平臺的實現方式如圖5‑4所示,編譯獲得的繪圖平臺JAR包和內核本地動態庫可供應用程序使用。藉助於SWIG的Director機制,使用Android SDK實現了畫布適配器和視圖適配器,實現對內核功能的擴展。

將跨平臺內核使用NDK編譯到本地動態連接庫中,接口形式爲JNI和封裝類庫。採用SWIG將C++類轉換爲JNI的Java類,SWIG所生成的C++導出函數文件與跨平臺內核的代碼文件一塊兒編譯爲動態庫。編譯過程當中使用Python腳本自動修正SWIG所生成的代碼中的缺陷,並自動將包含中文字符的文件由UTF8臨時轉換爲GBK編碼以便正常編譯轉換。

54_thumb2

圖5‑4 Android繪圖平臺的實現方式

對於Android本地動態連接庫的調試定位難題,利用NDK提供的日誌輸出C函數和庫文件,經過輸出日誌文字的方式來解決。用該方法診斷出了SWIG引發的JNI內存問題所在位置,最終採用Python腳本自動修正SWIG所生成的文件缺陷。

5.1.4 開發工具

使用了下列工具分別在Mac OS X 10.7和Windows 7上開發Android繪圖平臺:

(1)Android開發包(the ADT Bundle)r21.1,能夠在Eclipse中調試本地動態庫。

(2)Android NDK r8e,用於開發本地動態庫。

(3)SWIG 2.0.10,用於從C++頭文件生成JNI的類文件和C++封裝文件。

(4)Python 2.7,用於運行Python腳本自動修正SWIG所生成的文件缺陷。

(5)MSYS(Minimalist GNU for Windows)1.0,用於在Windows上模擬UNIX環境,執行Shell編譯腳本。

5.1.5 SWIG編譯配置

在touchvg.swig文件[1]中配置SWIG編譯選項,主要配置內容以下:

(1)在文件前面定義下面兩個宏:

SWIG_JAVA_NO_DETACH_CURRENT_THREAD

SWIG_JAVA_ATTACH_CURRENT_THREAD_AS_DAEMON

定義前者以便在每次調用本地代碼後不與當前線程斷開(使用SWIG的Director機制後,在本地函數調用結束時一些JNI對象還須要繼續有效,不能與當前線程斷開)。定義後者將JNI環境附加在守護線程上(默認是附加在界面主線程上,在Activity退出時可能會崩潰)。

(2)輸出JNI_OnLoad函數。Dalvik虛擬機要求必須實現JNI_OnLoad函數,本平臺僅簡單返回JNI_VERSION_1_6,由SWIG生成的代碼自動註冊本地函數。

(3)指定GiCanvas和GiView須要生成重定向類,並導出相應的頭文件。

(4)輸出要在Android代碼中使用的內核接口。例如,GiCoreView類。

(5)添加TmpJOBJ輔助類,在析構函數中自動釋放JNI本地引用對象。SWIG生成的Director類中某些形參的本地引用對象沒有釋放,會因超出256個JNI引用對象的限制而溢出崩潰。例如,在GiCanvas的drawBitmap函數中,name字符串對象所對應的本地引用對象「jstring jname」在調用了NewStringUTF函數後沒有調用DeleteLocalRef函數。

本文針對該問題提出的解決方法:將SWIG所生成的封裝文件中的「jstring jname = 0」替換爲「jstring jname = 0; TmpJOBJ jtmp(jenv, &jname)」,經過TmpJOBJ的析構做用自動調用DeleteLocalRef函數釋放引用。使用Python腳本[2]自動進行替換SWIG生成的封裝文件中的這類問題。

編寫了Shell腳本(mk/swig.sh),用於運行SWIG工具生成JNI導出函數的封裝文件(touchvg_java_wrap.cpp)和JNI類文件。JNI類的包名爲touchvg.jni,其文件輸出到工程的src/touchvg/jni目錄下,將與視圖適配器的代碼(src/touchvg/view目錄)共同生成爲一個JAR文件。

5.1.6 NDK編譯配置

Android繪圖平臺的代碼目錄結構見第20頁的圖3‑7。在工程的jni/Android.mk文件[3]中配置本地動態庫的NDK編譯選項,主要有:

(1)基於絕對地址$(LOCAL_PATH)/../../../core/include在LOCAL_C_INCLUDES中指定內核的頭文件路徑,基於相對地址 ../../../core/src 在LOCAL_SRC_FILES中指定跨平臺內核的實現文件(*.cpp,使用絕對的路徑沒法編譯)。

(2)由於SWIG的Director代碼使用了RTTI運行時類型信息,因此在LOCAL_CFLAGS中指定-frtti選項。

(3)爲了使用STL,在jni/Application.mk中指定「APP_STL := stlport_static」。

本文編寫了Shell腳本(ndk.sh),在其中進入android\demo\jni目錄自動運行ndk-build編譯出本地動態連接庫libtouchvg.so,在編譯過程當中自動應用Android.mk中的配置信息。Onur Cinar[4]介紹了在Android.mk中包含腳本的方法,可自動運行腳本。

本文在多個平臺編譯時發現Shell腳本文件應使用Unix行結束符(LF),不能是DOS結束符或Mac結束符,儘量避免使用中文字符。

5.2 基於Android Canvas實現畫布適配器

在第12頁的2.2.3節介紹了Android二維繪圖主要涉及的框架。本文主要基於兩種視圖類設計繪圖視圖類:android.view.View和android.view.SurfaceView,在繪圖視圖類中使用Android Canvas畫布類(使用android.graphics包)渲染。

5.2.1 畫布原語與Android Canvas的映射

本文基於android.graphics包設計畫布適配器類touchvg.view.CanvasAdapter,該類實現touchvg.jni.GiCanvas中的畫布原語函數,後者是經過SWIG從跨平臺內核的GiCanvas接口自動生成的。在內核中調用畫布接口GiCanvas的函數時,畫布適配器將被回調執行,從而容許使用Android Canvas渲染。

畫布適配器主要使用了android.graphics包中這些類:Canvas畫布類訪問繪圖函數接口,Paint類指定顏色等繪圖屬性,Path類構建路徑,Bitmap指定位圖數據。在繪圖視圖的onDraw函數中將Canvas畫布對象傳入畫布適配器,後續繪圖將在該畫布對象上進行。在離屏位圖上渲染時,從位圖構建畫布對象,接着傳入畫布適配器。

由於Paint對象只能指定一個顏色,沒法區分畫筆顏色和畫刷顏色,因此畫布適配器針對畫筆、畫刷和文字顯示分別使用一個Paint對象:mPen、mBrush、mTextPen。以顯示一個紅邊藍底的橢圓爲例,先設置mPen的顏色爲紅色、mBrush的顏色爲藍色,而後分別使用mPen和mBrush做爲參數繪製橢圓。爲了讓文字顏色和圖形顏色同步,在setPen函數中同時設置畫筆mPen和文字屬性mTextPen的顏色。

這三種Paint對象的參數設置見表5‑1。

表5‑1 Paint對象的參數設置

畫筆

畫刷

文字

mPen.setAntiAlias(true)

 

mTextPen.setAntiAlias(true)

mPen.setDither(true)

 

mTextPen.setDither(true)

mPen.setStyle(STROKE)

mBrush.setStyle(FILL)

 

mPen.setPathEffect(null)

mBrush.setColor(0)

 

mPen.setStrokeCap(Cap.ROUND)

   

mPen.setStrokeJoin(Join.ROUND)

   

表5‑1中,畫刷默認填充顏色爲透明色,即不填充。畫筆的默認線型爲實線,線端爲圓端,這樣在繪製短線時更像一個圓點。爲了讓點線等虛線類型的空白間隙整齊,在setPen函數中對全部虛線類型設置平端的線端類型。

與iOS繪圖平臺的實現相似,Android畫布適配器按表5‑2所示的映射方法實現了畫布原語函數。由跨平臺內核中的TestCanvas類生成和顯示矢量圖形和圖像。

在實現這些函數時,本文對下列內容進行了特殊處理或總結。

(1)在View中調用畫布適配器的clearRect函數,沒法使指定區域透明,如圖5‑5(i)所示。只能在原有圖形基礎上填充顏色,指定透明色將填充爲黑色。在SurfaceView中調用畫布適配器的clearRect函數,能夠擦除指定區域內的圖形,變爲透明區域,如圖5‑5(k)所示。

(2)當程序和視圖使用了硬件加速特性後,調用clipPath會崩潰,其緣由是在硬件加速時不支持clipPath函數。解決方法是在UnsupportedOperationException異常出現後將對應的視圖的層類型設置爲軟件實現方式(LAYER_TYPE_SOFTWARE)。

(3)在SurfaceView視圖中使用渲染線程連續繪圖可以達到48~56FPS的更新速度,實驗效果如圖5‑5(n)所示。測試用例爲繪製不斷延長的三次貝塞爾曲線,測試條件爲MOTO MZ606平板電腦(Android 4.0.3,1280×800)。

表5‑2 畫布原語與android.graphics的映射

畫布原語

測試號

Android函數對應關係

clearRect

i k

mCanvas.drawColor(mBkColor, Mode.CLEAR),須要設置剪裁區域

drawRect

a

mCanvas.drawRect,使用mPen和mBrush

drawEllipse

b

mCanvas.drawOval,寬高不超過1時使用drawPoint

beginPath

多個

建立路徑對象mPath

moveTo

多個

mPath.moveTo

lineTo

c

mPath.lineTo

bezierTo

e

mPath.cubicTo

quadTo

f

mPath.quadTo

closePath

c

mPath.close

drawPath

多個

mCanvas.drawPath(mPath, mBrush)、.drawPath(mPath, mPen)

drawHandle

g

mCanvas.drawBitmap,在指定點顯示

drawBitmap

g

mCanvas.drawBitmap,指定Matrix矩陣變換對象

drawTextAt

h

mTextPen.setTextSize、mCanvas.drawText,用到FontMetrics

setPen

多個

mPen.setColor、mPen.setStrokeWidth、mTextPen.setColor

mPen.setPathEffect、mPen.setStrokeCap

setBrush

多個

mBrush.setColor

saveClip

m

mCanvas.save(CLIP_SAVE_FLAG)

restoreClip

m

mCanvas.restore()

clipRect

m

mCanvas.clipRect

clipPath

m

mCanvas.clipPath,硬件加速時須要將視圖的層改成軟件實現類型

drawLine

d

mCanvas.drawLine

注:其中的測試號爲圖5‑5中的測試子圖號。

55_thumb4

圖5‑5 Android畫布適配效果

5.2.2 圖像的顯示和管理

在繪圖視圖中管理圖像對象,畫布適配器從視圖獲取圖像。顯示接口函數爲:

void drawBitmap(String name, float xc, float yc, float w, float h, float angle)

其中,使用名稱name標識圖像對象,(xc,yc)爲圖像的中心顯示位置,w和h爲顯示目標寬高,angle爲旋轉的角度(世界座標系中的逆時針方向)。

圖像繪製的基點在圖像的左上角,畫布的座標系爲ULO類型,繪製過程爲:

(1)根據name從繪圖視圖獲取Bitmap對象;

(2)計算變換矩陣:將顯示基點由圖像的左上角平移到中心,反向旋轉angle角度(弧度轉換爲度),將寬高分別放縮到w和h,最後平移到(xc,yc)。

(3)使用此矩陣顯示圖像對象。

本文實驗發如今顯示大圖片時,加載圖片所需時間遠大於顯示圖像的時間,所以減小圖片加載次數能加快顯示速度。本文采用下面兩種方法進行圖像管理:

(1)在繪圖視圖類中使用LruCache緩存圖片。定義LruCache<String, Bitmap>類型的成員變量,以圖像標識串(drawBitmap中的name)爲鍵值管理圖像對象。

(2)加載圖片前先檢查圖片的寬高,若是太大就以下降採樣率方式加載圖片。

5.3 繪圖視圖的設計和實驗

爲了提升視圖的顯示質量和性能,本文針對View、SurfaceView進行了實驗。

5.3.1 實現方式

繪圖視圖使用畫布適配器CanvasAdapter繪圖,由跨平臺內核中的GiCoreView和TestCanvas類自動顯示測試圖形。繪圖視圖類的關係見圖5‑6,實現方式說明以下。

(1)GraphView。從View派生,在onDraw中繪圖,調用invalidate()重繪。在onDraw中調用內核視圖的drawAll函數顯示全部圖形。在觸摸響應函數中調用內核視圖的onGesture函數傳遞手勢動做,由後者在某個交互命令中調用視圖的redraw等函數,這將回調到GraphView的視圖適配器(ViewAdapter),後者調用視圖的invalidate()標記須要重繪。

(2)面板表面視圖。從SurfaceView派生。調用setZOrderOnTop(true)設置爲面板窗口,顯示於宿主窗口之上。調用getHolder().setFormat(TRANSPARENT)設置其Surface背景透明,以顯示宿主窗口的內容。在Surface就緒和刷新顯示時啓動渲染線程,在繪圖線程中獲取畫布繪圖,由內核視圖的drawAll函數顯示全部圖形。

(3)媒體表面視圖。從SurfaceView派生。默認就是媒體窗口,顯示於宿主窗口之下,自動在宿主窗口上設置透明區域以便讓SurfaceView上的內容可見。在Surface就緒和刷新顯示時啓動渲染線程,在繪圖線程中獲取畫布繪圖。

(4)GraphViewCached。從View派生,使用一個位圖緩存圖形內容,在onDraw函數中顯示該位圖。

內核調用regenAll函數時銷燬該位圖,下次onDraw函數執行時從新生成位圖。應用增量繪圖技術,添加新圖形後調用regenAppend函數,直接在該位圖上繪製新圖形,下次onDraw函數執行時顯示有新內容的位圖。

56_thumb2

圖5‑6 Android渲染視圖的類關係

(5)靜態View + 動態View。在佈局視圖中建立兩個基於View的視圖類(GraphView和DynDrawStdView),分別顯示不變的圖形和常常改變的內容,前者渲染的內容一般較多。

(6)面板表面視圖 + View。在佈局視圖中建立GraphSfView和DynDrawStdView視圖。在GraphSfView中顯示靜態圖形,GraphSfView位於窗口頂端。在DynDrawStdView中顯示動態圖形。

(7)靜態View + 面板表面視圖。在佈局視圖中建立GraphView和DynDrawSfView視圖。在GraphView中顯示靜態圖形,在DynDrawSfView中顯示動態圖形。DynDrawSfView是面板表面視圖,在子線程中獲取畫布繪圖,每次刷新顯示時啓動渲染線程。

(8)面板表面視圖 + 面板表面視圖。在兩個位於窗口頂端的視圖類中分別顯示靜態圖形和動態圖形。

(9)媒體表面視圖 + View。在GraphSfView中顯示靜態圖形,GraphSfView位於根視圖層次的底端,在DynDrawStdView中顯示動態圖形。

(10)媒體表面視圖 + 面板表面視圖。在兩個基於SurfaceView的視圖類中分別渲染靜態圖形和動態圖形,靜態圖形在窗口底端渲染,動態圖形在頂端渲染。

以上的視圖類按表5‑3調用內核視圖的顯示函數,由GiCoreView顯示圖形。

表5‑3 Android視圖類與內核顯示函數的對應關係

視圖類

對應的GiCoreView顯示函數

視圖類

GiCoreView函數

GraphView

drawAll

DynDrawStdView

dynDraw

GraphSfView

drawAll

DynDrawSfView

dynDraw

GraphViewCached

drawAppend、dynDraw、drawAll

   
5.3.2 實驗結果

本文針對View、SurfaceView進行了上述十組實驗,實驗結果見圖5‑7和表5‑4。實驗條件爲:Android 3.0、模擬器(320×480),其中使用較小分辨率是便於在本文中插入屏幕截圖。在MOTO MZ606平板電腦(Android 4.0.3,1280×800)上實驗後也得出相同的結論。

從這十組實驗獲得下列結論:

(1)普通的繪圖方式基於View實現定製視圖,在onDraw函數中使用Canvas進行繪圖。該方式使用簡單,適合繪製簡單圖形。缺點是繪圖速度較慢,刷新一個視圖會使同級的其餘視圖被動刷新,容易引發顯示性能降低問題。

(2)交互式繪圖顯示速度快的方式有:使用增量繪圖技術的普通視圖(GraphViewCached);在SurfaceView中繪製動態圖形的雙層繪圖視圖。使用增量繪圖技術的優勢是能夠只須要一個繪圖視圖,雙層繪圖視圖的優勢是能夠在子線程中繪圖,能提升刷新幀率。能夠將二者的優勢結合起來,在GraphViewCached中顯示靜態圖形,在SurfaceView中繪製動態圖形。

(3)若是要在SurfaceView中異步繪製靜態圖形,合適的使用條件有:a、與其餘內容視圖沒有重疊區域;b、在窗口頂端透明顯示,不要在此區域顯示按鈕等臨時界面控件;c、在窗口底端顯示,窗口裏沒有不透明的大面積界面元素。

57_thumb2

圖5‑7 Android渲染視圖效果

表5‑4 Android渲染視圖的組合實驗狀況

圖號

視圖搭配類型

顯示速度和問題

a

GraphView

b

GraphSfView(面板表面視圖)

慢,圖形遮擋按鈕

c

GraphSfView(媒體表面視圖)

d

GraphViewCached

動態和靜態繪圖都很快

e

靜態View + 動態View

慢,另外一視圖被動刷新

f

面板表面視圖 + View

快,靜態圖形遮擋按鈕和動態圖形

g

靜態View + 面板表面視圖

h

面板表面視圖 + 面板表面視圖

快,動態繪圖拖尾明顯,遮擋按鈕

i

媒體表面視圖 + View

快,不透明視圖會遮擋靜態圖形

-

媒體表面視圖 + 面板表面視圖

快,不透明視圖會遮擋靜態圖形

注:其中的圖號爲圖5‑7中的子圖號。

5.4 Android繪圖平臺的結構

5.4.1 靜態結構

根據繪圖視圖的實驗結果,Android繪圖平臺按圖5‑8設計靜態類結構(省略了跨平臺內核的內部結構和SWIG的Director類),相應類的說明見表5‑5。

58_thumb6

圖5‑8 Android繪圖適配模塊的結構

表5‑5 Android繪圖適配模塊的類

含義和職責

GraphViewHelper

面向應用程序的繪圖封裝接口類,提供經常使用API

GraphViewCached

顯示靜態圖形的視圖類,使用了基於緩存位圖的增量繪圖技術,負責觸摸手勢識別,委託內核的GiCoreView實現圖形顯示和手勢操做

DynDrawSfView

顯示動態圖形的SurfaceView視圖類,委託GiCoreView顯示動態圖形

ViewAdapter

視圖適配器,容許內核回調Android視圖,通知刷新顯示

CanvasAdapter

使用Android Canvas實現的畫布適配器

GiCoreView

跨平臺內核的視圖分發器,託管圖形對象,分發顯示請求和手勢信息給圖形列表和當前命令

應用程序使用繪圖視圖有兩種方式:(1)僅使用GraphViewCached視圖,適合圖形量不太多的場合。(2)經過GraphViewHelper建立一個佈局視圖,自動建立GraphViewCached和DynDrawSfView視圖,適合動態繪圖幀率要求較高的場合。

5.4.2 應用效果

在Android繪圖平臺(屬於TouchVG框架)中應用多層繪圖技術分離靜態圖形視圖和動態圖形視圖,提升了動態交互式繪圖的回顯速度。在靜態圖形視圖中應用增量繪圖技術,在連續繪製曲線圖形時沒有明顯的拖尾現象。所以,繪圖體驗較流暢。

在跨設備平臺的內核中使用繪圖命令能夠顯示各類圖形,在內核視圖中使用仿射變換能夠實現放縮顯示。圖5‑9展現了在不一樣Android版本的模擬器和平板電腦上的實際繪圖效果。

59_thumb2

圖5‑9 Android綜合繪圖效果

5.5 本章小結

本章詳細描述了SWIG在Android中的應用方法和擴展機制,針對出現的本地引用問題提出了修正方法。實驗代表,SWIG所增長的封裝函數並不會使繪圖性能明顯降低。描述了基於Android Canvas實現畫布適配器的方式,實現了圖形和圖像的矢量化顯示。畫布適配器的單元測試使用了跨平臺內核自動繪製圖形,證實在內核中可使用C++在Android上交互式繪圖。

對普通視圖、面板表面視圖和媒體窗口進行了組合實驗,總結出交互式繪圖顯示速度快、不出現遮擋問題的兩種方式:使用增量繪圖技術的單一視圖方式;在SurfaceView中繪製動態圖形的雙層視圖方式。

最後給出了Android繪圖平臺的設計結構和應用效果。


[1] 詳細的SWIG編譯選項見文件:https://raw.github.com/rhcad/vglite/master/android/demo/jni/touchvg.swig 。

[2] Python腳本見文件:https://raw.github.com/rhcad/vglite/master/android/demo/jni/replacejstr.py 。

[3] NDK編譯配置文件見:https://raw.github.com/rhcad/vglite/master/android/demo/jni/Android.mk 。

[4] Onur Cinar. Pro Android C++ with the NDK. Berkeley: Apress, 2012

相關文章
相關標籤/搜索