Android教程網
  1. 首頁
  2. Android 技術
  3. Android 手機
  4. Android 系統教程
  5. Android 游戲
 Android教程網 >> Android技術 >> 關於Android編程 >> 一個簡單的例子帶你了解jni流程

一個簡單的例子帶你了解jni流程

編輯:關於Android編程

1、前言

jni是java調用原生語言來進行開發的一座橋梁,原生語言一般是指c,c++語言,即jni機制可以讓java語言調用c,c++語言,也可以讓c,c++語言調用java語言。這樣的相互調用,互相結合,主要是出現在對性能要求較高的應用上。在android中,由於它的開發語言也是java,所以也可以利用原生語言進行開發,對jni的了解和使用有助於我們在做應用的時候,對於時間性能要求較高的代碼段,可以用原生語言來開發,然後java通過jni機制來調用它,就可以達到很好的效果。   總的來說,jni是一種機制,一種可以然java和原生語言互相調用的機制。   學習jni開發需要有c++或者c基礎。  


2、基本環境要求

利用jni機制開發,對於開發環境是有特別要求的,因為c,c++語言的開發也需要環境的支持。   單純的android開發:jdk,sdk,eclipse,adt。   如果要在eclipse上進行jni開發,那麼需要:ndk,cygwin,ant。   本文接下來的例子都是使用eclipse的方法進行開發,使用android studio的朋友,由於編譯器自身的集成,利用jni機制進行開發會更加方便,但是為了更深入的了解jni機制,本文使用的是eclipse。     對於android的基本開發環境的安裝就不提了,jni需要的開發環境這裡就提一下,方便完全沒基礎的朋友安裝環境。  

2.1 ant的安裝

ant是一個命令行構建工具,它主要是驅動目標進行任務的進行。在jni中一般是用來驅動命令。它的安裝可以去ant下載官網下載。一般下載zip壓縮包即可。然後解壓安裝到我們指定的目錄(自由的目錄位置)。最關鍵的一步是添加環境變量。   假如你的ant根目錄是E:\apache-ant-1.9.7。那麼你只需要復制這個文件夾下的bin路徑,添加到高級環境變量的path變量中。注意與path變量本身存在的值必須添加分號進行分割。   比如:   \   隨後我們用命令行工具輸入ant -version命令。如果安裝成功就會輸出ant的版本號。    


2.2cygwin的安裝

  由於android原生語言開發環境包(NDK)是基於類unix系統運行的,它包含了許多shell腳本,而它們又是不可以直接在window系統運行的,因此需要安裝cygwin模擬類unix系統的環境。cygwin的下載地址   由於筆者的安裝環境以及完成,所以並不能圖文並茂德截圖講解。為了方便大家的安裝,筆者決定直接使用參考資料的截圖。   \     \     \     \     \   \     讀者只需要按照筆者的截圖順序去做就可以安裝成功了,cygwin一定要安裝成功,否則會導致ndk運行的失敗,這也是為什麼筆者花這麼多圖的原因。    

2.3 NDK的安裝

NDK即android原生語言開發環境包。下載地址:NDK的下載   我們下載好壓縮包解壓到我們指定的目錄,然後需要添加環境變量,比如筆者的NDK安裝在:E:\android-ndk-r11b。那麼只需要在path變量裡面添加此路徑即可,記得用分號分隔。   \     隨後我們在命令行窗口輸入ndk-build命令。如果有以下輸出說明安裝成功。   \      

2.4 在eclipse裡面配置jni開發環境

  需要指定NDK的目錄路徑。筆者的如下:   \    

3、實踐過程進行總結

實踐才是檢驗真理的唯一標准,通過上面的環境配置,我們就可以搭建一個可以開發jni的android環境了,接下來就一步一步引導大家去開發一個jni實例。     首先新建一個空白的android項目。   我們新建一個類,專門處理native函數。筆者命名為CppUtils。內容如下:  
public class CppUtils {

	static {
		System.loadLibrary("cppUtils");
	}

	/**
	 * 從CPP獲取字符串
	 * 
	 * @return
	 */
	public native String getStringFromCPP();

	

}

這裡是簡單的從c++原生代碼中獲取字符串。


3.1 System.loadLibrary

java在java.lang.System包提供了兩個靜態方法用於加載共享庫(一種含原生語言實現的可供android程序調用的庫),分別是load,loadLibrary兩個方法,由於我們在程序啟動的時候就需要加載共享庫,因此放在靜態代碼塊中加載。這兩個方法的參數是共享庫名稱。注意共享庫為了跨平台使用,它的文件名稱會包含一些前綴,而共享庫文件的後綴是so。比如我們加載cppUtils共享庫,其實他的全名是:libcppUtils.so。


3.2 native標簽

native標簽用於告訴java編輯器,它的方法是由原生語言實現的,因此不需要去實現它。native方法用分號結束。即如果你希望一個方法用原生語言實現,那麼你就給它聲明為native方法。  


3.3 生成原生語言頭文件

由於原生語言頭文件需要根據字節碼文件來進行分析,所以,在生成頭文件之前,我們必須對項目進行build。之後打開我們項目的bin/classes的文件夾,筆者的文件夾如下圖:   \   接著我們就要針對CppUtils.class進行分析生成頭文件,在我們對編寫原生語言頭文件的時候,最好借助工具生成,而不是手寫,這樣出錯的概率才會更低,否則很容易發生jni橋無法將java函數與原生方法聯系起來的錯誤。生成頭文件的方法,就是使用命令行工具。比如筆者這裡就是,先進入自己項目要分析的java文件的目錄下,然後生成頭文件。生成頭文件的命令如下: javah -classpath bin/classes com.example.jnibolg.CppUtils   完整的操作過程看下圖:   \     然後回到eclipse中,刷新下我們的項目,我們會發現多了一個以h結尾的文件,這個就是機器生成的頭文件。   \     關於原聲函數的實現以及祥光頭文件,我們需要放在jni文件夾中,因此,我們接下來需要在項目中建立jni文件夾,並將相關文件放進去。   \     這樣我們的原生文件就生成了。    

