1. 程式人生 > >一個簡單的例子帶你瞭解jni流程

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

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 <jni.h>
/* 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;
 */
<pre name="code" class="cpp">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_<NAME>模式的變數名衝突,必須包含這條指令。 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流程來說,是一個很好的瞭解。

---------文章寫自:HyHarden---------


相關推薦

一個簡單例子瞭解jni流程

1、前言 jni是java呼叫原生語言來進行開發的一座橋樑,原生語言一般是指c,c++語言,即jni機制可以讓java語言呼叫c,c++語言,也可以讓c,c++語言呼叫java語言。這樣的相互呼叫,互相結合,主要是出現在對效能要求較高的應用上。在android中,由於它

簡單易懂瞭解Java Agent

Java Agent這個技術,對於大多數同學來說都比較陌生,但是多多少少又接觸過,實際上,我們平時用的很多工具,都是基於Java Agent實現的,例如常見的熱部署JRebel,各種線上診斷工具(btrace, greys),還有阿里最近開源的arthas。 其實Java

一個小專案瞭解rest framework

這邊宣告一下,以下是參考官方文件來的。 新建專案 新建名為dimples的django專案 在其中建立一個名為astart的APP: 新建目錄 # 新建目錄 mkdir dimples cd dimples 新建虛擬環境 virtualenv env e

一個例子入門-Tableau

宣告:本文是學習W3Cschool教程整理所得,非原創,原文連結:W3Cschool 建立任何Tableau資料分析報告涉及三個基本步驟:        連線到資料來源: 它涉及定位資料並使用適當型別的連線來讀取資料;        選擇尺寸和度量: 這包括從源資料中選

簡單易懂瞭解二叉樹

前言 上一篇部落格為大家介紹了陣列與連結串列這兩種資料結構,雖然它們在某些方面有著自己的一些優點,但是也存在著一些自身的缺陷,本篇部落格為將為大家介紹一下資料結構---二叉樹,它在保留陣列和連結串列的優點的同時也改善了它們的缺點(當然它也有著自己的缺點,同時它的實現也比較複雜). 1. 陣列和連結串列的特點

簡單易懂瞭解紅黑樹

前言 上一篇部落格介紹了[二叉樹].二叉搜尋樹在樹是平衡的情況下搜尋、插入和刪除的效率都很好,但是如果二叉搜尋樹是不平衡的那麼它的效率就不那麼令人滿意了,而紅黑樹解決了二叉搜尋樹的這個問題,可以始終保持樹是平衡(大致平衡)的. 閱讀前須知: 如果您對二叉樹不太瞭解,請移步[二叉樹] 本文用到的評估紅黑樹效率

一個簡單例子理解Hashmap

前言 我知道大家都很熟悉hashmap,並且有事沒事都會new一個,但是hashmap的一些特性大家都是看了忘,忘了再記,今天這個例子可以幫助大家很好的記住。 場景 使用者提交一張試卷答案到服務端,post報文可精簡為 [{"question_i

一個簡單例子瞭解React-Redux

1. (單向)資料流 資料流是我們的行為與響應的抽象;使用資料流能幫我們明確了行為對應的響應,這和react的狀態可預測的思想是不謀而合的。 常見的資料流框架有Flux/reFlux/Redux。相比其它資料流框架,Redux輕量(壓縮後只有2K),而且在一

一個有趣的小例子,入門協程模組-asyncio

一個有趣的小例子,帶你入門協程模組-asyncio 上篇文章寫了關於yield from的用法,簡單的瞭解非同步模式,【https://www.cnblogs.com/c-x-a/p/10106031.html】這次讓我們通過一個有趣例子帶大家瞭解asyncio基本使用。 目標效果圖 基本

一個有趣的小例子,入門協程模塊-asyncio

其他 until res .html pychar port 打印 換行 異步 一個有趣的小例子,帶你入門協程模塊-asyncio 上篇文章寫了關於yield from的用法,簡單的了解異步模式,【https://www.cnblogs.com/c-x-a/p/10106

一個例子瞭解Java反射機制

本文來自:blog.csdn.net/ljphhj JAVA反射機制: 通俗地說,反射機制就是可以把一個類,類的成員(函式,屬性),當成一個物件來操作,希望讀者能理解,也就是說,類,類的成員,我們在執行的時候還可以動態地去操作他們. 理論的東東太多也沒用,下面我們看看實踐 Demo ~ Demo:

一個少女心滿滿的例子入門canvas

之前看到了一個很好看的canvas效果,然後拿來做我的部落格背景,不少童鞋留言說求教程,並且反應說太耗記憶體,於是前一段我就重寫了一遍,並且使用離屏渲染進行優化,效果還是挺顯著的。但是因為畢竟是canvas,需要一直進行重繪,所以還是比較耗記憶體的

一個例子瞭解MapReduce中shuffle的過程

Shuffle Shuffle基本概念   Shuffle的本義是洗牌、混洗,把一組有一定規則的資料儘量轉換成一組無規則的資料,越隨機越好。MapReduce中的Shuffle更像是洗牌的逆過程,把一組無規則的資料儘量轉換成一組具有一定

用socket.io實現websocket的一個簡單例子

soc .html www sock 在線 ket log html 簡單例子 http://biyeah.iteye.com/blog/1295196 socket.io的介紹 http://www.cnblogs.com/mazg/p/5467960.html

C語言多線程的一個簡單例子

color oid blog stdlib.h null bsp 等待 creat 多線程   多線程的一個簡單例子:    #include <stdio.h> #include <stdlib.h> #include <string.h&

一個經典例子徹徹底底理解java回調機制

pac hid find title tco tail comment 方法 rgs 轉帖請註明本文出自xiaanming的博客(http://blog.csdn.net/xiaanming/article/details/17483273),請尊重他人的辛勤勞動成果,謝謝

netsh interface portproxy的一個簡單例子

get 文檔 let pre interface 簡單例子 配置文件 exe CP netsh interface portproxy的微軟幫助文檔地址: https://technet.microsoft.com/zh-cn/library/cc776297(WS.10

使用java實現快速排序的一個簡單例子

fast val rgs 快速 實現 個數 static void sta public static void main(String[] args) { // 測試排序 Random r = new Random(); int arr[] = new

一圖抵千言:瞭解最直觀的神經網路架構視覺化

一張好的圖抵得上一千個等式。 神經網路是複雜、多維、非線性的陣列運算。如何在避免過於複雜或重複的情況下呈現深度學習模型架構的重要特徵呢?又該以何種方式清晰直觀、啟發性地呈現它們呢?(好看也是加分項!)無論研究還是教學專案對此都沒有固定標準。本文我們就來了解一下視覺化整個

什麼是雲連線?雲學院瞭解華為雲連線知識

雲連線為使用者提供一種能夠快速構建跨區域VPC之間以及雲上多VPC與雲下多資料中心之間的高速、優質、穩定的網路能力,幫助使用者打造一張具有企業級規模和通訊能力的全球雲上網路。通過購買一條雲連線,將使用者所需要實現互通的不同區域的網路例項載入到購買的雲連線例項中,這裡的網路例項可以是使用者自己購買的VPC例項或