学习目的:学会ndk编程编写基本c代码

so中常用的log输出

//引入头文件
#include<android/log.h>
//第一个参数为日志等级,第二个参数为tag,第三个参数为输出文本
__android_log__print(int prio, const char* tag, const char* text);
//为了方便起见,通常这样定义
#define LOGD(......) __android_log__print(ANDROID_LOG_DEFAULT, TAG, __VA_ARGS__)

NDK多线程

创建多线程

//线程id,其实就是long
pthread_t thread;
//线程id 线程属性 函数 传递
pthread_create(&thread, nullptr, myThread, nullptr);
//等待线程执行完毕
//默认的线程属性是joinable随着主线程结束而结束
//线程属性是dettah,可以分离执行
pthread_join(thread, nullptr);
//子线程中使用它来退出线程
pthread_exit(0)

传参

//传一个参数
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "hyq"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#include<pthread.h>
void* myThread(void* a){
for(int i=0; i<10; i++){
LOGD("myThread: %d", i);
}
int* num = static_cast<int*>(a);
LOGD("%d", *num);
pthread_exit(0);
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
int num = 10;
pthread_t pthread;
pthread_create(&pthread, nullptr,myThread, &num);
pthread_join(pthread, nullptr);
return env->NewStringUTF(hello.c_str());
}
//传递多个参数
#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "hyq"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#include<pthread.h>
struct hyq{
std::string name;
int age;
};
void* myThread(void* a){
for(int i=0; i<10; i++){
LOGD("myThread: %d", i);
}
hyq* w = static_cast<hyq*>(a);
LOGD("w.name: %s, w.age: %d", w->name.c_str(), w->age);
pthread_exit(0);
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
hyq w;
w.age=20;
w.name="www";
pthread_t pthread;
pthread_create(&pthread, nullptr,myThread, &w);
pthread_join(pthread, nullptr);
return env->NewStringUTF(hello.c_str());
}

接收返回值

只能接收字符串类型的返回值

#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "hyq"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__)
#include<pthread.h>
struct hyq{
std::string name;
int age;
};
void* myThread(void* a){
for(int i=0; i<10; i++){
LOGD("myThread: %d", i);
}
hyq* w = static_cast<hyq*>(a);
LOGD("w.name: %s, w.age: %d", w->name.c_str(), w->age);
pthread_exit(char*("hyq"));
}
extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {
std::string hello = "Hello from C++";
hyq w;
w.age=20;
w.name="www";
void* y;
pthread_t pthread;
pthread_create(&pthread, nullptr,myThread, &w);
pthread_join(pthread, &y);
LOGD("%s", (char*)y);
return env->NewStringUTF(hello.c_str());
}

函数注册

#include <jni.h>
#include <string>
#include <android/log.h>
#include <pthread.h>

#define TAG "xiaojianbang"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO , TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);


JavaVM* globalVM = nullptr;

void* myThread(void* a){

JNIEnv* env = nullptr;
if(globalVM->AttachCurrentThread(reinterpret_cast<JNIEnv **>(&env), nullptr) != JNI_OK){
LOGD("myThread GetEnv failed");
}else {
LOGD("myThread JNIEnv: %p", env);
}

pthread_exit(0);
}

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_ndkdemo_MainActivity_stringFromJNI(
JNIEnv* env,
jobject /* this */) {

LOGD("stringFromJNI JNIEnv: %p", env);
std::string hello = "Hello from C++";

pthread_t thread;
pthread_create(&thread, nullptr, myThread, nullptr);
pthread_join(thread, nullptr);

return env->NewStringUTF(hello.c_str());
}

