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每个线程中都有一份
pthread_t thread;pthread_create(&thread, nullptr, myThread, nullptr); 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函数的静态注册 必须遵循一定的命名规则,一般是:
系统会通过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 JNICALLJava_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" ); 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; 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 JNICALLJava_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" )); 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: # For more examples on how to use CMake, see https: # 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" );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" ); jfieldID privateStaticStringField = env->GetStaticFieldID(clazz,"privateStaticStringField" ,"Ljava/lang/String;" ); jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(clazz,privateStaticStringField)); const char * privatestr = env->GetStringUTFChars(privateStaticString,nullptr);LOGD("privateStaticString: %s" ,privatecstr); env->ReleaseStringUTFChars(privateStaticString,privatecstr);
获取对象字段 jclass clazz = env->FindClass("com/example/javaandso1/NDKDemo" );jfieldID publicStringField = env->GetFieldID(clazz,"publicStringField" ,"Ljava/lang/String;" );jstring privateStaticString = static_cast<jstring>(env->GetStaticObjectField(ReflectDemoObj,publicStringField));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数组 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" ); jmethodID publicStaticFuncID = env -> GetStaticMethodID(ReflectDemoClazz, "publicStaticFunc" ,"()V" ); env -> CallStaticVoidMethod(ReflectDemoClazz, publicStaticFuncID);
调用对象方法 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; 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) { 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声明成全局变量
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(){ 、、、 }
4、initarray的使用
__attribute__ ((constructor)) void initArrayTest1 () {、、、} __attribute__ ((constructor)) void initArrayTest2 () {、、、} __attribute__ ((constructor)) void initArrayTest3 () {、、、} __attribute__ ((constructor, visibility("hidden" ))) void initArrayTest4 () {、、、}
OnCreateNative化 extern "C" JNIEXPORT void JNICALL Java_com_example_oncreativenative_MainActivity_onCreate (JNIEnv *env, jobject thiz, jobject saved_instance_state) { jclass FragmentActivityClazz = env->FindClass("androidx/fragment/app/FragmentActivity" ); jmethodID onCreateID = env->GetMethodID(FragmentActivityClazz, "onCreate" , "(Landroid/os/Bundle;)V" ); env->CallNonvirtualVoidMethod(thiz, FragmentActivityClazz, onCreateID, saved_instance_state); jclass ActivityClazz = env->FindClass("android/app/Activity" ); jmethodID getLayoutInflaterID = env->GetMethodID(ActivityClazz, "getLayoutInflater" , "()Landroid/view/LayoutInflater;" ); jobject LayoutInflater = env->CallObjectMethod(thiz, getLayoutInflaterID); jclass ActivityMainBindingClazz = env->FindClass("com/example/oncreativenative/databinding/ActivityMainBinding" ); jmethodID inflateID = env->GetStaticMethodID(ActivityMainBindingClazz,"inflate" ,"(Landroid/view/LayoutInflater;)Lcom/example/oncreativenative/databinding/ActivityMainBinding;" ); jobject ActivitybindingObj= env->CallStaticObjectMethod(ActivityMainBindingClazz, inflateID, LayoutInflater); jmethodID getRootID = env->GetMethodID(ActivityMainBindingClazz, "getRoot" , "()Landroidx/constraintlayout/widget/ConstraintLayout;" ); jobject ConstraintLayout = env->CallObjectMethod(ActivitybindingObj, getRootID); jclass appCompatActivity = env->FindClass("androidx/appcompat/app/AppCompatActivity" ); jmethodID setContentVIewID = env->GetMethodID(appCompatActivity, "setContentView" , "(Landroid/view/View;)V" ); env->CallVoidMethod(thiz, setContentVIewID, ConstraintLayout); jfieldID sampleTextFieldID = env->GetFieldID(ActivityMainBindingClazz, "sampleText" , "Landroid/widget/TextView;" ); jobject tv = env->GetObjectField(ActivitybindingObj, sampleTextFieldID); jstring jstr = Java_com_example_oncreativenative_MainActivity_stringFromJNI(env, thiz); jclass TextViewClazz = env->FindClass("android/widget/TextView" ); jmethodID setTextID = env->GetMethodID(TextViewClazz, "setText" , "(Ljava/lang/CharSequence;)V" ); env->CallVoidMethod(tv, setTextID, jstr); }