Android Graphic : apk and Skia/OpenGL|ES

Android apk裏面的畫圖分爲2D和3D兩種:2D是由 Skia 來實現的,也就是咱們在框架圖上看到的SGL,SGL也會調用部分opengl的內容來實現簡單的3D效果;3D部分是由OpenGL|ES實現的,OpenGL|ES是Opengl的嵌入式版本,咱們先了解一下Android apk的幾種畫圖方式,而後再來來看一看這一整套的圖形體系是怎麼創建的。 java

首先畫圖都是針對提供給 應用程序的一塊內存填充 數據 , 沒去研究過一個Activity是否就對應着底層的一個surface,可是應該都是對這塊surface內存進行操做。所以說穿了就是咱們要麼調用2D 的API畫圖,要麼調用3D的API畫圖,而後將畫下來的圖保存在這個內存中,最後這個內存裏面的內容會被Opengl渲染之後變爲能夠在屏幕上的像素信息。

一 、Apk應用主要關心的仍是這些API的使用,在Android apk裏面畫圖有2種方式 [2D]: android

一、Simple Graphics in View

就是直接使用Android已經實現的一些畫圖操做,好比說images,shapes,colors,pre-defined animation等等,這些簡單的畫圖操做其實是由skia來提供的2D圖形操做。使用這些預約義好的操做,咱們能夠實現諸如貼一張背景圖啊,畫出簡單的形狀,實現一些簡單的動畫之類的操做。這裏的簡單能夠這麼理解,就是咱們在這裏沒有一筆一畫地構造出一個圖形出來,咱們只是把咱們的Graphic資源放入View體系中,由系統來將這些Graphic畫出來。 編程

舉個例子:咱們如今在Activity裏面綁定一個ImageView,咱們能夠設置這個ImageView的內容是咱們的picture,而後咱們可讓這個picture總體顏色上來點藍色調,而後咱們還能夠爲這個ImageView加入一個預約義動畫,這樣當系統要顯示這個View的時候就會顯示咱們的picture,而且會有動畫,並帶有一個藍色調,咱們並無本身去定義畫圖操做, 而是將這些內容放入View中,由系統來將這些內容畫出來。這種方式只能畫靜態或者極爲簡單的2D圖畫,對於實時性很強的動畫,高品質的遊戲都是無法實現的。 canvas

二、Canvas

首先咱們要明白這個Canvas是一個2D的概念,是在Skia中定義的。也就是說在這個方式下仍是說的畫2D圖形。咱們能夠把這個Canvas理解成系統提供給咱們的一塊內存區域(但實際上它只是一套畫圖的API,真正的內存是下面的Bitmap),並且它還提供了一整套對這個內存區域進行操做的方法,全部的這些操做都是畫圖API。也就是說在這種方式下咱們已經能一筆一劃或者使用Graphic來畫咱們所須要的東西了,要畫什麼要顯示什麼都由咱們本身控制。

這種方式根據環境還分爲兩種:一種就是使用普通View的canvas畫圖,還有一種就是使用專門的SurfaceView的canvas來畫圖。 兩種的主要是區別就是能夠在SurfaceView中定義一個專門的線程來完成畫圖工做,應用程序不須要等待View的刷圖,提升性能。前面一種適合處理 量比較小,幀率比較小的動畫,好比說象棋遊戲之類的;然後一種主要用在遊戲,高品質動畫方面的畫圖。下面是這兩種方式的典型sequence :

2.一、View canvas

(1)  定義一個本身的View :class your_view extends View{}  ;
(2)  重載View的onDraw方法:protected void onDraw(Canvas canvas){} ;
(3)  在onDraw方法中定義你本身的畫圖操做 ;
            
ps: 能夠定義本身的Btimap:
Bitmap b = Bitmap.createBitmap(100, 100, Bitmap.Config.ARGB_8888);
Canvas c = new Canvas(b);



可是必須將這個Bitmap放入View的canvas中,畫的圖才能顯示出來:public void drawBitmap (Bitmap bitmap, Matrix matrix, Paint paint)  ;

invalidate()這個函數被調用後,系統會在不久的未來從新調用onDraw()函數,這個時間很短但並非可預知的,我曾經在onDraw() 中寫了一個if語句,利用validate讓系統死循環調用onDraw刷兩張圖,幾乎上這兩張圖是刷在一塊兒的。

2.二、Surface View Canvas