jstring encodeFromC(JNIEnv* env, jobject obj, jint a, jbyteArray b, jstring c) {
return env->NewStringUTF("encodeFromC");
}

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved) {
globalVM = vm;
JNIEnv *env = nullptr;
if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {
LOGD("GetEnv failed");
return -1;
}

jclass MainActivityClazz = env->FindClass("com/example/ndkdemo/MainActivity");
// typedef struct {
// const char* name;
// const char* signature;
// void* fnPtr;
// } JNINativeMethod;
JNINativeMethod methods[] = {
//public native String stringFromJNI2(int a, byte[] b, String c);
{"stringFromJNI2", "(I[BLjava/lang/String;)Ljava/lang/String;", (void *)encodeFromC},
};
//jint RegisterNatives(jclass clazz, const JNINativeMethod* methods, jint nMethods)
env->RegisterNatives(MainActivityClazz, methods, sizeof(methods) / sizeof(JNINativeMethod));
return JNI_VERSION_1_6;
}

多个cpp文件编译成一个so

在CMakeLists.txt文件中添加要编译的.cpp

add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp main.cpp)

注意用空格分隔,不要用逗号

so路径的动态获取

package com.example.ndkdemo;

import android.content.Context;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.util.Log;

import java.util.List;

public class Utils {

public String getPath(Context cxt){
PackageManager pm = cxt.getPackageManager();
List<PackageInfo> pkgList = pm.getInstalledPackages(0);
if (pkgList == null || pkgList.size() == 0) return null;
for (PackageInfo pi : pkgList) {
if (pi.applicationInfo.nativeLibraryDir.startsWith("/data/app/")
&& pi.packageName.startsWith("com.example.ndkdemo")) {
Log.e("hyq", pi.applicationInfo.nativeLibraryDir);
return pi.applicationInfo.nativeLibraryDir;
}
}
return null;
}
}

注意:使用dlopen时,首先要修改build.gradle.kts中的

packagingOptions{
jniLibs{
useLegacyPackaging = true
}
}

通过jni创建Java对象

NewObject创建对象

//找到类
jclass clazz = env->FindClass("com/example/javaandso1/NDKDemo");
//找到方法
jmethodID methodId = env->GetMethodID(clazz, "<init>","()V");
//创建对象
jobject ReflectDemoObj = env->NewOjbect(clazz, methodId);
//打印
LOGD("ReflectDemoObj %p", ReflectDemoOjb);

通过jni创建Java对象

NewObject创建对象

//找到类
jclass clazz = env->FindClass("com/example/javaandso1/NDKDemo");
//找到方法
jmethodID methodId = env->GetMethodID(clazz, "<init>","()V");
//创建对象
jobject ReflectDemoObj = env->NewOjbect(clazz, methodId);
//打印
LOGD("ReflectDemoObj %p", ReflectDemoOjb);

AllocObject创建对象

//找到类
jclass clazz = env->FindClass("com/example/javaandso1/NDKDemo");
//找到方法ID
jmethodID methodId = env->GetMethodID(clazz, "<init>","(Ljava/lang/String;I)V");
//创建对象
jobject ReflectDemoObj2 = env->AllocObject(clazz);
jstring str = env->NewStringUTF("from jni str");
env->CallNonVirtualVoidMethod(ReflectDemoObj, clazz, methodId, jstr, 100);

通过jni访问Java属性

获取静态字段

//找到类
jclass clazz = env->FindClass("com/example/javaandso1/NDKDemo");
//找到字段ID
jfieldID privateStaticStringField = env->GetStaticFieldID(clazz,"privateStaticStringField","Ljava/lang/String;");
//得到字段内容
jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(clazz,privateStaticStringField));
//转化成c字符串
const char* privatestr = env->GetStringUTFChars(privateStaticString,nullptr);
LOGD("privateStaticString: %s",privatecstr);
//释放
env->ReleaseStringUTFChars(privateStaticString,privatecstr);

获取对象字段

//找到类
jclass clazz = env->FindClass("com/example/javaandso1/NDKDemo");
//找到字段ID
jfieldID publicStringField = env->GetFieldID(clazz,"publicStringField","Ljava/lang/String;");
//得到字段内容
jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(ReflectDemoObj,publicStringField));
//转化成c字符串
const char* publiccstr = env->GetStringUTFChars(publicString, nullptr);
LOGD("publicStringField: %s", publiccstr);
env ->ReleaseStringUTFChars(publicString, publiccstr);

