学习目的:学会ndk编程编写基本c代码
so中常用的log输出
#include<android/log.h>
__android_log__print(int prio, const char* tag, const char* text);
#define LOGD(......) __android_log__print(ANDROID_LOG_DEFAULT, TAG, __VA_ARGS__)
|
NDK多线程
创建多线程
pthread_t thread;
pthread_create(&thread, nullptr, myThread, nullptr);
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 ) { 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 ) { 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 ) { 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 ) {
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");
JNINativeMethod methods[] = { {"stringFromJNI2", "(I[BLjava/lang/String;)Ljava/lang/String;", (void *)encodeFromC}, }; 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");
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); }
|