最近花了一周时间来入门学习 Android JNI方面的知识,因为后续的工作很多需要用到c c++库,我需要用jni来包装一下c函数,来提供给上次java调用。总之多学点知识对自己有好处。
案例效果:
上文我们讲解了
android studio cmake生成.a文件(静态库)及调用(c c++)静态库.a
本文接着上文,这次我们利用JNI 来引用第三方动态库so文件,并且让Java调用它的函数。
第一步:生成.so动态库文件
编写C文件和头文件
DynamicLibraryTest.h
//
// Created by Administrator on 2023/9/5/005.
//#ifndef ANDROIDCMAKE_DYNAMICLIBRARYTEST_H
#define ANDROIDCMAKE_DYNAMICLIBRARYTEST_H#endif //ANDROIDCMAKE_DYNAMICLIBRARYTEST_Hint dynamicAdd(int a,int b,int c);char * getDynamicName(char * firstName,char * lastName);
DynaminLibraryTest.cpp
//
// Created by Administrator on 2023/9/5/005.
//#include "../include/static/DynamicLibraryTest.h"
#include <stdio.h>
#include <stdlib.h>
#include <string.h>int dynamicAdd(int a,int b,int c){return a+b+c;
}char * getDynamicName(char * firstName,char * lastName){char *name = (char *) malloc(strlen(firstName) + strlen(lastName));strcpy(name, firstName); // 把firstName复制到name中strcat(name, lastName); // 把lastName追加到name中return name;
}
编写CmakeLists文件
add_library(${CMAKE_PROJECT_NAME} SHARED src/DynaminLibraryTest.cpp )
make project之后,生成了.so动态库文件。
生成动态库之后,我把文件复制到了jni目录下面.
第二步:JNI动态注册.so动态库中的函数
编写cmakeList文件
cmake_minimum_required(VERSION 3.22.1)project("cmake")add_library(${CMAKE_PROJECT_NAME} SHAREDnative-lib.cppsrc/libtest.c# 编写动态库用到的文件,已经生成了deynaminLibraryTest.so文件就注释掉这个代码
# src/DynaminLibraryTest.cpp )#导入已经编译好的第三方静态库 或者 动态库 本例导入的静态库
add_library(calStatic STATIC IMPORTED)
#设置静态库(.a)导入的路径
set_target_properties(calStatic PROPERTIES IMPORTED_LOCATION${CMAKE_CURRENT_SOURCE_DIR}/jni/${CMAKE_ANDROID_ARCH_ABI}/libcalStatic.a
)#添加第三方 动态库
add_library(dynamicLibraryTest SHARED IMPORTED)
#设置动态库(.so)导入的路径
set_target_properties(dynamicLibraryTest PROPERTIES IMPORTED_LOCATION${CMAKE_CURRENT_SOURCE_DIR}/jni/${CMAKE_ANDROID_ARCH_ABI}/libdynamicLibraryTest.so)#通过target_link_libraries命令指明库文件,且通过target_include_directories命令指明相应的库头文件
target_include_directories(${CMAKE_PROJECT_NAME} PUBLIC ${PROJECT_SOURCE_DIR}/include/static/)
#需要链接或者编译的库
target_link_libraries(${CMAKE_PROJECT_NAME}# List libraries link to the target libraryandroid# 第三方静态库。calStatic# 第三方动态库dynamicLibraryTestlog)
JNI动态注册函数
#include <jni.h>
#include <string>
#include <stdlib.h>
#include <stdio.h>#include "libtest.h"//内部头文件,cmake需要连接源文件地址
#include "CalculStatic.h"//第三方静态库 .a
#include "DynamicLibraryTest.h"//第三方动态库 .so#include <android/log.h>
#define LOG_TAG "YIQI"
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__)
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, LOG_TAG, __VA_ARGS__)
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__)//静态注册和动态注册,函数名一样的时候,优先用动态注册的,动态注册效率高。
//静态注册
//extern "C" JNIEXPORT jstring JNICALL
//Java_com_gitbaike_cmake_MainActivity_stringFromJNI(
// JNIEnv* env,
// jobject /* this */) {
// std::string hello = "Hello from C++ 静态注册";
// return env->NewStringUTF(hello.c_str());
//}//静态注册
extern "C" JNIEXPORT void JNICALL
Java_com_gitbaike_cmake_MainActivity_HNPCInit( JNIEnv* env,jobject /* this */){HNPC_Init();
}//动态注册
extern "C" {//调用了libcalStatic.a静态库中的方法jint addSum(JNIEnv *env,jobject instance,jint a,jint b,jint c){ //调用静态库return calculAdd(a,b,c);}//调用了libDynamicLibraryTest.so动态库中的方法jint jDynamicAdd(JNIEnv *env,jobject instance,jint a,jint b,jint c){return dynamicAdd(a,b,c);}//调用了libDynamicLibraryTest.so动态库中的方法jstring jGetDynamicName(JNIEnv *env,jobject instance,jstring firstName,jstring lastName){char * name= getDynamicName((char*)env->GetStringUTFChars(firstName,0),(char*)env->GetStringUTFChars(lastName,0));return env->NewStringUTF(name);;}//入门程序 hello form c++
jstring stringFromJNI(JNIEnv *env, jobject instance) {std::string hello = "Hello from C++ 动态注册";return env->NewStringUTF(hello.c_str());
}//内部自定义c函数的调用,非第三方库
jint add(JNIEnv *env, jobject clazz, jint a, jint b) {return a + b;
}/*** Native层,改变Java层对象的属性*/
void changeName(JNIEnv *env, jobject clazz, jobject person){//获取person class对象//FindClass("com/gitbaike/cmake/model/Person"); 这里获取的是class类,不是对象jclass personObject=env->GetObjectClass(person);//这里获取的是对象//获取setName方法的方法IDjmethodID setNameId=env->GetMethodID(personObject,"setName", "(Ljava/lang/String;)V");char *setNewName="Native 层赋予你的新名字:";jstring str=env->NewStringUTF(setNewName);//这里注意,第一个参数需要是jobject对象,所以传入的是person,而不是personObject(jclass对象)env->CallVoidMethod(person,setNameId,str);
}/*** Native层,返回一个新的Person对象*/
jobject getNewPerson(JNIEnv *env,jobject clazz){jclass newPerson= env->FindClass("com/gitbaike/cmake/model/Person");jmethodID personMethod=env->GetMethodID(newPerson,"<init>", "(ILjava/lang/String;)V");jint age=88;char * name="gitbaike";jstring jName=env->NewStringUTF(name);jobject person=env->NewObject(newPerson,personMethod,age,jName);return person;
}/*** Native层,返回一个新的Person对象集合* 返回java层一个list*/
jobject getListPerson(JNIEnv *env,jobject clazz){
//因为list是无法实例对象,找到Arraylist,返回class对象jclass jclass1 = env->FindClass("java/util/ArrayList");//拿到构造函数idjmethodID contructMethod = env->GetMethodID(jclass1,"<init>","()V");//生成一个Arraylist对象,就是我们要返回的对象jobject list = env->NewObject(jclass1,contructMethod);//拿到 list的 add方法的methodId,准备往method添加几个数据jmethodID methodAdd = env->GetMethodID(jclass1,"add","(Ljava/lang/Object;)Z");//拿到Person的class对象jclass studentClass = env->FindClass("com/gitbaike/cmake/model/Person");//拿到person的构造函数的methodIdjmethodID jmethodID1 = env->GetMethodID(studentClass, "<init>", "(ILjava/lang/String;)V");for(int i =1;i<=4;i++){char * name="tl";char newName[50]="";
// std::to_string(i)
// char * iStr=( char *)i;strcat (newName, name);char * is=reinterpret_cast<char*>(&i);strcat (newName, is);jobject person = env->NewObject(studentClass,jmethodID1,i,env->NewStringUTF(newName));//调用 list的add方法,因为返回时boolean值,所以CallBooleanMethodenv->CallBooleanMethod(list,methodAdd,person);}return list;
}//动态注册
jint RegisterNatives(JNIEnv *env) {jclass clazz = env->FindClass("com/gitbaike/cmake/MainActivity");if (clazz == NULL) {LOGE("con't find class: com/gitbaike/cmake/MainActivity");return JNI_ERR;}JNINativeMethod methods_MainActivity[] = {{"stringFromJNI", "()Ljava/lang/String;", (void *) stringFromJNI},{"add", "(II)I", (void *) add},{"changePersonName", "(Lcom/gitbaike/cmake/model/Person;)V",(void *) changeName},{"getPerson", "()Lcom/gitbaike/cmake/model/Person;",(void *)getNewPerson},{"getPeronList", "()Ljava/util/List;",(void *) getListPerson},{"addSum", "(III)I",(void *)addSum},{"dynamicAdd", "(III)I",(void *) jDynamicAdd},{"getDynamicName", "(Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String;",(void *) jGetDynamicName}};// int len = sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]);return env->RegisterNatives(clazz, methods_MainActivity,sizeof(methods_MainActivity) / sizeof(methods_MainActivity[0]));
}jint JNI_OnLoad(JavaVM *vm, void *reserved) {JNIEnv *env = NULL;if (vm->GetEnv((void **) &env, JNI_VERSION_1_6) != JNI_OK) {return JNI_ERR;}jint result = RegisterNatives(env);LOGD("RegisterNatives result: %d", result);return JNI_VERSION_1_6;
}}
第三步:Android Java层调用JNI native函数
java调用:
package com.gitbaike.cmake;import android.os.Bundle;
import android.util.Log;
import android.widget.TextView;import com.gitbaike.cmake.databinding.ActivityMainBinding;
import com.gitbaike.cmake.model.Person;
import com.google.gson.Gson;import java.util.List;import androidx.appcompat.app.AppCompatActivity;public class MainActivity extends AppCompatActivity {private static final String TAG = "MainActivity";// Used to load the 'cmake' library on application startup.static {System.loadLibrary("cmake");}private ActivityMainBinding binding;@Overrideprotected void onCreate(Bundle savedInstanceState) {super.onCreate(savedInstanceState);binding = ActivityMainBinding.inflate(getLayoutInflater());setContentView(binding.getRoot());// Example of a call to a native methodTextView tv = binding.sampleText;tv.setText(stringFromJNI());HNPCInit();Person person=new Person();person.setAge(12);person.setName("java person");Log.d(TAG,"调用本地方法前: person.getName:"+person.getName());changePersonName(person);Log.d(TAG,"调用本地方法后: person.getName:"+person.getName());Person nPerson=getPerson();Log.d(TAG,"调用本地方法 getPerson()后: person:"+nPerson.toString());List<Person> personList=getPeronList();Log.d(TAG,"调用本地方法 getPerson()后: personList:"+new Gson().toJson(personList) );Log.d(TAG,"调用本地方法 (引用第三方静态库.a)addSum:"+addSum(10,15,65));Log.d(TAG,"调用本地方法 (引用第三方动态库.so)dynamicAdd:"+dynamicAdd(100,200,300));Log.d(TAG,"调用本地方法 (引用第三方动态库.so)getDynamicName:"+getDynamicName("hello"," https://nav.vpssw.com --->程序员网址导航<---"));}/*** A native method that is implemented by the 'cmake' native library,* which is packaged with this application.*/public native String stringFromJNI();public native void HNPCInit();public native int add(int a,int b);public native void changePersonName(Person mPerson);public native Person getPerson();public native List<Person> getPeronList();public native int addSum(int a,int b,int c);public native int dynamicAdd(int a,int b,int c);public native String getDynamicName(String firstName,String lastName);}
源码已经上传:https://download.csdn.net/download/qingfeng812/88301421
android studio cmake生成.a文件(静态库)及调用(c c++)静态库.a
android studio cmake生成so文件(动态库)及调用(c c++)动态库.so
java调用c函数 c函数调用java
jni静态注册与动态注册。
代码都是我跑通过的。
运行环境
as版本:Android Studio Giraffe | 2022.3.1 Patch 1
android AGP 8.1.1
gradle 版本8.0
jdk 17