设置字段

jfieldID privateStringFieldID = env->GetFieldID(clazz, "privateStringField", "Ljava/lang/String;");
env ->SetObjectField(ReflectDemoObj,privateStringFieldID,env->NewStringUTF("xiaojianbang"));

通过jni访问Java数组

//获取数组字段ID
jcalss = env->FindClass("com/example/javaandso1/NDKDemo");
jfieldID byteArrayID = env -> GetFieldID(clazz, "byteArray", "[B");
jbyteArray byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
int _byteArrayLength = env -> GetArrayLength(byteArray);
//修改数组字段
char javaByte[10];
for(int i=0; i<10; i++){
javaByte[i] = static_cast<char>(100 - i);
}
const jbyte *java_array = reinterpret_cast<const jbyte*>(javaByte);
env->SetByteArrayRegion(byteArray, 0, _byteArrayLength, java_array);
//获取数组字段
byteArray = static_cast<jbyteArray>(env->GetObjectField(ReflectDemoObj, byteArrayID));
_byteArrayLength = env -> GetArrayLength(byteArray);
jbyte* CBytes = env->GetByteArrayElements(byteArray, nullptr);
for(int i=0; i<_byteArrayLength;i++){
LOGD("CBytes[%d]=%d",i,CBytes[i]);
}
env -> ReleaseByteArrayElements(byteArray, CBytes, 0);

通过jni访问Java方法1

调用静态函数

//找到类
jclass ReflectDemoClazz = env -> FindClass("com/example/javaandso1/NDKDemo");
//得到方法ID
jmethodID publicStaticFuncID = env -> GetStaticMethodID(ReflectDemoClazz, "publicStaticFunc","()V");
//调用静态方法
env -> CallStaticVoidMethod(ReflectDemoClazz, publicStaticFuncID);

调用对象方法

//得到方法id
jmethodID privateFuncID = env->GetMethodID(ReflectDemoClazz, "privateFuncID", "(Ljava/lang/String;I)Ljava/lang/String;");
jstring str2 = env->NewStringUTF("this is from JNI");
jstring retval_jstring = static_cast<jstring>(env->CallObjectMethod(ReflectDemoObj,privateFuncID, str2, 100));
const char* retval_cstr = env->GetStringUTFChars(retval_jstring, nullptr);
LOGD("privateStaticString: %s", retval_cstr);
env ->ReleaseStringUTFChars(retval_jstring, retval_cstr);

CallVoidMethodA

jvalue args[2];
args[0].l = str2; //l代表对象
args[1].i = 1000;

jstring retval = static_cast<jstring>(env->CallObjectMethodA(ReflectDemoObj, privateFuncID, args));
const char* cpp_retval = env->GetStringUTFChars(retval, nullptr);
LOGD("cpp_retval: %s", cpp_retval);
env->ReleaseStringUTFChars(retval, cpp_retval);

通过jni访问Java方法2

参数是数组,返回值是数组

jclass StringClazz = env->FindClass("java/lang/String");
jobjectArray StringArr = env->NewObjectArray(3, StringClazz, nullptr);
for(int i=0; i< 3;i++){
jstring str3 = env->NewStringUTF("NDK");
env->SetObjectArrayElement(StringArr, i, str3);
}
jmethodID privateStaticFuncId = env->GetStaticMethodID(ReflectDemoClazz, "privateStaticFunc", "([Ljava/lang/String;)[I");
jintArray intArr = static_cast<jintArray>(env->CallStaticObjectMethod(ReflectDemoClazz,
privateStaticFuncId,
StringArr));
int *cintArr = env->GetIntArrayElements(intArr, nullptr);
LOGD("cintArr[0]=%d",cintArr[0]);
env->ReleaseIntArrayElements(intArr, cintArr, JNI_ABORT);

