最近花了一周时间来入门学习 Android JNI方面的知识,因为后续的工作很多需要用到c c++库,我需要用jni来包装一下c函数,来提供给上次java调用。总之多学点知识对自己有好处。
案例效果:
上文我们讲解了
android studio cmake生成.a文件(静态库)及调用(c c++)静态库.a
本文接着上文,这次我们利用JNI 来引用第三方动态库so文件,并且让Java调用它的函数。
编写C文件和头文件
DynamicLibraryTest.h
//
// Created by Administrator on 2023/9/5/005.
//
#ifndef ANDROIDCMAKE_DYNAMICLIBRARYTEST_H
#define ANDROIDCMAKE_DYNAMICLIBRARYTEST_H
#endif //ANDROIDCMAKE_DYNAMICLIBRARYTEST_H
int 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
#include
#include
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
)
编写cmakeList文件
cmake_minimum_required(VERSION 3.22.1)
project("cmake")
add_library(${CMAKE_PROJECT_NAME} SHARED
native-lib.cpp
src/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 library
android
# 第三方静态库。
calStatic
# 第三方动态库
dynamicLibraryTest
log)
JNI动态注册函数
#include
#include
#include
#include
#include "libtest.h"//内部头文件,cmake需要连接源文件地址
#include "CalculStatic.h"//第三方静态库 .a
#include "DynamicLibraryTest.h"//第三方动态库 .so
#include
#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方法的方法ID
jmethodID 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,"" , "(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");
//拿到构造函数id
jmethodID contructMethod = env->GetMethodID(jclass1,"" ,"()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的构造函数的methodId
jmethodID jmethodID1 = env->GetMethodID(studentClass, "" , "(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值,所以CallBooleanMethod
env->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;
}
}
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;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
// Example of a call to a native method
TextView 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