接上一篇,搭建好基於Android Studio的環境以後,編寫native代碼相對來講也比較簡單了。在Android上編寫Native代碼和在Linux編寫C/C++代碼仍是有區別,Native代碼通常須要與JVM交互數據,須要遵循必定的規範,本文來介紹一下基本的JNI代碼寫法。java
咱們仍是從實例出發,配置好Android Studio工程以後,咱們須要建立jni目錄和在jni目下建立c/c++文件和相應的頭文件,建立方式見下圖。android
在實例工程中咱們建立了NdkSample.cpp 和 NdkSample.h,源碼見下面:c++
#include "NdkSample.h" JNIEXPORT jstring JNICALL Java_com_zyp_ndktest_MainActivity_sayHello (JNIEnv *env, jclass cls, jstring j_str) { const char *c_str = nullptr; char buff[128] = {0}; jboolean isCopy; c_str = env->GetStringUTFChars(j_str, &isCopy); printf("isCopy:%d\n",isCopy); if(c_str == NULL) { return NULL; } printf("C_str: %s \n", c_str); sprintf(buff, "hello %s", c_str); env->ReleaseStringUTFChars(j_str, c_str); return env->NewStringUTF(buff); }
#ifndef NDKTEST_NDKSAMPLE_H #define NDKTEST_NDKSAMPLE_H #include "jni.h" #include <stdio.h> #include <string.h> extern "C" { JNIEXPORT jstring JNICALL Java_com_zyp_ndktest_MainActivity_sayHello(JNIEnv *env, jclass type, jstring filename); } #endif //NDKTEST_NDKSAMPLE_H
如今來簡單介紹一下,首先是NdkSample.h文件,剛剛建立的時候只有相應的預處理命令,咱們在頭文件預處理命令之間加上 jni.h ,stdio.h ,string.h 後兩個非必要。將咱們要在java層調用的接口聲明出來,放在extern "c"{} 中(告訴編譯器按照C標準進行編譯)。第一次接觸jni的同窗看到那麼複雜的函數命名和奇怪的JNIEXPORT ,JNICALL,JNIEnv之類的估計有點不習慣,本文就不詳細介紹它們的意思,其實你跟蹤源碼它們就是幾個宏(JNIEnv是一個結構體保存當前環境的上下文),其它的jstring,jclass之類的很好理解就是在Native環境中對JVM中java對應結構的一種表示方式。app
函數命令方式是包名加activity名加函數名,表如咱們在java層中的包名是java.com.zyp.ndktest,在MainActivity中調用sayHello函數,則jni層函數命名就要寫成上面的方式。ide
接下來看NdkSample.cpp文件中函數的定義。ni層的函數還須要多兩個參數,一個是JNIEnv * ,一個是jclass。咱們在java層調用的時候就只用傳遞前兩個參數以外的參數。例子中咱們想從java層傳遞一個String類型的參數到jni層,jni層從JVM中取數據的時候取到的倒是jstring類型,在jni層咱們不能直接使用須要轉換。這裏咱們經過env->GetStringUTFChars(j_str, &isCopy)函數來完成,將j_str所在的地址轉換並賦值給const char *類型的指針,以後咱們就能夠經過該指針訪問那塊內存了。注意這裏是const char * 表示該指針指向的內存區域的內容是不能夠改變的,java中的String 也是自帶final屬性的。GetStringUTFChars()其實是將JVM內部的Unicode轉化成爲了C/C++認識的UTF-8的格式的字符串,注意這個函數內部發生了內存分配,至關因而拷貝了一份Unicode而後進行轉化,因此後面須要ReleaseStringUTFChars()來釋放內存。函數
最後該函數返回一個新的構建好的jstring類型給java層,爲了將C/C++層的UTF-8字符串轉換爲JVM中的Unicode字符串,須要調用另一個函數NewStringUTF()來完成轉換。gradle
咱們注意到JVM中的內容jni中不能直接操做須要進行轉換,jni中的內同也要進行轉換,所以也有大量的相關jni接口存在,後面文章中會挑選一些來說解。spa
此外,函數中JNIEnv *env這個參數須要說一下,在C和C++中使用方式是不同的,不要搞混了。在C中,看到JNIEnv 咱們實質是取得了JNINativeInterface* (JNIEnv指針的指針),咱們得使用**env獲取結構體,從而才能使用結構體裏面的方法。在C++中,看到JNIEnv咱們實質是取得了JNIEnv*(JNIEnv結構體的指針),咱們能夠直接使用env->使用結構體裏面的方法。注意咱們調用GetStringUTFChars()的方式,可是注意和Java_com_zyp_ndktest_MainActivity_sayHello()進行區別。指針
如今來看看咱們在java層中如何調用jni層的接口,看下面代碼:code
package com.zyp.ndktest; import android.support.v7.app.AppCompatActivity; import android.os.Bundle; import android.util.Log; public class MainActivity extends AppCompatActivity { @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); String ret = sayHello("zhuzhu"); Log.i("JNI_INFO", ret); } static { System.loadLibrary("NdkSample"); } public native static String sayHello(String str); }
咱們首先要經過System.loadLibrary()加載jni代碼編譯後生成的.so,可是這個庫的名字怎麼來的呢,注意回過頭去看上一篇中gradle ndk{}中的內容,咱們是在那裏進行的命名的;而後還要聲明native static 類型的該函數。而後直接調用就行了。運行結果見下圖。
但願經過這篇文章可以讓你們入門JNI開發^_^。