通过Java访问父类方法

onCreateNative化

protected native void onCreate(Bundle savedInstanceState) ;
extern "C"
JNIEXPORT void JNICALL
Java_com_example_javaandso1_MainActivity_onCreate(JNIEnv *env, jobject thiz,
jobject saved_instance_state) {
// TODO: implement onCreate()
jclass AppCompatActivityClazz = env->FindClass("androidx/fragment/app/FragmentActivity");
jmethodID onCreateID = env->GetMethodID(AppCompatActivityClazz, "onCreate", "(Landroid/os/Bundle;)V");
env->CallNonvirtualVoidMethod(thiz, AppCompatActivityClazz, onCreateID, saved_instance_state);
}

内存管理

局部引用

大多数的jni函数,调用以后返回的结果都是局部引用

因此,env->NewLocalRef 基本不用

一个函数内的局部引用数量是有限制的,在早期的安卓系统中,体现的更为明显

当函数体内需要大量局部引用时,比如大循环中,最好及时删数不用的局部引用

可以使用 env->DeleteLocalRef 来删除局部引用

局部引用相关的其他函数

env->EnsureLocalCapacity(num) 判断是否有足够的局部引用可以使用,足够则返回0

需要大量使用局部引用时,手动删除太过麻烦,可使用以下两个函数来批量管理局部引用

env->PushLocalFrame(num)

env->PopLocalFrame(nullptr)

全局引用

在jni开发中,需要跨函数使用变量时,直接定义全局变量是没用的

需要使用以下两个方法,来创建和删除全局引用

env->NewGlobalRef

env->DeleteGlobalRef

弱全局引用

与全局引用基本相同,区别是弱全局引用有可能会被回收

env->NewWeakGlobalRef

env->DeleteWeakGlobalRef

子线程中获取Java类

在子线程中,findClass可以直接获取系统类

在主线程中获取类,使用全局引用来传递到子线程中

// 先在头部声明类
jclass MainActivityClazz = static_cast(env->NewGlobalRef(tempMainActivityClass));
// 然后将它声明成全局引用
JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved){
globalVM = vm;
JNIEnv *env = nullptr;
if(vm -> GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK){
LOGD("GetEnv failed");
return -1;
}
jclass tempClass = env -> FindClass("com/xiaojianbang/ndkdemo/MainActivity");
MainActivityClazz = static_cast<jclass>(env->NewGlobalRef(tempMainActivityClass));
}

在主线程中获取正确的ClassLoader,在子线程中取加载类

在Java中,可以先获取类的字节码,然后使用getClassLoader()来获取

Demo.class.getClassLoader();
new Demo().getClass().getClassLoader();
Class.forName(...类的路径...).getClassLoader();

在jni的主线程中获取ClassLoader()

将ClassLoaderObj声明成全局变量

jobject ClassLoaderObj;
jclass MainActivityClazz = env->FindClass("com/example/javaandso1/MainActivity");
jclass classClazz = env->FindClass("java/lang/Class");
jmethodID getClassLoaderID = env->GetMethodID(classClazz, "getClassLoader","()Ljava/lang/ClassLoader;");
jobject tempClassLoaderobj = env->CallObjectMethod(MainActivityClazz, getClassLoaderID);
ClassLoaderObj = env->NewGlobalRef(tempClassLoaderObj);

在jni的子线程中loadClass

jclass ClassLoaderClazz = env->FindClass("java/lang/ClassLoader");
jmethodID loadClassID = env->GetMethodID(ClassLoaderClazz, "loadClass", "(Ljava/lang/String;)Ljava/lang/Class");
jclass MainActivityClazz = static_cast<jclass>(env->CallObjectMethod(ClassLoaderObj,loadClassID,env->NewStringUTF(env->NewStringUTF("com.example.ndkdemo.MainActivity"))));

init与initarray

1、so在执行JNI_Onload之前,还会执行两个构造函数init、initarray

