實戰試用 Android NDK 初見成效

來源:互聯網
上載者:User

作者:莊曉立 (liigo)

日期:2011.6.15

原創連結:http://blog.csdn.net/liigo/archive/2011/06/14/6544649.aspx

轉載請註明出處:http://blog.csdn.net/liigo

 

   我(liigo)以前的圍棋SGF棋譜解析器是使用C語言開發的,為了省事和加快移植,就不打算用Java語言重寫了,計劃通過 JNI (Java Native Interface) 在JAVA中調用C語言的代碼,自然的,Android NDK 就派上用場了。我此前只是對NDK稍有耳聞,從來沒用過。上周末抽了大概一天的時間實戰試用 Android NDK 初見成效,收穫不小。略加記錄,以備後用。

 

  一開始當然是先下載NDK(發現android.com已在牆內了),閱讀其官方使用文檔。文檔中提到NDK在Windows下需要依賴Cygwin(卻沒有指導性操作提示),去cygwin.com下載一setup.exe為線上下載安裝程式。由於NDK沒有相關說明,我在安裝cygwin問題上費了一些周折,經過網路搜尋,在安裝程式配置介面指定安裝devel類別,設定為Install(見),以便確保下載安裝得到其中的gcc和make程式。下載過程可能比較漫長,耐心等待吧。Devel裡可能有許多不是NDK所必需的內容(導致cygwin安裝目錄大幅膨脹),多了就多了吧,總比缺胳膊少腿好。安裝完成後,嘗試在cygwin控制台運行gcc和make命令,有恰當輸出反饋說明安裝成功了(可以滿足NDK需要),如果有SB的“command not found”之類錯誤提示(這裡借用一網友語氣),說明你還得考慮重新下載安裝cygwin。

 

  NDK和Cygwin都有了,接下來怎麼辦,我也糊塗了,NDK文檔似乎沒有說怎麼把兩者結合起來使用。因為發現NDK中的ndk-build是shell指令碼,需要linux環境才能運行,初步想到去cygwin控制台命令列執行 ndk-build 命令,結果進去一看,全是 /home, /usr, /dev 之類Linux目錄,我的NDK在windows系統中的D盤呀,怎麼訪問到?又是藉助於網路搜尋,知道了在cygwin內 /cygdrive 就可以訪問當前Windows系統下的磁碟各分區,如 /cygdrive/c 表示C盤,/cygdrive/d 表示D盤。OK,按照NDK文檔,執行命令:

cd $PROJECT<br /> $NDK/ndk-build

$PROJECT、$NDK 都是環境變數,以後再定義吧,先用絕對完整路徑吧,寫成這樣:

cd /cygdrive/d/eclipse/workspaces/GoSgfViewer<br /> /cygdrive/d/android/ndk/ndk-build

  每次都寫這麼長的路徑誰受得了,得了,定義成環境變數吧,再次搜尋網路,修改 <cygwin>/home/liigo/.bash_profile 這個檔案(其中liigo目錄應該是cygwin安裝時根據當前windows使用者產生的),在最後加入以下幾行:

NDK=/cygdrive/d/Android/ndk<br />export NDK<br />EWS=/cygdrive/d/eclipse/workspaces<br />export EWS<br />SGF=/cygdrive/d/eclipse/workspaces/GoSgfViewer<br />export SGF

以後編譯命令列就簡化了:

cd $SGF<br /> $NDK/ndk-build

這中間還出現一個插曲,我是用Windows系統內建的“寫字板”程式(wordpad.exe)編輯修改 .bash_profile 檔案的,結果進入cygwin控制台時它提示無法解析設定檔。分析發現寫字板儲存的文字檔行尾是"/r/n",而cygwin要求設定檔行尾是"/n",我一時沒有趁手達到此要求的文字編輯器,於是我寫了幾行易語言代碼(這裡下載源碼)解決了這個問題:

  還是各有各的辦法,你要是有EditPlus或者EmEditor或者UltraEdit,一個另存新檔就能搞定的事。我現在在想,當時為什麼沒想到用位元組集替換的方法,還可以少寫好幾行代碼,失誤失誤。

 

  接下來算是真正進入正題。不過對我(liigo)來說好像有點輕車熟路。前些年使用JNI的經曆,現在還有些印象,所以不覺得困難。先編寫含有native方法的Java Class,用java編譯器編譯之;然後用javah處理剛才編譯產生的class,產生用於實現對應native方法的C/C++標頭檔,包含相應的函數原型聲明;自己建立對應的.c/.cpp檔案,#include剛才產生的標頭檔,定義並實現其中的函數(代碼見下文)。我是在記事本中完成這些函數編碼的,這不是裝B,實在是暫時沒想到有其他更好的辦法,Eclipse IDE似乎也沒提供什麼支援,且不說NDK其實跟ADT甚至沒什麼直接關聯。好在這個過程是比較順利的,中間自然少不了隨時查看JNI官方文檔中的介面函數說明。

 

  代碼寫完之後,參照NDK文檔,在Eclipse項目目錄jni子目錄內建立一個Android.mk文字檔,其內容就從NDK文檔中抄下來稍作修改(修改了第三行和第四行):

