代码可以直接复制到项目里运行学习
package com.example.zhuangtest
import androidx.appcompat.app.AppCompatActivity
import android.os.Bundle
import android.util.Log
import com.example.zhuangtest.databinding.ActivityMainBinding
class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding
val name = "changeString"
val age = 29
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root)
// Example of a call to a native method
changeName()
changeAge()
callAddMethod()
binding.sampleText.text = age.toString()
testo1()
testo2()
testo3()
test04()
test05()
}
fun testo1() {
var intArray = intArrayOf(1,2,3,4,5,6)
var stringArrayy = arrayOf("李小龙", "李连杰" , "李元霸")
testArrayAction(99,"你好", intArray, stringArrayy)
for (i in intArray) {
Log.v("java层数据", "$i")
}
}
fun testo2() {
var student = Student()
student.name = "史泰龙"
student.age = 88
putObject(student, "九阳神功")
}
fun testo3() {
insertObject()
}
fun test04() {
quote()
}
fun test05() {
delQuote()
}
/**
* A native method that is implemented by the 'zhuangtest' native library,
* which is packaged with this application.
*/
external fun stringFromJNI(): String
external fun getStringPwd(): String
// 引用类型
external fun changeName()
// 基本数据类型
external fun changeAge()
// 给jni调用
external fun callAddMethod()
// String引用类型 数组
external fun testArrayAction(count : Int, textInfo: String, ints: IntArray, strs: Array<String>)
// 类类型引用 Student
external fun putObject(student: Student, str : String)
// 凭空构建对象,没有传递对象给JNI
external fun insertObject()
// JNI验证构造函数的调用和全局或局部引用
external fun quote()
// 释放全局引用
external fun delQuote()
// 给native调用
fun add(n1: Int, n2 : Int) = n1 + n2
/**
* 属性签名规则
* java的boolean ----------- Z
* java的byte ----------- B
* java的char ----------- C
* java的short ----------- S
* java的Int ----------- I
* java的Long ----------- L
* java的float ----------- F
* java的double ----------- D
* java的void ----------- V
* java的引用类型 ----------- Lxxx/xxxx/类名;
* java的Strding ----------- Ljava/lang/String;
* java的array int[] ----------- [I double[][][] [[[D
*
* javap -s -p xxx.class 输出全部属性的签名
*/
companion object {
// Used to load the 'zhuangtest' library on application startup.
init {
// System.load("D:/xxx/xxxx.so") 绝对路劲加载so库
// 这种是从库目录遍历层级目录,去自动查找 apk里面的lib/libzhuangtest.so
System.loadLibrary("zhuangtest")
}
}
}
package com.example.zhuangtest
import android.util.Log
class Dog {
constructor(n1 : Int) { // "<init>" JIN中调用构造函数的名字都是<init>,它靠签名去确定的
Log.v("Dog", "n1")
}
constructor(n1 : Int, n2 : Int) { // "<init>"
Log.v("Dog", "n1 + n2")
}
constructor(n1 : Int, n2 : Int, n3 : Int) { // "<init>"
Log.v("Dog", "n1 + n2 + n3")
}
}
package com.example.zhuangtest
import android.util.Log
class Person {
var student : Student ? = null
set(value) {
Log.v("Person call setStudent ", value.toString());
}
companion object {
@JvmStatic
fun putStudent(student: Student) {
Log.v("Person call putStudent ", student.toString());
}
}
}
package com.example.zhuangtest
import android.util.Log
class Student {
var name : String ? = null
var age = 0
companion object {
@JvmStatic
fun showInfo(info: String) {
Log.v("Student", info)
}
}
override fun toString(): String {
return "Student(name=$name, age=$age)"
}
}
默认创建的:native-lib.cpp
#include <jni.h>
#include <string>
// NDK工具库里面的log库
#include <android/log.h>
#define TAG "JNI_TEST"
// ... 我都不知道传入什么,借助JNI里面的宏来自动帮我填充 __VA_ARGS__
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
using namespace std;
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_zhuangtest_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
string hello = "Hello from C++";
const char * name = hello.c_str();
name = "test jni";
return env->NewStringUTF(name);
}
extern "C" // 无论是C或者是C++,最后全部都是采用C的方式,所以采用C的标准
JNIEXPORT // 标记该方法可以被外部调用,某些平台上可以不加,为了兼容最好加上
jstring // java <---> native 转换用的,jni层对string的封装
JNICALL // 代表是JNI的标记,可以少
// 包名_类名_方法名 注意: 我们的包名_ native_1
// jobject thiz 谁调用,就是谁的实例
Java_com_example_zhuangtest_MainActivity_getStringPwd(JNIEnv *env, jobject thiz) {
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_changeName(JNIEnv *env, jobject thiz) {
// 获取class
jclass j_cls = env->GetObjectClass(thiz);
// 获取属性
// jfieldID GetFieldID(MainActivity.class, 属性名, 属性的签名)
jfieldID j_fid = env->GetFieldID(j_cls, "name", "Ljava/lang/String;");
//通过属性名或者变量
jstring j_str = static_cast<jstring>(env->GetObjectField(thiz, j_fid));
// 打印字符串
char * c_str = const_cast<char *>(env->GetStringUTFChars(j_str, NULL));
LOGI("jni : %s", c_str);
// printf() C cout << <<endl; C++
//修改成 Beyond
jstring jName = env->NewStringUTF("Beyond");
env->SetObjectField(thiz, j_fid, jName);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_changeAge(JNIEnv *env, jobject thiz) {
// 跟反射差不多,没什么好说的,直接用,这个东西写多几次自然而然就了解了。
jclass j_cls = env->GetObjectClass(thiz);
jfieldID j_fid = env->GetFieldID(j_cls, "age", "I");
jint age = env->GetIntField(thiz, j_fid);
age += 10;
env->SetIntField(thiz, j_fid, age);
}
// 调用java层的函数
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_callAddMethod(JNIEnv *env, jobject job) {
jclass j_cls = env->GetObjectClass(job);
// 两个int类型的入参和一个int的返回参数 "(II)I"
jmethodID j_method = env->GetMethodID(j_cls, "add", "(II)I");
int sum = env->CallIntMethod(job, j_method, 10, 10);
LOGE("sum result : %d\n",sum);
}
// jint == int
// jstring == String
// jintArray == int[]
// jobjectArray == 引用类型数组
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_testArrayAction(JNIEnv *env, jobject thiz, jint count,
jstring text_info, jintArray ints,
jobjectArray strs) {
// int 和 String ==> jint jstring
int countInt = count; // jint 本质上是int,所以可以用int接收,int是它的顶层父类
LOGI("参数一 count:%d\n", countInt);
// 操作杆 --> JMV env->
const char * textInfo = env->GetStringUTFChars(text_info, NULL);
LOGI("参数二 textInfo:%s\n", textInfo);
// 把jint[] 转成 int*
jint * jintArray = env->GetIntArrayElements(ints, NULL);
//java 层数组的长度
// jsize GetArrayLength(jarray array)
// 接收jarray 我们传入了一个 jintArray ,因为jintArray的父类也是_jarray
jsize size = env->GetArrayLength(ints);
for(int i = 0; i < size; ++i) {
*(jintArray+i) += 100;
LOGI("参数三 int[]:%d\n", *jintArray+i);
}
// 直接修改指针所对应的值 java层打印,发现没修改成功,还是原来的123456.
// java --> JVM(JNI) --> C/C++ 我们直接修改C/C++层,发现java层显示的还是原来的值,说明这中间肯定有赋值操作有另外的引用。
// 类似这种情况,我们修改之后还需要通过env进行刷新 env操作杆告诉JVM
/**
* 第一个入参是原来的jinArray数组,第二个是修改后的数组
* 第三个刷新类型:
* 0: 刷新java数组,并释放C++层数组。
* JIN——COMMIT : 只提交,只刷新java层数组,不释放C++层数组
* JIN——ABORT: 只释放C++层数组
* 基本上我们经常用到的是 0 ,其它两种几乎用不到。
*/
// 刷新之后java层打印就是我们修改后预期的数字了
env->ReleaseIntArrayElements(ints, jintArray, 0);
// jobjectArray 代表是java层的引用类型,不一样 ,注意长度一定要经过env从JVM获取到java层的长度
jsize strsize = env->GetArrayLength(strs);
for(int i = 0; i < strsize; i++) {
// 我们知道是string,强制转换
jstring jobj = static_cast<jstring>(env->GetObjectArrayElement(strs, i));
// jstring 转成 char * C++层的
const char * jobjCharp = env->GetStringUTFChars(jobj, NULL);
LOGI("参数四 引用类型String 具体的: %s\n", jobjCharp);
//释放jstring 不写也没问题,最好是写。
env->ReleaseStringUTFChars(jobj, jobjCharp);
}
}
// jobject student == Student
// jstring str == String
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_putObject(JNIEnv *env,
jobject thiz,
jobject student,
jstring str) {
const char * strChar = env->GetStringUTFChars(str, NULL);
LOGI("strChar: %s\n", strChar);
// 释放
env->ReleaseStringUTFChars(str, strChar);
// 第一种方式,编译器非常智能,会自动填充
// jclass studentClass = env->FindClass("com/example/zhuangtest/Student");
// 第二种
jclass studentClass = env->GetObjectClass(student);
// Student 类里面的函数规则 签名
// 编译器非常智能,第二个参数例如写了:setName,第三个参数签名会自动填充修正,但是基本的规则我们还是需要了解的
jmethodID setName = env->GetMethodID(studentClass, "setName","(Ljava/lang/String;)V");
jmethodID getName = env->GetMethodID(studentClass, "getName", "()Ljava/lang/String;");
jmethodID showInfo = env->GetStaticMethodID(studentClass, "showInfo","(Ljava/lang/String;)V");
// 调用setName
jstring value = env->NewStringUTF("AAAA");
env->CallVoidMethod(student, setName, value);
// 调用getName
jstring getNameResult = static_cast<jstring>(env->CallObjectMethod(student, getName));
// 直接打印是不行的,会报错,getNameResult是JNI层的,打印这里是C++,需要的是指针。
// LOGI("调用到getNmae方法,值是:%s\n", getNameResult);
const char * getNameValue = env->GetStringUTFChars(getNameResult, NULL);
LOGI("调用到getNmae方法,值是:%s\n", getNameValue);
// 调用showInfo
jstring jstringInfo = env->NewStringUTF("静态方法你好,我是C++");
env->CallStaticVoidMethod(studentClass, showInfo, jstringInfo);
}
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_insertObject(JNIEnv *env, jobject thiz) {
// 1、通过包名 + 类名的方式 拿到 Student class
const char * studentstr = "com/example/zhuangtest/Student";
jclass studentClass = env->FindClass(studentstr);
// 2、通过Student 的class 实例化Student对象,C++ new Student
// AllocObject 只实例化对象,不会调用对象的构造函数
jobject studentObject = env->AllocObject(studentClass);
// 方法的规则定义
jmethodID setNmae = env->GetMethodID(studentClass, "setName", "(Ljava/lang/String;)V");
jmethodID setAge = env->GetMethodID(studentClass, "setAge", "(I)V");
// 调用方法
jstring strValue = env->NewStringUTF("无");
env->CallVoidMethod(studentObject, setNmae, strValue);
env->CallVoidMethod(studentObject, setAge, 99);
// =================================== 下面是Person 对象
const char * persontstr = "com/example/zhuangtest/Person";
jclass personClass = env->FindClass(persontstr);
jobject personObject = env->AllocObject(personClass);
jmethodID setStudent = env->GetMethodID(personClass, "setStudent", "(Lcom/example/zhuangtest/Student;)V");
jmethodID putStudent = env->GetStaticMethodID(personClass, "putStudent","(Lcom/example/zhuangtest/Student;)V");
env->CallVoidMethod(personObject, setStudent, studentObject);
env->CallStaticVoidMethod(personClass, putStudent, studentObject);
// 释放 第一类
env->DeleteLocalRef(personClass);
env->DeleteLocalRef(studentClass);
env->DeleteLocalRef(personObject);
env->DeleteLocalRef(studentObject);
// 第二类
// env->GetStringUTFChars()
// env->ReleaseStringUTFChars()
// jobject jstring jclass 这里都属于局部引用,函数结束后会自动释放,养成好习惯用完马上释放,内存性能更加。
}
// C++ 有堆 、栈
// JNI函数 局部引用 全局引用 它属于JVM中的一块
jclass dogClass; // 这个实际上是局部引用
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_quote(JNIEnv *env, jobject thiz) {
if (NULL == dogClass) {
// const char * dogStr = "com/example/zhuangtest/Dog";
// dogClass = env->FindClass(dogStr);
// 升级为全局引用,需要手动释放 相当于C++ new 对象
const char * dogStr = "com/example/zhuangtest/Dog";
jclass temp = env->FindClass(dogStr);
dogClass = static_cast<jclass>(env->NewGlobalRef(temp));
}
// "<init>" 签名返回对象V 是不会变的,构造函数是不会有返回值的
jmethodID init1 = env->GetMethodID(dogClass, "<init>", "(I)V");
jmethodID init2 = env->GetMethodID(dogClass, "<init>", "(II)V");
jmethodID init3 = env->GetMethodID(dogClass, "<init>", "(III)V");
jobject dog1 = env->NewObject(dogClass, init1, 100);
jobject dog2 = env->NewObject(dogClass, init2, 100, 200);
jobject dog3 = env->NewObject(dogClass, init3, 100, 200, 300);
env->DeleteLocalRef(dog1);
env->DeleteLocalRef(dog2);
env->DeleteLocalRef(dog3);
// 函数结束释放 dogClass 如果调用两次,第二次进不了if会崩溃,dogClass虽然被释放了,但是不等于NULL,是一个悬空指针。
}
extern int age; // 声明
extern void show();// 声明 show 函数
extern "C"
JNIEXPORT void JNICALL
Java_com_example_zhuangtest_MainActivity_delQuote(JNIEnv *env, jobject thiz) {
if (dogClass != NULL) {
LOGI("全局引用释放完毕");
env->DeleteLocalRef(dogClass);
dogClass = NULL; //不要去成为悬空指针
}
// 直接调用,实现在另外的文件 test.cpp
show();
}
记得CMakeLists.txt里面要加上 native-lib.cpp test.cpp ,有两个cpp文件