如何做网站demo,湘潭网站建设 磐石网络实惠,北京视频网站建设,wordpress默认用户名JNI基础知识 JNI简介NDK配置开发环境JNI实践配置CMakeJNI编码JNI注册1.静态注册2.动态注册 编译方式CMakeLists编译Makefile编译命令编译 JNI和C/C代码分离Java调用C/C查看so中包含的方法 C/C调用Java打印C/C的log生成多个共享库soJNI调试 本文整理了JNI技术基础知识 JNI简介
… JNI基础知识 JNI简介NDK配置开发环境JNI实践配置CMakeJNI编码JNI注册1.静态注册2.动态注册 编译方式CMakeLists编译Makefile编译命令编译 JNI和C/C代码分离Java调用C/C查看so中包含的方法 C/C调用Java打印C/C的log生成多个共享库soJNI调试 本文整理了JNI技术基础知识 JNI简介
JNI 是java原生接口(Java Native Interface)它定义了 Android 从受管理代码使用 Java 或 Kotlin 编程语言编写编译的字节码与原生代码使用 C/C 编写进行交互的方法也就是安卓通过JNI技术提供Java调用C/C或者C/C调用Java的能力。JNI 不依赖于供应商支持从动态共享库加载代码虽然有时较为繁琐但效率较高。
NDK
Android NDK(Native Development Kit)原生开发工具包它是一组能将C或C(“原生代码”)嵌入到Android 应用中的工具。可以帮助开发者快速开发C/C的动态库自动将so和java应用一起打包成apk。 NDK集成了一些交叉编译器并提供了相应的mk文件用于隔离CPU、平台、ABI等差异开发人员通过配置mk文件(指出“哪些文件需要编译”、“编译特性要求”等)就可以生成自己的so库。 原生共享库NDK 从 C/C 源代码构建这些库或 .so 文件。so是shared object的缩写。 原生静态库NDK 也可构建静态库或 .a 文件而您可将静态库关联到其他库。
配置开发环境
下载NDK FileSettingsAndroid SDKSDK Tools勾选需要的版本号applyOK配置项目NDK 如果NDK location无法编辑输入可以在local.properties中新增ndk.dir进行设置sdk.dirD\:\\win10_program\\develop\\Android\\AndroidSDK
ndk.dirD\:\\win10_program\\develop\\Android\\AndroidSDK\\ndk\\26.1.10909125 // 已经过时的用法可以直接删除此行配置具体参考下面的操作。较新的项目直接在app\build.gradle中直接配置ndk版本号即可。android {namespace com.xxx.xxxcompileSdk 33ndkVersion 25.2.9519653...
}命令查看ndk版本号ndk-build --version
JNI实践
这里使用官方例子介绍。
配置CMake
配置CMake的目的是告诉CMake改如何从源码编译生成目标库。
# 需要生成的目标库native-lib
# 也可以使用add_executable()生成可执行文件
add_library( # 指定要生成的目标库名称为native-libnative-lib# 将native-lib库设置默认为SHARED(原生共享库.so)或STATIC(原生静态库.a)SHARED# 生成native-lib库所需源码的相对路径列表。包含.cpp和.hsrc/main/cpp/native-lib.cppNativeImpl.cpp) # NativeImpl.cpp在后面代码分离部分实现# 指定源文件关联的头文件适用于头文件和源文件分离的情况但是也可以不写因为.cpp中已经include了
include_directories(src/main/cpp/include/)# 在已有库中查找需要的库并将它的路径存储在变量xxx-lib中。类似用法的函数
# find_file()、find_path()、find_program()、find_package()
find_library( # 自定义变量的名称xxx-libxxx-lib#在ndk开发包中查找需要的libyyy.so存储到xxx-lib变量中yyy ) # 将依赖的库文件链接到此目标库上
target_link_libraries(# 指定目标库native-lib# 将下面的库列表全部连接到目标库上${xxx-lib} # 获取find_library找到的yyy库android # 获取android库log) # 获取log库注意 1、如果对库文件有修改变动请务必在Gradle之前清理一下项目 Build Clean Project。 2、如果需要生成多个共享库可以在CMakeLists.txt文件中增加多个成对的add_library和target_link_libraries函数。
JNI编码
在Java侧声明调用方法。如stringFromJNI // 应用启动时调用此函数会加载原生共享文件sodemo.sostatic {System.loadLibrary(sodemo); //官方推荐使用ReLinker.loadLibrary}/*** 声明此方法在原生端共享文件sodemo.so中实现它与该应用程序打包在一起。*/public native String stringFromJNI();在C侧实现具体方法Java_com_wingtech_sodemo_JNIUtils_stringFromJNI
#include jni.h
#include stringextern C JNIEXPORT jstring JNICALL
Java_com_wingtech_sodemo_JNIUtils_stringFromJNI(JNIEnv *env, jobject thiz) {std::string hello Hello from C;return env-NewStringUTF(hello.c_str());
}方法名称Java_包名_类名_方法名
方法参数JNIEnv* 是指向虚拟机环境的指针。jobject 是指向从 Java 端传递的隐式 this 对象的指针。重要C/C和Java通过此方法名称建立了一对一映射关系。
JNI注册
1.静态注册
如果只有一个类具有原生方法建议使用静态注册。使用标准 System.loadLibrary 从共享库加载原生代码。 从静态类初始化程序中调用 System.loadLibrary或 ReLinker.loadLibrary。具体静态注册同前面JNI编码部分所述。
2.动态注册
如果有多个类有原生方法可以使用RegisterNatives注册也可以让运行时使用dlsym动态查找它们。可以从 Application进行调用这样始终加载该库而且总是会提前加载。当执行到System.loadLibrary()函数时会回调JNI组件中的JNI_OnLoad()函数当释放该组件时会回调JNI_OnUnload()函数。
JNIEXPORT jint JNI_OnLoad(JavaVM* vm, void* reserved) {JNIEnv* env;// 通过调用了GetEnv函数获取JNIEnv结构体指针envJNI环境变量JNIEnv结构体是指向一个函数表的// 该函数表又指向了一些列对应的JNI函数。所以可以通过env和java交互如GetObjectClassCallVoidMethod等if (vm-GetEnv(reinterpret_castvoid**(env), JNI_VERSION_1_6) ! JNI_OK) {return JNI_ERR;}// Find your class. JNI_OnLoad is called from the correct class loader context for this to work.jclass c env-FindClass(com/example/app/package/MyClass);if (c nullptr) return JNI_ERR;// 将所有方法装进数组中。这里数组中每个元素是结构体JNINativeMethod。// typedef struct {// const char* name;//Java层native方法的名字// const char* signature;//Java层native方法的描述符// void* fnPtr;//对应JNI函数的指针// } JNINativeMethod;static const JNINativeMethod methods[] {{nativeFoo, ()V, reinterpret_castvoid*(nativeFoo)},{nativeBar, (Ljava/lang/String;I)Z, reinterpret_castvoid*(nativeBar)},};// 使用RegisterNatives注册所有原生方法int rc env-RegisterNatives(c, methods, sizeof(methods)/sizeof(JNINativeMethod));if (rc ! JNI_OK) return rc;return JNI_VERSION_1_6;
}jni函数的指针 void regist(JNIEnv *env, jobject thiz, jobject jCallback) { LOGD(“–动态注册调用成功–”); jstring pJstring env-NewStringUTF(“动态注册调用成功”); jclass pJclass env-GetObjectClass(thiz); jmethodID id env-GetMethodID(pJclass, “beInjectedDebug”, “(Ljava/lang/String;)V”); //执行函数 env-CallVoidMethod(thiz,id,pJstring); }
编译方式
一般有两种编译方式
1、CMakeLists编译2、Makefile编译3、命令编译
CMakeLists编译
1、CMakeLists配置 具体配置如前面配置CMake的介绍这里使用cpp目录下的CMakeLists.txt、native-lib.cpp文件生成.so库。 2、gradle配置 在app\build.gradle中设置库文件适配的CPU架构类型和CMakeLists.txt 文件路径。
android {namespace com.xxx.sodemocompileSdk 33defaultConfig {applicationId com.xxx.sodemominSdk 31targetSdk 33versionCode 1versionName 1.0testInstrumentationRunner androidx.test.runner.AndroidJUnitRunnerndk {// 生成so库类型abiFilters armeabi-v7a, arm64-v8a,x86,x86_64}}externalNativeBuild {cmake {// 设置CMakeLists.txt文件路径path file(src/main/cpp/CMakeLists.txt)version 3.22.1}}...}3、编译库文件 点击Make按钮或Build-Make Project运行结束后会在 根目录/app/build/intermediates/cmake/debug/obj 路径下生成对应平台的.so库文件。
Makefile编译
使用MK文件编译不需要编辑CMakeLists.txt也不需要在build.gradle中配置只要在Android.mk和Application.mk文件中配置好即可。一般在C/C同目录下创建mk文件。 1、编写Android.mk文件
#设置当前编译路径为当前文件夹路径
LOCAL_PATH :$(call my-dir)#清空编译环境的变量由其他模块设置过的变量
include $(CLEAR_VARS)
LOCAL_LDLIBS : -lm -llog#指定生成模块的名称(库引用名称),编译时会自动添加lib前缀
LOCAL_MODULE :JNITest123#需要编译的源文件。如果存在多个.cpp文件时使用\隔开
LOCAL_SRC_FILES :native-lib.cpp \NativeImpl.cpp # NativeImpl.cpp在后面代码分离部分实现#生成动态库
include $(BUILD_SHARED_LIBRARY)2、编写Application.mk文件
#模块名字与Android.mk中保持一致
APP_MODULES : JNITest123#支持平台这里支持所有平台
APP_ABI : all
APP_ALLOW_MISSING_DEPStrue3、编译库文件 方式一 1检查编译环境 打开cmd窗口运行ndk-build --version如下输出说明ndk配置正确。 2在cmd中进入C/C文件所在目录下执行ndk-build命令编译。
NDK_PROJECT_PATH. # 当前项目
APP_PLATFORMandroid-16 # 有默认值可以不设置
APP_BUILD_SCRIPT./Android.mk # 当前目录下的Android.mk文件。注意:这里根据实际情况修改路径
NDK_APPLICATION_MK./Application.mk # 当前目录下的Application.mk文件。注意:这里根据实际情况修改路径
NDK_LOG1 # 打印编译日志整理成一行命令执行
ndk-build NDK_PROJECT_PATH. APP_BUILD_SCRIPT./Android.mk NDK_APPLICATION_MK./Application.mk NDK_LOG13执行结果 说明在哪个目录下执行ndk-build命令编译就在此目录下生成库文件。
E:\work\Test\Andriod\SoDemo\app\src\mainndk-build NDK_PROJECT_PATH. APP_BUILD_SCRIPT./cpp/Android.mk NDK_APPLICATION_MK./cpp/Application.mk APP_PLATFORMandroid-16
Android NDK: WARNING: APP_PLATFORM android-16 is higher than android:minSdkVersion 1 in ./AndroidManifest.xml. NDK binaries will *not* be compatible with devices older than android-16. See https://android.googlesource.com/platform/ndk//master/docs/user/common_problems.md for more information.
[arm64-v8a] Compile : JNITest123 native-lib.cpp
[arm64-v8a] SharedLibrary : libJNITest123.so
[arm64-v8a] Install : libJNITest123.so libs/arm64-v8a/libJNITest123.so
[x86_64] Compile : JNITest123 native-lib.cpp
[x86_64] SharedLibrary : libJNITest123.so
[x86_64] Install : libJNITest123.so libs/x86_64/libJNITest123.so
[armeabi-v7a] Compile thumb: JNITest123 native-lib.cpp
[armeabi-v7a] SharedLibrary : libJNITest123.so
[armeabi-v7a] Install : libJNITest123.so libs/armeabi-v7a/libJNITest123.so
[x86] Compile : JNITest123 native-lib.cpp
[x86] SharedLibrary : libJNITest123.so
[x86] Install : libJNITest123.so libs/x86/libJNITest123.so方式二在Android Studio中打开终端Terminalcd进入C/C文件所在目录的父目录下执行ndk-build.cmd即可。只要代码没有问题一般可以在同级目录下生成文件。
PS E:\work\Test\Andriod\SoDemo\app\src\main D:\win10_program\develop\Android\AndroidSDK\ndk\26.1.10909125\ndk-build.cmd
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-21.
[arm64-v8a] SharedLibrary : libJNITest123.so
[arm64-v8a] Install : libJNITest123.so libs/arm64-v8a/libJNITest123.so
[x86_64] Compile : JNITest123 native-lib.cpp
[x86_64] SharedLibrary : libJNITest123.so
[x86_64] Install : libJNITest123.so libs/x86_64/libJNITest123.so
[armeabi-v7a] Compile thumb: JNITest123 native-lib.cpp
[armeabi-v7a] SharedLibrary : libJNITest123.so
[armeabi-v7a] Install : libJNITest123.so libs/armeabi-v7a/libJNITest123.so
[x86] Compile : JNITest123 native-lib.cpp
[x86] SharedLibrary : libJNITest123.so
[x86] Install : libJNITest123.so libs/x86/libJNITest123.so常见问题
E:\work\Test\Andriod\SoDemo\app\src\main D:\win10_program\develop\Android\AndroidSDK\ndk\21.0.6113669\ndk-build.cmd
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-16.
Android NDK: Your APP_BUILD_SCRIPT points to an unknown file:
D:\win10_program\develop\Android\AndroidSDK\ndk\21.0.6113669/jni/Android.mk // 这里是路径问题jni
D:/win10_program/develop/Android/AndroidSDK/ndk/21.0.6113669/build//../build/core/add-application.mk:88: *** Android NDK: Aborting... . Stop.因为这里的mk文件实际在src\main\cpp中而NDK编译环境默认在jni目录下找mk文件所以报错无法找到。这里可以将文件名称cpp修改为默认路径jni也可以在ndk-build命令里指定mk的路径具体修改如下。
ndk-build.cmd APP_BUILD_SCRIPT./cpp/Android.mk NDK_APPLICATION_MK./cpp/Application.mkPS E:\work\Test\Andriod\SoDemo\app\src\main D:\win10_program\develop\Android\AndroidSDK\ndk\26.1.10909125\ndk-build.cmd APP_BUILD_SCRIPT./cpp/Android.mk NDK_APPLICATION_MK./cpp/Application.mk
Android NDK: APP_PLATFORM not set. Defaulting to minimum supported version android-21.
[arm64-v8a] Compile : JNITest123 native-lib.cpp
[arm64-v8a] SharedLibrary : libJNITest123.so
...命令编译
JNI和C/C代码分离
分离设计目的是希望在JNI文件中出现少量的C代码。 1、编写.cpp文件 这里以NativeImpl.cpp为例
#include jni.h
#include NativeImpl.hNativeImpl::NativeImpl() {}NativeImpl::~NativeImpl() {}int NativeImpl::Clear_Zero() {LOGD(打印C LOGD);LOGE(打印C LOGE);LOGI(打印C LOGI);LOGW(打印C LOGW);return 0;
}2、编写.h文件 这里以NativeImpl.h为例
#ifndef SODEMO_NATIVEIMPL_H
#define SODEMO_NATIVEIMPL_H
#include LOG.h#include vector
class NativeImpl {public:NativeImpl();virtual ~NativeImpl();virtual int Clear_Zero();
};#endif //SODEMO_NATIVEIMPL_H3、在JNI文件中调用C方法。
#include jni.h
#include NativeImpl.hNativeImpl nativeImpl;
NativeImpl* getNativeImpl(){return nativeImpl;
}extern C
JNIEXPORT jint JNICALL
Java_com_mytest_sodemo_JNIUtils_clrNumber(JNIEnv *env, jclass clazz) {// TODO: implement clrNumber()int zero getNativeImpl()-Clear_Zero();return zero;
}注意代码分离后需要将纯C的源码添加到编译环境中也就是在CMakeLists.txt的add_library方法中添加NativeImpl.cpp或者在Android.mk的LOCAL_SRC_FILES中添加NativeImpl.cpp。具体参考上面的编译方式。
Java调用C/C
Java调用C或C程序前提是给定了C或C的动态库dllWindows或soLinux文件和函数头文件说明这里介绍如何正确调用第三方so。 Java层调用C函数主要通过建立的映射关系这里jni函数调用java层的函数就要通过JNIEnv。 1、将第三方提供的so文件全部放进app\libs目录下然后在app\build.gradle的sourceSets中配置libs这样就会在打包时自动把libs下的文件副本迁移到apk的lib目录下。 当然这里的路径可以自定义只要Gradle在打包时能通过你配置的路径找到so的存放位置即可。
android {defaultConfig {...}sourceSets {main {jniLibs.srcDirs [libs] // 打包时会把app\libs下的共享库.so的副本迁移到apk的lib目录下。// jniLibs.srcDirs [libs/test] // 也可以在app\libs下新建各个公司或模块提供的库目录。// jniLibs.srcDirs [src/mylibs] // 也可以自定义路径so的存储路径只要能找到就行。}}...
}2、根据已知的Java_xx包名_yy类名_方法名格式也可以通过nm命令获取so库的方法在自己的项目中app\src\main\java目录下新建xx包名然后再创建一个和so中的yy类名相同的类这里要确保包名、类名、方法名、库名(不带lib前缀)四者一致最后在自己的项目中直接调用yy类中的方法即可。
extern C JNIEXPORT jstring JNICALL
Java_com_mytest_sodemo_JNIUtils_stringFromJNI(JNIEnv *env, jobject) {std::string hello Hello from C;return env-NewStringUTF(hello.c_str());
}注意这一步非常重要通过如此设计的方法名建立了java和C之间的映射关系所以在其他应用中使用时也需要建立这种映射关系否则报错UnsatisfiedLinkError。 //todo 缺图 3、清理和检查 清理后重新打包BuildClean Project等一会儿Make Project或者Build APK(s)等一会儿。 检查so是否打入包内BuildAnalyze APKOK打开目标APK的lib目录。 4、常见异常 如果检查没有发现错误编译运行后还是出现UnsatisfiedLinkError异常多半是因为apk中的so没有打入包内请按照第3步处理.
查看so中包含的方法
需要使用nm工具一般在sdk\ndk\xx版本\toolchains\x86-4.9\prebuilt\windows-x86_64\i686-linux-android\bin
nm -D so文件路径 C/C调用Java
打印C/C的log
在CPP目录下新建head文件LOG.h
#ifndef SODEMO_LOG_H
#define SODEMO_LOG_H#include android/log.h#define TAG haitao
#define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, TAG, __VA_ARGS__);
#define LOGW(...) __android_log_print(ANDROID_LOG_WARN, TAG, __VA_ARGS__);
#define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, TAG, __VA_ARGS__);
#define LOGI(...) __android_log_print(ANDROID_LOG_INFO, TAG, __VA_ARGS__);#endif //SODEMO_LOG_H在需要使用的文件中#include LOG.h即可。
生成多个共享库so
如果需要生成多个共享库可以在CMakeLists.txt再增加add_library和target_link_libraries 参考配置CMake
# 生成libtest-1.so
add_library(test-1SHAREDnative-lib.cpp)
target_link_libraries(test-2 android)
...# 生成libtest-2.so
add_library(test-2 SHAREDnative-lib.cpp)
target_link_libraries(test-2android)JNI调试 在Debug模式下有时候会出现这个Permission denied的提示。 解决方法退出App重新debug运行。如果退出无法解决此问题重新USB连接即可。 与君共勉人生自当扶摇上揽星衔月逐日光。你只管去劈浪与众生争锋芒你举步是八万里宽广你眼望是千江拍白浪你平生这一趟要让旁人想都不敢想