(1)  定義一個本身的SurfaceView : class your_surfaceview extends SurfaceView  implements SurfaceHolder.Callback() {}  ;
(2)  實現SurfaceHolder.Callback的3個方法surfaceCreated()   surfaceChanged()   surfaceDestroyed() ;
(3)  定義本身的專一於畫圖的線程  : class your_thread extends Thread() {} ;
(4)  重載線程的run()函數  [通常在run中定義畫圖操做,在surfaceCreated中啓動這個線程]
(5)  畫圖的過程通常是這樣的:
SurfaceHolder surfaceHolder = getHolder() ;//取得holder,這個holder主要是對surface操做的適配,用戶不具有對surface操做的權限
surfaceHolder.addCallback(this) ;   //註冊實現好的callback
Canvas canvas = surfaceHolder.lockCanvas() ; //取得畫圖的Canvas
/*---------------------------------畫圖
**-------------------------------- 畫圖結束*/
surfaceHolder.unlockCanvasAndPost() ; //提交併顯示



全部前面3種方式的畫圖的一些例子在SDK上都有,寫得也比較清楚,我這裏就不說了,這裏寫一下我調這些代碼過程一些小經驗,應該主要涉及的是Activity這方面,應該之後都用獲得:

首先是關於Eclipse的 :
(1) ctrl + shift + O 能夠自動添加須要的依賴包,這功能挺實用的以爲,還有alt + /是語法補全 ;
(2) 代碼中右鍵比較實用的功能有不少,我記得的是F3找類的聲明,F4找類的繼承關係 ;
(3) 斷點調試比較方便的,在Eclipse的右上交能夠選擇閱讀代碼的方式,還能經如debug模式,我如今用到的兩個打log的方式:
             Log.e("class", "value : "+ classname) ;    //檢測class是否爲空指針
             Log.e(this.getClass().getName(), "notice message") ;

而後是關於Activity的 :
(1) 首先儘可能把UI的設計放在XML中實現,而不要放在代碼中實現,這樣方便設計,修改和移植 ;
(2) 全部使用到的component都必須在manifest中聲明,否則程序中找不到相應的conponet的時候會報錯 ;
(3) 通常每個Activity都對應於一個類,和一個相應的佈局文件xml ;
(4) 每個Activity只有使用setContentView()綁定內容後纔會顯示,並且你才能從這個內容(好比xml中)獲取到你須要的元素 ;
(5) res/drawable和res/raw中的元素的區別是drawable中的元素像素可能會被系統優化,而raw中的不會被優化 ;
(6) 當多個Activity都從res/drawable中得到同一個元素,若是其中一個修改它的屬性,全部其餘的Activity中這個元素的相應屬性都會改變 ;
(7) res/anim中保存的是動畫相關的xml ;

下面咱們總結如下2D畫圖用到的包 :
android.view                   //畫圖是在View中進行的
android.view.animation         //定義了一些簡單的動畫效果Tween Animation 和 Frame. Animation
android.graphics                  //定義了畫圖比較通用的API,好比canvas,paint,bitmap等
android.graphics.drawable //定義了相應的Drawable(可畫的東西),好比說BitmapDrawable,PictureDrawable等
android.graphics.drawable.shapes          //定義了一些shape



2、瞭解了2D,咱們再來看看3D的畫圖方式。

3D畫圖SDK上講得很簡單,只是提了一個通用的方式,就是繼承一個View,而後在這個View裏面得到 Opengl的句柄進行畫圖,道理應該來講是和2D同樣的,差異就是一個是使用2D的API畫圖,一個是使用3D的。不過由於3D openGl|ES具備一套自己的運行機制,好比渲染的過程控制等,所以Android爲咱們提供了一個專門的用在3D畫圖上的 GLSurfaceView。這個類被放在一個單獨的包android.opengl裏面,其中實現了其餘View所不具有的操做:

(1) 具備OpenGL|ES調用過程當中的錯誤跟蹤,檢查工具,這樣就方便了Opengl編程過程的debug ;
(2) 全部的畫圖是在一個專門的Surface上進行,這個Surface能夠最後被組合到android的View體系中 ;
(3) 它能夠根據EGL的配置來選擇本身的buffer類型,好比RGB565,depth=16 (這裏有點疑問,SurfaceHolder的類型是SURFACE_TYPE_GPU,內存就是從EGL分配過來的?)
(4) 全部畫圖的操做都經過render來提供,並且render對Opengl的調用是在一個單獨的線程中
(5) Opengl的運行週期與Activity的生命週期能夠協調

下面咱們再看看利用GLSurface畫3D圖形的一個典型的Sequence
(1)  選擇你的EGL配置(就是你畫圖須要的buffer類型) [optional] :
setEGLConfigChooser(boolean)
setEGLConfigChooser(EGLConfigChooser) 
setEGLConfigChooser(int, int, int, int, int, int)



(2) 選擇是否須要Debug信息 [optional] :
setDebugFlags(int)
setGLWrapper(GLSurfaceView.GLWrapper).



(3) 爲GLSurfaceView註冊一個畫圖的renderer : setRenderer(GLSurfaceView.Renderer)
(4) 設置reander mode,能夠爲持續渲染或者根據 命令來渲染,默認是continuous rendering [optional]: setRenderMode(int)

