JNI入门基础
环境安装
想要使用jni进行ndk开发,我们首先要安装下面这些工具,否则直接从入门到放弃。
-
下载ndk支持
在Android studio中下载上图中框选的两个工具,版本号自己任意选一个。下载完成之后,Android Studio就拥有了进行ndk编译的能力。 -
新建ndk工程
如果是一个全新的工程,我们在新建工程时,选择c++工程,然后无脑下一步即可,这样Android Studio就会给你生成一个c++工程的模板代码,我们后续要添加一些c/c++代码都可以仿照这个模板。
如果,我们是现在原有的工程上添加一个c/c++的library呢?我们可以新建一个native library,然后也是无脑下一步,跟新建一个java的library流程一致。
-
c/c++代码提示设置
在我们编写c/c++的过程中,有一个十分恶心的东西,就是没有代码提示,有时候编译器抽风,无故报红。这对一些ndk老手来说可能不算什么,但是对一些新手来说就十分难受了,那么我们该如何解决这个问题呢?
JNI与NDK
jni,即Java本地接口,它的存在使得Java与本地其他类型语言(如c、c++)能够进行交互。java的很多功能实际上的驱动都是通过c/c++开发的,通过JNI,Java可以调用c/c++实现的驱动,从而扩展jvm的能力。另外,在高效率的数学运算、游戏的实时渲染、音视频的编码和解码方面,一般都是用c开发的。
-
jni在线api:https://docs.oracle.com/javase/8/docs/technotes/guides/jni/spec/jniTOC.html
-
jni和ndk的关系
- jni是jdk提供的一套非常强大的框架,可供c/c++与java互相调用,一般jni的头文件在jdk安装路径的include文件夹里面。
- ndk是Android平台提供的Native开发工具集开发包,可以认为是把前面的jni额外又封装了一层。一般ndk中的jni安装在如下目录下:C:\\Users\\dell\\AppData\\Local\\Android\\Sdk\\ndk\\21.4.7075529\\toolchains\\llvm\\prebuilt\\windows-x86_64\\sysroot\\usr\\include
-
案例讲解
我们来看看ndk开发中涉及到的几个文件:- cpp/CMakeLists.txt
# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html# Sets the minimum version of CMake required to build the native library.cmake_minimum_required(VERSION 3.18.1)# Declares and names the project.project("jnidemo")# Creates and names a library, sets it as either STATIC
# or SHARED, and provides the relative paths to its source code.
# You can define multiple libraries, and CMake builds them for you.
# Gradle automatically packages shared libraries with your APK.add_library( # Sets the name of the library.jnidemo# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cpp)# Searches for a specified prebuilt library and stores the path as a
# variable. Because CMake includes system libraries in the search path by
# default, you only need to specify the name of the public NDK library
# you want to add. CMake verifies that the library exists before
# completing its build.find_library( # Sets the name of the path variable.log-lib# Specifies the name of the NDK library that# you want CMake to locate.log)# Specifies libraries CMake should link to your target library. You
# can link multiple libraries, such as libraries you define in this
# build script, prebuilt third-party libraries, or system libraries.target_link_libraries( # Specifies the target library.jnidemo# Links the target library to the log library# included in the NDK.${log-lib})
- cpp/native-lib.cpp:我们所写的c/c++就是放在该文件中或者新建一个新的c/c++文件
#include <jni.h>
#include <string>// 日志输出
#include <android/log.h>#define TAG "Brett"// __VA_ARGS__ 代表 ...的可变参数
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);extern "C" JNIEXPORT jstring JNICALL
Java_com_mvp_jnidemo_MainActivity_stringFromJNI(JNIEnv *env,jobject /* this */) {std::string hello = "Hello from C++";return env->NewStringUTF(hello.c_str());
}extern "C"
JNIEXPORT void JNICALL
Java_com_mvp_jnidemo_MainActivity_changeStr(JNIEnv *env, jobject thiz) {//jclass获取方式一
// jclass cls = env->FindClass("com/mvp/jnidemo/MainActivity");//方式二jclass cls = env->GetObjectClass(thiz);jfieldID nameFid = env->GetFieldID(cls, "name", "Ljava/lang/String;");jstring value = env->NewStringUTF("Beyond");env->SetObjectField(thiz, nameFid, value);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_mvp_jnidemo_MainActivity_changeAge(JNIEnv *env, jclass clazz) {jfieldID ageFid = env->GetStaticFieldID(clazz, "age", "I");int age = env->GetStaticIntField(clazz, ageFid);env->SetStaticIntField(clazz, ageFid, age + 10);}//c调用java
extern "C"
JNIEXPORT jint JNICALL
Java_com_mvp_jnidemo_MainActivity_callJavaMethod(JNIEnv *env, jobject thiz) {jclass mainActivitCls = env->GetObjectClass(thiz);// jmethodID (*GetMethodID)(JNIEnv*, jclass, const char*, const char*);jclass cls = env->GetObjectClass(thiz);jmethodID addMid = env->GetMethodID(cls, "add", "(II)I");// jint CallIntMethod(jobject obj, jmethodID methodID, ...)int result = env->CallIntMethod(thiz, addMid, 1, 1);LOGD("result:%d\\n", result)// ++++++++++++++++++++++ C调用 public String showString(String str,int value) 函数jmethodID showStringMid = env->GetMethodID(mainActivitCls, "showString", "(Ljava/lang/String;I)Ljava/lang/String;");// jobject (*CallObjectMethod)(jobject, jmethodID, ...);jstring value = env->NewStringUTF("李元霸");jstring resultStr = (jstring) env->CallObjectMethod(mainActivitThis, showStringMid, value, 9527);// jstring是在jni中的,c/c++中需要转为char*const char * resultCstr = env->GetStringUTFChars(resultStr, NULL);LOGD("r==:%s\\n", resultCstr);
}
extern “C” // 表示下面的代码,采用C的编译方式 如果是新建了一个.c文件,则需要删掉否则编译会报错,如果是新建了一个c++文件,则需要添加
JNIEXPORT // JNIEXPORT: JNI重要标记关键字,不能少 可以认为是为不同os平台设定的一个调用规则(标记为该方法可以被外部调用) Windows内部规则,与Linux内部规则 不同
void // void 代表java中的 void
JNICALL // 也是一个关键字,(可以少的) jni call (约束函数入栈顺序,和堆栈内存清理的规则)
JNIEnv *env JNI JNIEnv是整个jni的核心所在,我们学习jni其实就是学习JNIEnv里面提供的api方法
如果当前是 native-lib.c
// (*env)->xxx函数
// (*env)->DeleteLocalRef()
// C语言是 JNIEnv *env 二级指针
// (*env)->DeleteLocalRef(env, NULL); // C是没有对象的,想持有env环境,就必须传递进去
(*env)->NewStringUTF(env,“AAAA”);
如果当前是 native-lib.cpp ->调用一级指针下的函数
// env->xxx函数
// env->DeleteLocalRef()
// C++语言是 JNIEnv *env 一级指针
// env->DeleteLocalRef(NULL); // C++是有对象的,本来就会持有this,所以不需要传
为简单通用起见,我们就使用c++语言来编写相关的逻辑。
- 调用者(拥有native方法的类)——>MainActivity
package com.mvp.jnidemo;import androidx.appcompat.app.AppCompatActivity;import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;import com.mvp.jnidemo.databinding.ActivityMainBinding;public class MainActivity extends AppCompatActivity {private String name = "Brett";private static int age = 20;// Used to load the 'jnidemo' library on application startup.//可以认为哪里又native方法,哪里就需要System.loadLibrary("xxx");方法static {System.loadLibrary("jnidemo");}private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());Log.e("MainActivity", "name is " + name+" ,age is "+age);changeStr();changeAge();Log.e("MainActivity", "name is " + name+" ,age is "+age);callJavaMethod();}public int add(int num1, int num2) {Log.e("MainActivity", "num1 is " + num1 + " ,num2 is " + num2);return num1 + num2;}public String showString(String str,int value) {System.out.println("C居然调用了我 showString str:" + str + " value:" + value);return "【" + str + "】";}/* A native method that is implemented by the 'jnidemo' native library,* which is packaged with this application.*/public native String stringFromJNI();public native void changeStr();public static native void changeAge();public native int callJavaMethod();}
附:类型签名
代码 | 类型 |
---|---|
“I” | int |
“B” | byte |
“C” | char |
“D” | double |
“F” | float |
“J” | long |
“S” | short |
“Z” | boolean |
“V” | void |
“[…;” | 数组 |
“[[…;” | 二维数组 |
“[[[…;” | 三维数组 |
String[] | [Ljava/lang/String; |
String[][] | [[Ljava/lang/String; |
int,String,String[] | ILjava/lang/String;[Ljava/lang/String; |
int,boolean,long,String[],double | IZJ[Ljava/lang/String;D |
Class<?>,String,Object…paramType | Ljava/lang/Class;Ljava/lang/String;[Ljava/lang/Object; |
int[] | [I |