最近整理了之前關於jni的代碼,這裏梳理下,供之後參考。java
JNI是Java Native Interface的縮寫,它提供了若干的接口實現了Java和其餘語言的通訊(主要是c、c++)。從Java1.1開始,JNI標準成爲java平臺的一部分,它容許Java代碼和其餘語言寫的代碼進行交互。jni是Android中java和c++之間鏈接的橋樑,jni是jvm提供的一種與native方法對接的方式。android
一旦使用JNI,JAVA程序就喪失了JAVA平臺的兩個優勢:c++
一、程序再也不跨平臺。要想跨平臺,必須在不一樣的系統環境下從新編譯本地語言部分。算法
二、程序再也不是絕對安全的,本地代碼的不當使用可能致使整個程序崩潰。一個通用規則是,你應該讓本地方法集中在少數幾個類當中。這樣就下降了JAVA和C之間的耦合性。數據庫
當你開始着手準備一個使用JNI的項目時,請確認是否還有替代方案。應用程序使用JNI會帶來一些反作用。下面給出幾個方案,能夠避免使用JNI的時候,達到與本地代碼進行交互的效果:安全
一、JAVA程序和本地程序使用TCP/IP或者IPC進行交互。app
二、當用JAVA程序鏈接本地數據庫時,使用JDBC提供的API。jvm
三、JAVA程序可使用分佈式對象技術,如JAVA IDL API。分佈式
這些方案的共同點是,JAVA和C處於不一樣的線程,或者不一樣的機器上。這樣,當本地程序崩潰時,不會影響到JAVA程序。ide
下面這些場合中,同一進程內JNI的使用沒法避免:
一、程序當中用到了JAVA API不提供的特殊系統環境纔會有的特徵。而跨進程操做又不現實。
二、你可能想訪問一些己有的本地庫,但又不想付出跨進程調用時的代價,如效率,內存,數據傳遞方面。
三、JAVA程序當中的一部分代碼對效率要求很是高,如算法計算,圖形渲染等。
總之,只有當你必須在同一進程中調用本地代碼時,再使用JNI。
註冊方法有兩種:靜態註冊和動態註冊
1,在Java文件中定義native方法。
2,在cmd命令行模式中切換目錄到定義native方法class文件(或者java文件)存放位置。
3,用javah 和javac命令生成包含native方法的.h頭文件。
4,實現native方法,用ndk-build編譯生成.so庫。
靜態註冊方法步驟比較繁瑣,在項目中我比較偏向動態註冊方法。
首先建立一個Android項目,勾選上include c++ support,MainActivity.java中內容:
public class MainActivity extends AppCompatActivity { // Used to load the 'native-lib' library on application startup. static { System.loadLibrary("JNITest"); } public String TAG = "MainActivity"; public JNITest jniTest; TextView mTextView; @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_main); TextView tv = (TextView) findViewById(R.id.sample_text); mTextView = (TextView)findViewById(R.id.textView); jniTest = new JNITest(); int sum = jniTest.addInt(4,3); // Example of a call to a native method mTextView.setText(jniTest.getString()+" "+sum); Log.d(TAG,"set text after...."); } }
再建立一個java文件定義所須要的native方法,這裏定義了兩個方法
package com.example.szq.testjni; public class JNITest { public JNITest(){ } public native String getString(); public native int addInt(int a,int b); }
在src/main目錄下建立jni文件夾,並新建JNITest.c和Android.mk兩個文件
#include <stdio.h> #include <stdlib.h> #include <jni.h> #include <assert.h> #define JNIREG_CLASS "com/example/szq/testjni/JNITest" //定義native方法的java文件 //實現 jstring jni_getstr(JNIEnv* jniEnv,jobject ob) { return (*jniEnv)->NewStringUTF(jniEnv,"動態註冊JNI test"); } jint jni_add(JNIEnv* jniEnv,jobject ob, jint a,jint b) { return a+b; } static JNINativeMethod gMethods[] = { {"getString", "()Ljava/lang/String;", (void*)jni_getstr}, {"addInt", "(II)I", (void*)jni_add}, }; static int registerNativeMethods(JNIEnv* env , const char* className , JNINativeMethod* gMethods, int numMethods) { jclass clazz; clazz = (*env)->FindClass(env, className); if (clazz == NULL) { return JNI_FALSE; } if ((*env)->RegisterNatives(env, clazz, gMethods, numMethods) < 0) { return JNI_FALSE; } return JNI_TRUE; } /* * 爲全部類註冊本地方法 */ static int registerNatives(JNIEnv* env) { int re = registerNativeMethods(env, JNIREG_CLASS,gMethods, sizeof(gMethods)/sizeof(gMethods[0])); return re; } /* * System.loadLibrary("lib")時會調用 * 若是成功返回JNI版本, 失敗返回-1 */ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM* vm, void* reserved) { JNIEnv* env = NULL; jint result = -1; if ((*vm)->GetEnv(vm, (void**) &env, JNI_VERSION_1_6) != JNI_OK) { return -1; } assert(env != NULL); if (!registerNatives(env)) {//註冊 return -1; } //成功 result = JNI_VERSION_1_6; return result; }
配置Android.mk
LOCAL_PATH := $(call my-dir) include $(CLEAR_VARS) LOCAL_MODULE := JNITest LOCAL_SRC_FILES := JNITest.c LOCAL_LDFLAGS += -llog include $(BUILD_SHARED_LIBRARY)
在命令行模式下切換到jni目錄,運行ndk-build會生成.so庫(前提是ndk環境先配置好),將.so文件copy到src/main/libs中,構建項目,就能運行出下面的結果:
注:生成的.so文件必定要在與jni同一層的libs文件夾中
配置build.gradle,由於在構建項目時,編譯器會自動加載gradle文件,因此在gradle中加入編譯的任務(task)就能編譯jni中的c文件了,配置以下:
apply plugin: 'com.android.application' android { compileSdkVersion 26 defaultConfig { applicationId "com.example.szq.testjni" minSdkVersion 18 targetSdkVersion 26 versionCode 1 versionName "1.0" testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner" ndk{ moduleName "JNITest" ldLibs "log", "z", "m" abiFilters "armeabi", "armeabi-v7a", "x86" //用於指定應用應該使用哪一個標準庫,此處添加c++庫支持 stl "stlport_static" // 支持stl cFlags "-fexceptions" // 支持exception } tasks.withType(JavaCompile) { compileTask -> compileTask.dependsOn 'ndkBuild', 'copyJniLibs' } sourceSets.main{ jniLibs.srcDirs = ['libs'] } externalNativeBuild { cmake { cppFlags "" } } } buildTypes { release { minifyEnabled false proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro' } } externalNativeBuild { cmake { path "CMakeLists.txt" } } } task ndkBuild(type: Exec) { // def ndkDir = project.plugins.findPlugin('com.android.application').sdkHandler.getNdkFolder() def ndkDir = project.android.ndkDirectory commandLine "$ndkDir\\ndk-build.cmd", '-C', 'src/main/jni', "NDK_OUT=$buildDir/ndk/obj", "NDK_APP_DST_DIR=$buildDir/ndk/libs/\$(TARGET_ARCH_ABI)" } task copyJniLibs(type: Copy) { from fileTree(dir: file(buildDir.absolutePath + '/ndk/libs'), include: '**/*.so') into file('src/main/jniLibs') } dependencies { implementation fileTree(dir: 'libs', include: ['*.jar']) implementation 'com.android.support:appcompat-v7:26.1.0' implementation 'com.android.support.constraint:constraint-layout:1.0.2' testImplementation 'junit:junit:4.12' androidTestImplementation 'com.android.support.test:runner:1.0.1' androidTestImplementation 'com.android.support.test.espresso:espresso-core:3.0.1' }
這樣直接構建項目,構建完成就能運行程序了。
在Android studio 3.0版本中添加了更加方便的CMake來編譯jni,配置文件是CMakeLists.txt,CMake會在之後的項目中常常用到,有興趣的能夠一塊兒研究下