So中常用的Log输出

#include<android/log.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__);

NDK多线程

JavaVM每个进程中只有一份

JNIEnv每个线程中都有一份

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

JNIOnload

so中各函数执行的时机

init、init_array、JNI_OnLoad

JNI_OnLoad的定义

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

注意事项

一个so中可以不定义JNI_OnLoad

一旦定义了JNI_OnLoad,在so被加载的时候会自动执行

必须返回JNI版本 JNI_VERSION_1_6

JNIEnv的获取方式

vm->GetEnv
globalVM -> AttachCurrentThread //子线程只能用这个

so函数注册

JNI函数的静态注册

必须遵循一定的命名规则,一般是:

Java_包名_类名_方法名

系统会通过dlpoen加载对应的so,通过dlsym来回去指定名字的函数地址,然后调用

静态注册的jni函数,必然在导出表里

JNI函数的动态注册

通过env->RegisterNatives注册函数,通常在JNI_OnLoad中注册

可以给同一个Java函数注册多个native函数,以最后一次为准

先在java中声明

public native String stringFromJNI1(String a, int b, byte[] c);

再在cpp中定义

#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "hyq"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso1_MainActivity_stringFromJNI(
JNIEnv* env,
jobject){
std::string hello = "Hello from C++";
return env->NewStringUTF(hello.c_str());
}

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

JNIEXPORT jint JNI_OnLoad(JavaVM *vm, void *reserved){
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/javaandso1/MainActivity");
//要注册的方法数组,每个数组含有Java代码中的函数名字,签名,和so中的函数
JNINativeMethod methods[] = {
{"stringFromJNI1","(Ljava/lang/String;I[B)Ljava/lang/String",(void*)xiaojianbang}
}
env -> RegisterNatives(MainActivityClazz, methods, sizeof(methods)/sizeof(JNINativeMethod));
return JNI_VERSION_1_6;
}

多个cpp文件编译成一个so

新建.cpp文件,在CmakeLists.txt加入新建的.cpp,使用前先声明函数

编译多个so

编写多个cpp文件

修改CMkaeLists.txt

Java静态代码块加载多个so

so动态路径的获取

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

so之间的相互调用

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

packagingOptions{
jniLibs{
useLegacyPackaging = true
}
}

MainActivity

public class MainActivity extends AppCompatActivity{

static{
System.loadLibrary("javaandso1");
}

private ActivityMainBinding binding;

@Override
protected void onCreate(Bundle savedInstanceState){
super.onCreate(savedInstanceState);

binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());

TextView tv = binding.sampleText;
//在Java代码中获取库文件路径
String path = getPath(getApplicationContext() + "/libhyq.so");
tv.setText(stringFromJNI(path));
Log.d("xiaojianbang","getSoPath: ",getPath(getApplicationContext()));

public native String stringFromJNI(String a);
public native String stringFromJNI1(String a, int b, byte[] c);

public String getPath(Context cxt){
PackageManager pm = cxt.getPackageManager();
Lsit<PackageInfo> pkgList = pm.getInstalledPackages(0);
Log.d("xiaojianbang","pkList: " + pkgList);
if(pkgList == null || pkgList.size() == 0) return null;
for(PacakgeInfo pi: pkgList){
if(pi.applicationInfo.nativeLibraryDir.startsWith("/data/app")
&&pi.packageName.startsWith("com.example.javanadso1")
){
return pi.applicaitonInfo.nativeLibraryDir;
}
}
return null;
}
}
}

native-lilb.cpp

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

#define TAG "xiaojianabng"
#define LOGD(...) __andorid_log_print(ANDORID_LOG_DEBUG, TAG, __VA_ARGS__);
void test();

extern "C" JNIEXPORT jstring JNICALL
Java_com_example_javaandso1_MainActivity_stringFromJNI(
JNIEnv* env,
jobject, jstring path
){
std::string hello = "Hello from C++";
const char* cPath = env->GetStringUTFChars(path, nullptr);

void *soinfo = dlopen(cpath, RTLD_NOW);
if(soinfo == NULL){
LOGD("dlopen failed: %s", dlerror());
env->ReleaseStringUTFChars(path, cPath);
return env->NewStringUTF("dlopen failed");
}

void (*ref)();
ref = reinterpret_cast<void (*)()>(dlsym(soinfo, "_Z7fromSoBv"));//这串字符从IDA中获得
if(ref == NULL){
LOGD("dlsym failed: %s", dlerror());
dlclose(soinfo);
env->ReleaseStringUTFChars(path, cPath);
return env->NewStringUTF("dlsym failed");
}

ref();
dlclose(soinfo);
env->ReleaseStringUTFChars(path, cPath);
}

demo.cpp

#include <jni.h>
#include <string>
#include <android/log.h>
#define TAG "xiaojianbang"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);

void fromSoB(){
LOGD("libhyq.so fromSoB");
}

若是写成

extern "C" void fromSoB(){

}

在dlsym中可直接传入函数名字

还可以直接调用,在cmakeList中添加链接

# For more information about using CMake with Android Studio, read the
# documentation: https://d.android.com/studio/projects/add-native-code.html.
# For more examples on how to use CMake, see https://github.com/android/ndk-samples.

# Sets the minimum CMake version required for this project.
cmake_minimum_required(VERSION 3.22.1)

# Declares the project name. The project name can be accessed via ${ PROJECT_NAME},
# Since this is the top level CMakeLists.txt, the project name is also accessible
# with ${CMAKE_PROJECT_NAME} (both CMake variables are in-sync within the top level
# build script scope).
project("javaandso1")

# 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.
#
# In this top level CMakeLists.txt, ${CMAKE_PROJECT_NAME} is used to define
# the target library name; in the sub-module's CMakeLists.txt, ${PROJECT_NAME}
# is preferred for the same purpose.
#
# In order to load a library into your app from Java/Kotlin, you must call
# System.loadLibrary() and pass the name of the library defined here;
# for GameActivity/NativeActivity derived applications, the same library name must be
# used in the AndroidManifest.xml file.
add_library(${CMAKE_PROJECT_NAME} SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
native-lib.cpp
main.cpp
)

add_library(hyq SHARED
# List C/C++ source files with relative paths to this CMakeLists.txt.
demo.cpp
)

# Specifies libraries CMake should link to your target library. You
# can link libraries from various origins, such as libraries defined in this
# build script, prebuilt third-party libraries, or Android system libraries.
target_link_libraries(${CMAKE_PROJECT_NAME}
# List libraries link to the target library
hyq
android
log)

target_link_libraries(hyq
# List libraries link to the target library
android
log)

并在顶部声明

extern "C" void fromSoB();

通过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);
}