2、so加固、so中字符串加密等等,一般会把相关代码放到这里

3、init的使用

extern "C" void _init(){    //函数名必须为_init
、、、
}

4、initarray的使用

__attribute__ ((constructor)) void initArrayTest1(){、、、}
__attribute__ ((constructor)) void initArrayTest2(){、、、}
__attribute__ ((constructor)) void initArrayTest3(){、、、}
__attribute__ ((constructor, visibility("hidden"))) void initArrayTest4(){、、、}

//constructor后面的值,较小的先执行,最好从100以后开始使用,如果其中存在不写的话,默认放最后执行

OnCreateNative化

extern "C"
JNIEXPORT void JNICALL
Java_com_example_oncreativenative_MainActivity_onCreate(JNIEnv *env, jobject thiz,
jobject saved_instance_state) {
//实现super.onCreate()
//找到类
jclass FragmentActivityClazz = env->FindClass("androidx/fragment/app/FragmentActivity");
//找到方法ID
jmethodID onCreateID = env->GetMethodID(FragmentActivityClazz, "onCreate", "(Landroid/os/Bundle;)V");
//调用父类方法
env->CallNonvirtualVoidMethod(thiz, FragmentActivityClazz, onCreateID, saved_instance_state);

//实现getLayoutInflater()
//找到类
jclass ActivityClazz = env->FindClass("android/app/Activity");
//找到方法ID
jmethodID getLayoutInflaterID = env->GetMethodID(ActivityClazz, "getLayoutInflater", "()Landroid/view/LayoutInflater;");
//调用此方法得到layoutInflater
jobject LayoutInflater = env->CallObjectMethod(thiz, getLayoutInflaterID);

//实现binding = ActivityMainBinding.inflate(getLayoutInflater());
//找到类
jclass ActivityMainBindingClazz = env->FindClass("com/example/oncreativenative/databinding/ActivityMainBinding");
//找到方法ID
jmethodID inflateID = env->GetStaticMethodID(ActivityMainBindingClazz,"inflate","(Landroid/view/LayoutInflater;)Lcom/example/oncreativenative/databinding/ActivityMainBinding;");
//调用此方法的到binding
jobject ActivitybindingObj= env->CallStaticObjectMethod(ActivityMainBindingClazz, inflateID, LayoutInflater);

//实现binding.getRoot()
//找到ID
jmethodID getRootID = env->GetMethodID(ActivityMainBindingClazz, "getRoot", "()Landroidx/constraintlayout/widget/ConstraintLayout;");
//调用方法的到对象
jobject ConstraintLayout = env->CallObjectMethod(ActivitybindingObj, getRootID);

//实现setContentView()
//找到类
jclass appCompatActivity = env->FindClass("androidx/appcompat/app/AppCompatActivity");
//找到方法ID
jmethodID setContentVIewID = env->GetMethodID(appCompatActivity, "setContentView", "(Landroid/view/View;)V");
env->CallVoidMethod(thiz, setContentVIewID, ConstraintLayout);

//实现TextView tv = binding.sampleText
// 获取 sampleText 字段 ID
jfieldID sampleTextFieldID = env->GetFieldID(ActivityMainBindingClazz, "sampleText", "Landroid/widget/TextView;");
// 获取 TextView 对象
jobject tv = env->GetObjectField(ActivitybindingObj, sampleTextFieldID);
// 实现 tv.setText(stringFromJNI());
// 调用 stringFromJNI 方法获取字符串
jstring jstr = Java_com_example_oncreativenative_MainActivity_stringFromJNI(env, thiz);
// 找到 TextView 类
jclass TextViewClazz = env->FindClass("android/widget/TextView");
// 找到 setText 方法 ID
jmethodID setTextID = env->GetMethodID(TextViewClazz, "setText", "(Ljava/lang/CharSequence;)V");
// 调用 setText 方法
env->CallVoidMethod(tv, setTextID, jstr);
}