环境安装
想要使用jni进行ndk开发,我们首先要安装下面这些工具,否则直接从入门到放弃。
JNI与NDK
jni,即Java本地接口,它的存在使得Java与本地其他类型语言(如c、c++)能够进行交互。java的很多功能实际上的驱动都是通过c/c++开发的,通过JNI,Java可以调用c/c++实现的驱动,从而扩展jvm的能力。另外,在高效率的数学运算、游戏的实时渲染、音视频的编码和解码方面,一般都是用c开发的。
# 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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView 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 |