3.4 分析頭文件

接下來我們需要分析頭文件的內容,有助於幫助我們了解整個實現過程。首先我們看頭文件的代碼:  
/* DO NOT EDIT THIS FILE - it is machine generated */
#include 
/* Header for class com_example_jnibolg_CppUtils */

#ifndef _Included_com_example_jnibolg_CppUtils
#define _Included_com_example_jnibolg_CppUtils
#ifdef __cplusplus
extern "C" {
#endif
/*
 * Class:     com_example_jnibolg_CppUtils
 * Method:    getStringFromCPP
 * Signature: ()Ljava/lang/String;
 */
JNIEXPORT jstring JNICALL Java_com_example_jnibolg_CppUtils_getStringFromCPP
  (JNIEnv *, jobject);

#ifdef __cplusplus}#endif#endif

 
首先我們可以看到jni.h頭文件被包含了,這個頭文件包含了jni機制為了實現從java對象到原生語言的映射的規則。因此,我們一切java調用原生函數,或者原聲函數調用java,都必須通過它來實現。
  其次我們關注這個函數聲明:    
JNIEXPORT jstring JNICALL Java_com_example_jnibolg_CppUtils_getStringFromCPP
  (JNIEnv *, jobject);

這個函數聲明說明了,它實現的是jnibolg包下的CppUtils類的getStringFromCPP方法,返回的是jstring類型,這是一個jni類型,映射到java的string類型。這裡不詳細解析jni類型映射,以免變得復雜,主要以實現一個例子了解整個流程為主。   JNIEnv 是一個指針,指向jni對象。通過它,就可以調用jni.h頭文件包含的所有函數。即,它就是一個指向jni對象的指針。   jobject表示當前函數所實現方法所屬的java對象,這裡指的是CppUtils的一個實例。     有了頭文件,那麼接下來,我們需要用到NDK了,這就需要獲取NDK的支持。    

3.5 獲取NDK的支持

右擊我們的項目,找到android tools,點擊add native support,這樣就可以獲取NDK開發包的支持了。點擊之後,會需要你填寫一個名稱,這個名稱將會用作共享庫的名稱,同時這個名字也是我們CppUtils中System.loadLibrary所包含的文件名稱。   \   確定之後,我們看jni文件夾,會生成NDK支持的文件。   \     其中cppUtils.cpp是我們要進行實現的C++文件。Android.mk是NDK的makefile文件,通過他,可以將原生語言的實現生成為共享庫。我們之前安裝的cygwin目的就是支持NDK的系統構建。我們打開我們NDK的目錄,筆者的如下:   \ 可以發現NDK有很多makefile文件,這些文件都是用於幫助構成共享庫的。    

3.6 分析mk文件的內容

  我們打開Android.mk文件,內容如下:  
LOCAL_PATH := $(call my-dir)

include $(CLEAR_VARS)

LOCAL_MODULE    := cppUtils
LOCAL_SRC_FILES := cppUtils.cpp

include $(BUILD_SHARED_LIBRARY)

  LOCAL_PATH,這是一個用於定位源文件的宏,在Android.mk文件中,它必須是第一個變量。 include $(CLEAR_VARS),作用是清除命名沖突,因為Android構建系統在單次執行中會構建多個文件和模塊,為了避免LOCAL_模式的變量名沖突,必須包含這條指令。 LOCAL_MODULE,指的是生成的共享庫的名稱,為了適應不同的架構,生成的共享庫會含有lib前綴。 LOCAL_SRC_FILES,指的是生成共享庫的源文件,多個源文件之間用空格隔開。 include $(BUILD_SHARED_LIBRARY)指令表示生成一個共享庫。   關於共享庫的生成,可以有更復雜的組織,比如多個共享庫依賴某個靜態庫.....這裡不詳細講解,只為了讓大家理解整個流程。我們要知道的就是,基本的生成流程都是按照上面這幾條指令順序來的。


3.7實現原生代碼

  我們打開cppUtils.cpp文件,會發現是空的。如果包含了jni.h頭文件指令,我們把他刪掉,因為我們即將要實現的頭文件已經包含了jni,h頭文件,所以我們的源文件無需再次包含。   接著我們將頭文件需要實現的函數聲明復制過來,為了避免出錯,強烈建議復制過來。然後修改成下面這個樣子。  
#include "com_example_jniblog_CppUtils.h"

JNIEXPORT jstring JNICALL Java_com_example_jniblog_CppUtils_getStringFromCpp
  (JNIEnv * env, jobject jthis)
{
   return env->NewStringUTF("來自C++");
}

 

3.8調用native函數

做完了上面這些,就可以函數調用了。調用和正常的java調用沒有差別的。比如這裡就是:  
private TextView text;
	CppUtils cppUtils = new CppUtils();

	@Override
	protected void onCreate(Bundle savedInstanceState) {
		super.onCreate(savedInstanceState);
		setContentView(R.layout.activity_main);
		text = (TextView) findViewById(R.id.text);
		text.setText("從C++獲取字符串:" + cppUtils.getStringFromCpp());
	}

     

3.9 總結

  從整個流程下來,我們可以清晰的知道每一步要怎麼做為什麼這麼做這麼做的意義,雖然並沒有深入講解,但是對於整個jni流程來說,是一個很好的了解。
  1. 上一頁:
  2. 下一頁:
熱門文章
閱讀排行版
Copyright © Android教程網 All Rights Reserved