最近整理了以前关于jni的代码,这里梳理下,供以后参考。
JNI简介
JNI是Java Native Interface的缩写,它提供了若干的接口实现了Java和其他语言的通信(主要是c、c++)。从Java1.1开始,JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。jni是Android中java和c++之间连接的桥梁,jni是jvm提供的一种与native方法对接的方式。
JNI的副作用
一旦使用JNI,JAVA程序就丧失了JAVA平台的两个优点:
1、程序不再跨平台。要想跨平台,必须在不同的系统环境下重新编译本地语言部分。
2、程序不再是绝对安全的,本地代码的不当使用可能导致整个程序崩溃。一个通用规则是,你应该让本地方法集中在少数几个类当中。这样就降低了JAVA和C之间的耦合性。
JNI使用场景
当你开始着手准备一个使用JNI的项目时,请确认是否还有替代方案。应用程序使用JNI会带来一些副作用。下面给出几个方案,可以避免使用JNI的时候,达到与本地代码进行交互的效果:
1、JAVA程序和本地程序使用TCP/IP或者IPC进行交互。
2、当用JAVA程序连接本地数据库时,使用JDBC提供的API。
3、JAVA程序可以使用分布式对象技术,如JAVA IDL API。
这些方案的共同点是,JAVA和C处于不同的线程,或者不同的机器上。这样,当本地程序崩溃时,不会影响到JAVA程序。
下面这些场合中,同一进程内JNI的使用无法避免:
1、程序当中用到了JAVA API不提供的特殊系统环境才会有的特征。而跨进程操作又不现实。
2、你可能想访问一些己有的本地库,但又不想付出跨进程调用时的代价,如效率,内存,数据传递方面。
3、JAVA程序当中的一部分代码对效率要求非常高,如算法计算,图形渲染等。
总之,只有当你必须在同一进程中调用本地代码时,再使用JNI。
JNI注册方法
注册方法有两种:静态注册和动态注册
静态注册
1,在Java文件中定义native方法。
2,在cmd命令行模式中切换目录到定义native方法class文件(或者java文件)存放位置。
3,用javah 和javac命令生成包含native方法的.h头文件。
4,实现native方法,用ndk-build编译生成.so库。
静态注册方法步骤比较繁琐,在项目中我比较偏向动态注册方法。
动态注册JNI
首先创建一个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文件夹中
NDK自动编译配置
配置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会在以后的项目中经常用到,有兴趣的可以一起研究下