這裏有一個要注意的地方就是必須將Opengl的運行和Activity的生命週期綁定在一塊兒,也就是說Activity pause的時候,opengl的渲染也必須pause。另外GLSurfaceView還提供了一個很是實用的線程間交互的函數 queueEvent(Runnable),能夠用在主線程和render線程之間的交互,下面就是SDK提供的範例:
    
class MyGLSurfaceView extends GLSurfaceView {
     private MyRenderer mMyRenderer;
     public void start() {
         mMyRenderer = ...;
         setRenderer(mMyRenderer);
     }
     public boolean onKeyDown(int keyCode, KeyEvent event) {
         if (keyCode == KeyEvent.KEYCODE_DPAD_CENTER) {
             queueEvent(new Runnable() {
                 // This method will be called on the rendering
                 // thread:
                 public void run() {
                     mMyRenderer.handleDpadCenter();
                 }});
             return true;
         }
         return super.onKeyDown(keyCode, event);
      }
   }



GLSurfaceView是Android提供的一個很是值得 學習的類,它其實是一個如何在View中添加畫圖線程的例子,如何在 Java 中使用線程的例子,如何添加事件隊列的例子,一個使用SurfaceView畫圖的經典Sequence,一個如何定義Debug信息的例子,以爲把它看懂了能夠學到不少 知識 ,具體的源碼在:/framworks/base/opengl/java/android/opengl/GLSurfaceView.java 。

3D的內容基本到這裏基本講完了,剩下的主要是如何使用Opengl API的問題了,能夠看看API demo中簡單的立方體,複雜的能夠看看它那個魔方的實現。下面咱們總結一下3D畫圖須要用到的包:
Android.opengl     //主要定義了GLSurfaceView
javax.microedition.khronos.egl   //java層的egl接口
javax.microedition.khronos.opengles  //opengl API





3、瞭解了2D和3D基本的畫圖方法,咱們再回過頭來看看整個Android對Opengl和Skia的調用層次關係

3.一、首先來看2D,2D是主要使用的圖形引擎,畢竟3D受制於其太高的硬件要求在手機上使用仍是比較少,並且Skia也能部分實現相似於3D的效果, 所以能夠說SKia實現了Android平臺上絕大多數的圖形工做。下面咱們來看看從應用層到底層對skia的調用關係:
  
Android對skia的調用是一個比較 經典的調用過程,應用程序的幾個包是在SDK中提供的;JNI放在框架的JNI目錄下面的Graphic目錄;skia是做爲一個第三方組件放在external目錄下面。咱們能夠稍微瞭解一下skia的結構:      
   
這裏主要涉及到的3個庫:
  • libcorecg.so  包含/skia/src/core的部份內容,好比其中的Region,Rect是在SurfaceFlinger裏面計算但是區域的操做基本單位
  • libsgl.so        包含/skia/src/core|effects|images|ports|utils的部分和所有內容,這個實現了skia大部分的圖形效果,以及圖形格式的編解碼
  • libskiagl.so   包含/skia/src/gl裏面的內容,主要用來調用opengl實現部分效果
另外我看到/skia/src中有兩個目錄animator和view沒有寫入makefile的編譯路徑中,我以爲這兩個目錄是很重要的,不知道是如今Android還沒使用到,仍是用其餘的方式加載進去的。

要想在底層使用skia的接口來畫圖須要全面瞭解skia的一整套機制,實際上skia開源到如今還沒多久,在網上能找到的資料是也是很粗淺的,若是未來真須要在這方面下功夫確定是須要必定的工做量的。

3.二、Android對3D的調用曾經讓我迷惑了一段時間,由於在framewoks/base/core/jni這個目錄一直沒找到跟opengl相關的內容,後面去仔細看看opengl裏面的內容才知道Android把opengl的本地實現,JNI,java接口都放在/frameworks /base/opengl下面了,並且它內部還帶了一個工具能夠生成JNI代碼。

  

咱們來看看opengl的目錄結構:
  •      /include     包含egl和gles全部的頭文件
  •      /java/android/opengl   這個目錄包含的就是咱們3D畫圖要使用到的GLSurfaceView
  •      /java/com/google/android/gles_jni   這個目錄包含一些自動生成的文件
  •      /java/javax/microedition/khronos/egl   這就是應用層使用到的egl接口
  •      /java/javax/microedition/khronos/opengl  這就是應用層使用到的opengl接口
  •      /libagl  這個就是opengl主要的實現了
  •      /libs  這裏麪包含兩個庫的實現,一個是libegl.so 還有一個是libGL|ES_CM.so
  •      /tools  在個人理解這就是一個jni的生成工具
Opengl編程誰都知道是一個大工程,我以爲如今對3D的需求應該是很低的,不少效果咱們使用skia也能夠實現。因此我以爲未來的重點應該仍是放在skia上面。
相關文章
相關標籤/搜索