cocos android分析
來自:https://xiebaochun.github.io/
cocos2d-x Android環境搭建
cocos2d-x環境搭建比較簡單,但是小問題還是不少,我盡量都涵蓋的全麵一些。cygwin、NDK(ADT):C++相關
如果之前沒有Android開發環境,還需要Android SDK,Eclipse
cocos2d-x源碼
我的環境為ndk r7,cygwin1.7,Android SDK為2.2和3.0.另外,我是通過真機調試,在模擬器上不行,估計還是我T410顯卡的問題.
安裝cygwin,在cygwin文件進行路徑設置
在cygwin\home\Administrator的.bash_profile中添加如下代碼
1: ANDROID_NDK_ROOT=/cygdrive/e/ADT/android-ndk-r7c
2: export ANDROID_NDK_ROOT
3: NDK_ROOT=/cygdrive/e/ADT/android-ndk-r7c
4: export NDK_ROOT
將libgnustl_static.a從NDK中的android-ndk-r7c\sources\cxx-stl\gnu-libstdc++\libs\armeabi拷貝至cocos2d-1.0.1-x-0.13.0-beta\HelloWorld\android\obj\local\armeabi,這個從解決方案上看應該是stl的引用不一致導致的問題,但編譯中會報錯“png.a can not find”,但是path路徑確實沒什麼問題,所以比較坑爹,總之這樣就搞定了,我也沒怎麼深究。
進入cocos2d-1.0.1-x-0.13.0-beta\HelloWorld\android目錄下修改如下內容到指定路徑
1: NDK_ROOT_LOCAL=/cygdrive/e/ADT/android-ndk-r7c
2: COCOS2DX_ROOT_LOCAL=/cygdrive/f/cocos2d-1.0.1-x-0.13.0-beta
cygwin中進入cocos2d-1.0.1-x-0.13.0-beta\HelloWorld\android目錄下,執行./build_native.sh,編譯C++,JNI接口,供Android Java使用,如果成功,在在libs中生成libhelloworld.so動態庫,我們都是為了它做了這麼多工作
在Eclipse中導入cocos2d-1.0.1-x-0.13.0-beta\HelloWorld\android工程,熟悉Android的一看就發現,其實這本身就是一個Java工程,我們剛才的操作隻是其中jni的部分,供Java下麵的調用實現而已
Eclipse中執行Build Project,生成R.java
Run
Make
ndk的Make是在GNU的Make的基礎上的一種封裝,下麵我們來分析一下./build_native.sh都做了哪些操作。簡單說主要是資源拷貝和代碼編譯。
資源拷貝在我的cygwin裏麵發現有問題,拷貝後的文件是錯誤的,且不能刪除我沒有深究,自己手動拷貝了一下。和shell一致,很容易理解,不再深究。
ndk-build編譯HelloWorld工程,編譯jni文件夾下麵的Android.mk,和makefile基本相似,指定需要編譯的文件,include路徑,依賴工程cocos2dx_static,進行編譯,例如HelloWorld的makefile大致如下:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
LOCAL_MODULE := helloworld_shared
LOCAL_MODULE_FILENAME := libhelloworld
LOCAL_SRC_FILES := helloworld/main.cpp \
../../Classes/AppDelegate.cpp \
../../Classes/HelloWorldScene.cpp
LOCAL_C_INCLUDES := $(LOCAL_PATH)/../../Classes
LOCAL_WHOLE_STATIC_LIBRARIES := cocos2dx_static
include $(BUILD_SHARED_LIBRARY)
$(call import-module,cocos2dx)
LOCAL_PATH := $(call my-dir):指定當前路徑為為LOCAL_PATH
include $(CLEAR_VARS):清空除LOCAL_PATH之外的其他環境變量的幹擾
LOCAL_MODULE&LOCAL_MODULE_FILENAME:模塊名稱&生成庫名稱
LOCAL_SRC_FILES:編譯的C++ Source
LOCAL_WHOLE_STATIC_LIBRARIES:依賴的靜態庫
BUILD_SHARED_LIBRARY:生成的為共享庫。因為Android的動態庫都為JNI所用,所以稱為共享庫,而靜態庫隻為其他C++庫所用。
$(call import-module,<name>):通過NDK_MODULE_PATH環境變量引用的模塊<name>的目錄列表,並且將其自動包含到Android.mk中
這樣,一個編譯環境的include,library,target基本指定,則編譯出最終的目標文件,和makefile思路上沒什麼區別,另外這裏需要編譯出cocos2dx.a,靜態庫,是通過cocos2d文件夾中的make編譯而成,這個腳本則要複雜一些,不過思想並無不同。更多NDK Make可以參考:《Android Make》
JNI交互
C++接口封裝完畢後,我們就開始看一下Java代碼,了解一下最終實現的流程和效果,Java代碼如下:
Java層的框架也很簡單,這裏並沒有多Accelerometer和Music、Sound等進行分析,隻是對涉及到顯示相關的進行分析。Java層麵流程如下:
如上,如果熟悉Android界麵開發,可以從基類了解到Java層麵是通過Activity、GLSuffaceView來進行的顯示。這裏不詳細介紹,如果有興趣,可以看一下《剖析遊戲開發用view還是surfaceView》,View類似傳統的二維靜態界麵,數據驅動顯示,而SurfaceView則類似三維機製,實時渲染。因為Cocos2d是OpenGL的,這也好解釋。
對於整個框架其實要說的也很多,不過我對Java還不太了解,所以有些東西看的不一定透,也難免有一些問題。
Renderer
Renderer類負責每一幀的渲染驅動,調用步驟如圖裏麵的1和2,在2中調用jni裏麵的nativeRender實現一幀的渲染,而GLSurfaceView則負責UI交互的監聽。
這種機製的好處是在Java中Renderer渲染器是獨立線程調用,因此和UI之間沒有交互性,這樣既保證了用戶體驗(用戶的事件通過GLSurfaceView監聽,最終通過Renderer傳遞至C++層麵來響應),也保證了渲染過程的抗幹擾,依舊通過C++層麵進行渲染。,整個顯示過程用到的jni封裝主要如下:
private static native void nativeTouchesBegin(int id, float x, float y); private static native void nativeTouchesEnd(int id, float x, float y); private static native void nativeTouchesMove(int[] id, float[] x, float[] y); private static native void nativeTouchesCancel(int[] id, float[] x, float[] y); private static native boolean nativeKeyDown(int keyCode); private static native void nativeRender(); private static native void nativeInit(int w, int h); private static native void nativeOnPause(); private static native void nativeOnResume();
jni的封裝主要有兩部分,一個是cocos2d自己的JNI封裝,這部分封裝主要是為了在Java中調用cocos2d的jni接口,一個是HelloWorld中自己的jni接口封裝。這一塊本來是我比較感興趣的地方,因為jni封裝還是挺繁瑣的一件事情,最後發現cocos2d在本質上也沒有什麼區別,麻煩的還是得封裝。第二點,cocos2d主要是遊戲引擎,所以基本所有功能都是由C++層麵來實現,一幀的渲染,事件的處理,而Java層主要負責邏輯處理,最終通過jni調用C++接口來實現。第三點來說,cocos2d本身封裝的還是很簡潔的,這點我覺得做的還是很優雅的,在設計這塊,是以Java的邏輯為依據來進行劃分,我覺得這個很可取,雖然cocos2d是C++做起來的,但是並沒有為了保證各個平台的一致性而強迫接口的一致,而是在jni層按照SDK在具體平台的應用特點來進行封裝,這樣減低了實現難度,提高了代碼的易用度,犧牲就是應用平台接口的局部不一致性。jni層麵主要是事件傳遞和窗口渲染部分的接口封裝,針對遊戲開發者而言,最核心的部分都可以在Windows平台下完成,然後在Android部分完成特有事件的傳遞,渲染部分直接采用cocos2d給出的標準範例實現即可,大大簡化了開發者自己封裝jni的工作。
窗口綁定
窗口綁定我理解的並不太透徹,首先,我認為CCEGLView_Android隻是一個虛的窗口,並沒有實質功能,隻是為了便於架構理解。
void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeInit(JNIEnv* env, jobject thiz, jint w, jint h) { if (!cocos2d::CCDirector::sharedDirector()->getOpenGLView()) { cocos2d::CCEGLView *view = &cocos2d::CCEGLView::sharedOpenGLView(); view->setFrameWidthAndHeight(w, h); // if you want to run in WVGA with HVGA resource, set it // view->create(480, 320); Please change it to (320, 480) if you're in portrait mode. cocos2d::CCDirector::sharedDirector()->setOpenGLView(view); AppDelegate *pAppDelegate = new AppDelegate(); cocos2d::CCApplication::sharedApplication().run(); } } void Java_org_cocos2dx_lib_Cocos2dxRenderer_nativeRender(JNIEnv* env) { cocos2d::CCDirector::sharedDirector()->mainLoop(); }
函數一是Java層調用onSurfaceCreated時調用函數,用來獲取GLView窗口,用來下一步的渲染,而這個View窗口並沒有類似Windows下的handle綁定,而接下來函數二是Java中onDrawFrame渲染每一幀時進行調用,最終調用底層的Director渲染,完成一幀繪製(詳細內容可參考《cocos2d-x之HelloWorld範例分析(一)》)。
怎麼來理解這種窗口綁定方式,保證我現在調用的gl函數,就能夠繪製在窗口呢,通篇沒有一個類似的handle從Java傳遞給JNI,通篇C++層麵的View也隻是一個隻有Width和Height屬性的結構體,所以我理解的是GLSurfaceView.Renderer默認在自己的線程中進行了封裝,已經自己完成了和OpenGL的綁定。這個我覺得應該是靠譜的吧,而且自己來實時的每一幀渲染,下麵的就不管裏,你自己願意調Java的接口也行,自己調gl的渲染也可以。這樣也挺好的,都不用我顧慮這個事情了,隻要給我高度寬度知道位置信息,我直接渲染。
文字
其他圖形圖像的繪製,都是和係統無關的。整個的渲染過程,也是跨平台的,一個平台的整合,主要是環境搭建、不同語言之間的消息傳遞、View的映射這些,前麵也都闡述了,隻是文字有一定的特殊,在Windows下使用CDC,在Linux是Freetype,在Android下如何實現?我覺得cocos2d實現思路也是不錯的:C++通過JNI在Java層繪製,生成一張BitMap給C++,然後貼圖完成。這個優點是簡單,缺點就是如果文字太多的話,效率損失還是有的,其實我覺得如果有機會,還是用Freetype來畫應該也可以嚐試一下。
當然,也新學了一招,C++調用Java的方式,在jni裏麵也提供了,嗬嗬,代碼在下麵貼一下:
bool getBitmapFromJava(const char *text, int nWidth, int nHeight, CCImage::ETextAlign eAlignMask, const char * pFontName, float fontSize) { JniMethodInfo methodInfo; if (! JniHelper::getStaticMethodInfo(methodInfo, "org/cocos2dx/lib/Cocos2dxBitmap", "createTextBitmap", "(Ljava/lang/String;Ljava/lang/String;IIII)V")) { CCLOG("%s %d: error to get methodInfo", __FILE__, __LINE__); return false; } jstring jstrText = methodInfo.env->NewStringUTF(text); jstring jstrFont = methodInfo.env->NewStringUTF(pFontName); methodInfo.env->CallStaticVoidMethod(methodInfo.classID, methodInfo.methodID, jstrText, jstrFont, (int)fontSize, eAlignMask, nWidth, nHeight); methodInfo.env->DeleteLocalRef(jstrText); methodInfo.env->DeleteLocalRef(jstrFont); methodInfo.env->DeleteLocalRef(methodInfo.classID); return true; } static bool getStaticMethodInfo_(cocos2d::JniMethodInfo &methodinfo, const char *className, const char *methodName, const char *paramCode) { jmethodID methodID = 0; JNIEnv *pEnv = 0; if (! getEnv(&pEnv)) { break; } jclass classID = getClassID_(className, pEnv); methodID = pEnv->GetStaticMethodID(classID, methodName, paramCode);
參照裏麵的注釋,C++驅動Java實現繪製,Java完成繪製後,調用Java_org_cocos2dx_lib_Cocos2dxBitmap_nativeInitBitmapDC接口,實現內存的拷貝,而s_BmpDC中的m_pData用來保存,進行下一步的紋理貼圖,完成整改流程的傳遞.
總結
介紹完畢,整個過程中,cocos2d使用的技術並不神秘,主要是一個熟悉的過程.最值得稱讚的是JNI封裝的比較使用,本身做遊戲開發,基本所有功能都會在C++中封閉實現,隻需要提供一個規範的Java外殼就可以,既跨平台有高效.另外,就是cocos2d對各個平台的語言取舍,哪些用Java方便,哪些用C++ 保持平台一致,都做的還是很合理的.
最後更新:2017-04-03 07:56:58
上一篇:
swift 學習這十八:宏()
下一篇:
開源軟件發展的保障:分發協議
本該遭拒的十大科技專利:蘋果滑動解鎖上榜
MaxCompute(原ODPS)性能優化之合並小文件
互聯網企業安全高級指南3.3 如何推動安全策略
Sql查詢原理與Select執行順序(詳細)
Centos7安裝配置ELK(Elasticsearch + Logstash + Kibana)分析Nginx日誌簡單單點配置
HDU2178猜數字
How can I detect the Android runtime (Dalvik or ART)?
更新數據庫中某一列的值,讓其在原數的基礎上加N
ThreadLocal的解析
【視覺目標跟蹤最高峰】VOT Challenge 2017 亞軍北郵團隊技術分享(附代碼)