LOCAL_PATH := $(call my-dir)<br />include $(CLEAR_VARS)<br />LOCAL_MODULE := liigo-sgf-parser<br />LOCAL_SRC_FILES := sgf.c com_liigo_go_SgfParser.cpp<br />include $(BUILD_SHARED_LIBRARY)

  然後進cygwin,執行編譯命令:

cd $SGF<br /> $NDK/ndk-build

 

  如果遇到錯誤則根據提示做相應修改,重新編譯。成功之後,NDK會在eclipse項目目錄內產生一些檔案(例如libs目錄內的動態庫.so)。此時回到Eclipse IDE,重新編譯Android項目,產生的.apk就是包含有調用C/C++代碼的應用軟體了。我在模擬器中部署此apk時貌似還出現了一些部署性錯誤,導致apk無法正常啟動,後來又莫名其妙好了,似乎需要重啟eclipse IDE並重新編譯整個項目?存疑。

 

  現在總結一下NDK的作用,它提供一些庫檔案和標頭檔,提供交叉編譯的手段(調用gcc,make),編譯產生必要的檔案到項目目錄(供ADT打包.apk)。至於在Java中調用C函數(通過native方法),和在C/C++中調用Java方法或讀寫對象成員(fields),主要是Java本身的JNI的功勞。

 

  在ndk-build編譯過程中,我主要遇到了以下兩種編譯錯誤,C/C++語法錯誤,導致了一大批編譯錯誤。第一個是調用JNI函數時,我參照其官方文檔,想當然的編寫代碼為類似如下:

GetMethodID(env, ...)
其實在C++(.cpp檔案)中正確的寫法應該是:

env->GetMethodID(...)
C語言(.c檔案)中正確的寫法應該是:

(*env)->GetMethodID(env, ...)
遇到的另一個錯誤是結構體(struct)的預聲明文法,我原來的寫法在VC編譯器中是可以編譯的:

typedef struct _tagSGFParseContext SGFParseContext;<br />//...<br />typedef struct _tagSGFParseContext<br />{<br />//...<br />}<br />SGFParseContext;
但是在gcc(g++)編譯器中不認可這種寫法,說是類型重定義,修改為以下代碼後編譯通過:

typedef struct _tagSGFParseContext SGFParseContext;<br />//...<br />struct _tagSGFParseContext<br />{<br />//...<br />};

  後來經過測試,各種功能正常調用成功,說明NDK正確的發揮了它的作用。下面貼一些代碼,僅供參考。

首先是 SgfParser.java:

package com.liigo.go;<br />import java.util.ArrayList;<br />import java.util.List;<br />public class SgfParser {<br />static {<br />System.loadLibrary("liigo-sgf-parser");<br />}<br />private int mContext; //used for native</p><p>public native void nativeInitSGFParseContext();<br />public native void nativeCleanupSGFParseContext();<br />public native int nativeParseSGFText(String sgfText);<br />public native int nativeParseSGFFile(String sgfFile);</p><p>public void parseSGFFile(String sgfFile) {<br />nativeInitSGFParseContext();<br />nativeParseSGFFile(sgfFile);<br />nativeCleanupSGFParseContext();<br />}</p><p>public void parseSGFText(String sgfText) {<br />nativeInitSGFParseContext();<br />nativeParseSGFText(sgfText);<br />nativeCleanupSGFParseContext();<br />}</p><p>interface Listener {<br />public void onSgfTree(String treeHeader, int treeIndex);<br />public void onSgfTreeEnd(int treeIndex);<br />public void onSgfNode(String nodeHeader);<br />public void onSgfNodeEnd();<br />public void onSgfProperty(String id, String value);<br />}<br />private List<SgfParser.Listener> mListeners = new ArrayList<SgfParser.Listener>();</p><p>public void addListener(SgfParser.Listener listener) {<br />mListeners.add(listener);<br />}<br />public void removeListener(SgfParser.Listener listener) {<br />mListeners.remove(listener);<br />}</p><p>private void onTree(String treeHeader, int treeIndex) {<br />for(Listener l : mListeners) {<br />l.onSgfTree(treeHeader, treeIndex);<br />}<br />}</p><p>private void onTreeEnd(int treeIndex) {<br />for(Listener l : mListeners) {<br />l.onSgfTreeEnd(treeIndex);<br />}<br />}</p><p>private void onNode(String nodeHeader) {<br />for(Listener l : mListeners) {<br />l.onSgfNode(nodeHeader);<br />}<br />}</p><p>private void onNodeEnd() {<br />for(Listener l : mListeners) {<br />l.onSgfNodeEnd();<br />}<br />}</p><p>private void onProperty(String id, String value) {<br />for(Listener l : mListeners) {<br />l.onSgfProperty(id, value);<br />}<br />}<br />}
然後是com_liigo_go_SgfParser.cpp:

#include "com_liigo_go_SgfParser.h"<br />#include "sgf.h"<br />#include <stdlib.h><br />#include <stdio.h><br />static jclass mThisClass = 0;<br />static jclass GetThisClass(JNIEnv* env)<br />{<br /> if(mThisClass == 0)<br /> {<br /> mThisClass = env->FindClass("com/liigo/go/SgfParser");<br /> }<br /> return mThisClass;<br />}<br />static jfieldID mContext_fieldID = 0;<br />static jfieldID GetContextFiledID(JNIEnv* env)<br />{<br /> if(mContext_fieldID == 0)<br /> mContext_fieldID = env->GetFieldID(GetThisClass(env), "mContext", "I");<br /> return mContext_fieldID;<br />}<br />static jmethodID GetContextMethodID(SGFParseContext* pContext, const char* name, const char* sig)<br />{<br />JNIEnv* env = (JNIEnv*) pContext->pUserData1;<br />return env->GetMethodID(GetThisClass(env), name, sig);<br />}<br />static void onProperty(SGFParseContext* pContext, const char* szID, const char* szValue)<br />{<br />jmethodID methodID = GetContextMethodID(pContext, "onProperty", "(Ljava/lang/String;Ljava/lang/String;)V");<br />JNIEnv* env = (JNIEnv*) pContext->pUserData1;<br />jobject obj = (jobject) pContext->pUserData2;<br />env->CallVoidMethod(obj, methodID, env->NewStringUTF(szID), env->NewStringUTF(szValue));<br />}<br />static void onNode(SGFParseContext* pContext, const char* szNodeHeader)<br />{<br />jmethodID methodID = GetContextMethodID(pContext, "onNode", "(Ljava/lang/String;)V");<br />JNIEnv* env = (JNIEnv*) pContext->pUserData1;<br />jobject obj = (jobject) pContext->pUserData2;<br />env->CallVoidMethod(obj, methodID, env->NewStringUTF(szNodeHeader));<br />}<br />static void onNodeEnd(SGFParseContext* pContext)<br />{<br />jmethodID methodID = GetContextMethodID(pContext, "onNodeEnd", "()V");<br />JNIEnv* env = (JNIEnv*) pContext->pUserData1;<br />jobject obj = (jobject) pContext->pUserData2;<br />env->CallVoidMethod(obj, methodID);<br />}<br />static void onTree(SGFParseContext* pContext, const char* szTreeHeader, int treeIndex)<br />{<br />jmethodID methodID = GetContextMethodID(pContext, "onTree", "(Ljava/lang/String;I)V");<br />JNIEnv* env = (JNIEnv*) pContext->pUserData1;<br />jobject obj = (jobject) pContext->pUserData2;<br />env->CallVoidMethod(obj, methodID, env->NewStringUTF(szTreeHeader), treeIndex);<br />}<br />static void onTreeEnd(SGFParseContext* pContext, int treeIndex)<br />{<br />jmethodID methodID = GetContextMethodID(pContext, "onTreeEnd", "(I)V");<br />JNIEnv* env = (JNIEnv*) pContext->pUserData1;<br />jobject obj = (jobject) pContext->pUserData2;<br />env->CallVoidMethod(obj, methodID, treeIndex);<br />}<br />/*<br /> * Class: com_liigo_go_SgfParser<br /> * Method: nativeInitSGFParseContext<br /> * Signature: ()V<br /> */<br />JNIEXPORT void JNICALL Java_com_liigo_go_SgfParser_nativeInitSGFParseContext<br /> (JNIEnv * env, jobject obj)<br />{<br /> SGFParseContext* pContext = (SGFParseContext*) malloc(sizeof(SGFParseContext));<br /> initSGFParseContext(pContext, onTree, onTreeEnd, onNode, onNodeEnd, onProperty, env, obj);<br /> //store pContext to mContext field<br /> env->SetIntField(obj, GetContextFiledID(env), (int)pContext);<br />}<br />static SGFParseContext* GetSGFParseContext(JNIEnv * env, jobject obj)<br />{<br /> return (SGFParseContext*) env->GetIntField(obj, GetContextFiledID(env));<br />}<br />/*<br /> * Class: com_liigo_go_SgfParser<br /> * Method: nativeCleanupSGFParseContext<br /> * Signature: ()V<br /> */<br />JNIEXPORT void JNICALL Java_com_liigo_go_SgfParser_nativeCleanupSGFParseContext<br /> (JNIEnv * env, jobject obj)<br />{<br /> SGFParseContext* pContext = GetSGFParseContext(env, obj);<br /> cleanupSGFParseContext(pContext);<br /> free(pContext);<br /> env->SetIntField(obj, GetContextFiledID(env), (int)NULL);<br />}<br />/*<br /> * Class: com_liigo_go_SgfParser<br /> * Method: nativeParseSGFText<br /> * Signature: (Ljava/lang/String;)I<br /> */<br />JNIEXPORT jint JNICALL Java_com_liigo_go_SgfParser_nativeParseSGFText<br /> (JNIEnv * env, jobject obj, jstring sgftext)<br />{<br /> SGFParseContext* pContext = GetSGFParseContext(env, obj);<br />const char* data = env->GetStringUTFChars(sgftext, NULL);<br />parseSGF(pContext, data, 0);<br />env->ReleaseStringUTFChars(sgftext, data);<br />}<br />/*<br /> * Class: com_liigo_go_SgfParser<br /> * Method: nativeParseSGFFile<br /> * Signature: (Ljava/lang/String;)I<br /> */<br />JNIEXPORT jint JNICALL Java_com_liigo_go_SgfParser_nativeParseSGFFile<br /> (JNIEnv * env, jobject obj, jstring sgffile)<br />{<br />const char* filename = env->GetStringUTFChars(sgffile, NULL);<br />int len = 0;<br />char* filedata = NULL;<br />FILE* pfile = fopen((const char*) filename, "r");<br />printf("/n/n--- test parse sgf file: %s ---/n", filename);<br />if(pfile)<br />{<br />fseek(pfile, 0, SEEK_END);<br />len = ftell(pfile);<br />//assert(len > 0);<br />fseek(pfile, 0, SEEK_SET);<br />filedata = (char*) malloc(len + 1);<br />//assert(filedata);<br />fread(filedata, 1, len, pfile);<br />filedata[len] = '/0'; //ensure end with '/0'</p><p>const char* sgfdata = filedata;</p><p>//ignore utf-8 BOM bytes<br />if(len>=3 && filedata[0]==(char)0xEF && filedata[1]==(char)0xBB && filedata[2]==(char)0xBF)<br />sgfdata += 3;<br /> SGFParseContext* pContext = GetSGFParseContext(env, obj);<br />parseSGF(pContext, (const char*)sgfdata, 0);<br />fclose(pfile);<br />pfile = NULL;<br />free(filedata);<br />}<br />else<br />{<br />printf("/n/n--- open sgf file error: %s ---/n/n", filename);<br />}</p><p>env->ReleaseStringUTFChars(sgffile, filename);<br />} 

相關文章

聯繫我們

該頁面正文內容均來源於網絡整理,並不代表阿里雲官方的觀點,該頁面所提到的產品和服務也與阿里云無關,如果該頁面內容對您造成了困擾,歡迎寫郵件給我們,收到郵件我們將在5個工作日內處理。

如果您發現本社區中有涉嫌抄襲的內容,歡迎發送郵件至: info-contact@alibabacloud.com 進行舉報並提供相關證據,工作人員會在 5 個工作天內聯絡您,一經查實,本站將立刻刪除涉嫌侵權內容。

A Free Trial That Lets You Build Big!

Start building with 50+ products and up to 12 months usage for Elastic Compute Service

  • Sales Support

    1 on 1 presale consultation

  • After-Sales Support

    24/7 Technical Support 6 Free Tickets per Quarter Faster Response

  • Alibaba Cloud offers highly flexible support services tailored to meet your exact needs.