| |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
-> 移动开发 -> Android - JNI 开发你所需要知道的基础,Android工程师面试题 -> 正文阅读 |
|
[移动开发]Android - JNI 开发你所需要知道的基础,Android工程师面试题 |
// 2. 获取成员变量 id // 4. 修改 String // 5. 释放资源 // 获取静态变量 GetFieldID 和 GetStaticFieldID 需要三个参数:
类型签名一览表
操作 method操作 method 和 filed 非常相似,先获取 MethodID,然后对应的 CallXXXMethod 方法
在 java 中我们要想获取 MainActivity 的 className 会这样写: this.getClass().getName() 可以看到需要先调用 getClass 方法获取 Class 对象,然后调用 Class 对象的 getName 方法,我们来看一下如何在 native 方法中调用: extern “C” JNIEXPORT jstring JNICALL // 7. 释放资源 return env->NewStringUTF(hello.c_str()); 创建对象首先定义一个 java 类: public class Person { public Person(int age, String name){ public void print(){ 然后我们再 JNI 中创建一个 Person 并调用它的 print 方法: // 1. 获取 Class jmethodID printId = env->GetMethodID(pClazz,“print”,"()V"); // 4. 释放资源 JNI 引用JNI 分为三种引用:
上面的代码片段中最后都会有释放资源的代码,这是 c/c++ 编程的良好习惯,对于不同 JNI 引用有不同的释放方式。 局部引用创建JNI 函数返回的所有 Java 对象都是局部引用,比如上面调用的 NewObject/FindClass/NewStringUTF 等等都是局部引用。 释放
手动释放的场景有了自动释放之后为什么还需要手动释放呢?主要考虑一下场景:
所以我们应该养成手动释放本地引用的好习惯。 手动释放的方式
全局引用创建JNI 允许程序员从局部引用创建全局引用: static jstring globalStr; //局部可以释放,因为有了一个全局引用使用str,局部str也不会使用了 释放全局引用在显式释放之前保持有效,可以通过 DeleteGlobalRef 来手动删除全局引用调用。 弱全局引用与全局引用类似,弱引用可以跨方法、线程使用。与全局引用不同的是,弱引用不会阻止GC回收它所指向的VM内部的对象
创建static jclass globalClazz = NULL; 释放删除使用 DeleteWeakGlobalRef 线程相关局部变量只能在当前线程使用,而全局引用可以跨方法、跨线程使用,直到它被手动释放才会失效。 加载动态库在 android 中有两种方式加载动态库:
比如下面代码会报错,在 java.library.path 下找不到 hello static{ [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-0HR5wu7m-1637978792238)(https://user-gold-cdn.xitu.io/2020/6/16/172bcae4b92c0365?imageView2/0/w/1280/h/960/ignore-error/1)] 可以使用下面代码打印出 java.library.path ,并且吧 hello 拷贝到改路径下: public static void main(String[] args){ JNI_OnLoad调用System.loadLibrary()函数时, 内部就会去查找so中的 JNI_OnLoad 函数,如果存在此函数则调用。 JNI_OnLoad 必须返回 JNI 的版本,比如 JNI_VERSION_1_6、JNI_VERSION_1_8。 动态注册JNI 匹配对应的 java 方法有两种方式:
静态注册的名字需要包名,太长了,可以使用动态注册来缩短方法名。 比如我们再 Java 中有两个 native 方法: public class MainActivity extends AppCompatActivity { public native int dynamicJavaFunc2(int i); 在 native 代码中,我们不使用静态注册,而使用动态注册 void dynamicNativeFunc1(){ // 需要动态注册的方法数组 jint JNI_OnLoad(JavaVM* vm, void* reserved){
int result = vm->GetEnv(reinterpret_cast<void **>(&env), JNI_VERSION_1_6); if(result != JNI_OK){ return JNI_VERSION_1_6; 这样我们再 MainActivity 中调用 dynamicJavaFunc1 方法就会调用 native 中的 dynamicNativeFunc1 方法。 native 线程中调用 JNIEnv*前面介绍过 JNIEnv* 是和线程相关的,那么如果在 c++ 中新建一个线程A,在线程A 中可以直接使用 JNIEnv* 吗? 答案是否定的,如果想在 native 线程中使用 JNIEnv* 需要使用 JVM 的 AttachCurrentThread 方法进行绑定: JavaVM *_vm; jint JNI_OnLoad(JavaVM* vm, void* reserved){ void* threadTask(void* args){ // … // 线程 task 执行完后不要忘记分离 extern “C” 交叉编译在一个平台上编译出另一个平台上可以执行的二级制文件的过程叫做交叉编译。比如在 MacOS 上编译出 android 上可用的库文件。 如果想要编译出可以在 android 平台上运行的库文件就需要使用 ndk。 两种库文件linux 平台上的库文件分为两种:
Android 原生开发套件 (NDK):这套工具使您能在 Android 应用中使用 C 和 C++ 代码。 CMake:一款外部编译工具,可与 Gradle 搭配使用来编译原生库。如果您只计划使用 ndk-build,则不需要此组件。 LLDB:Android Studio 用于调试原生代码的调试程序。 NDK原生开发套件 (NDK) 是一套工具,使您能够在 Android 应用中使用 C 和 C++ 代码,并提供众多平台库。 我们可以在 sdk/ndk-bundle 中查看 ndk 的目录结构,下面列举出三个重要的成员:
使用 ndk 手动编译动态库在 ndk 目录下的 toolchains 下有多个平台的编译工具,比如在 /arm-linux-androideabi-4.9/prebuilt/darwin-x86_64/bin 下可以找到 arm-linux-androideabi-gcc 执行文件,利用 ndk 的这个 gcc 可以编译出在 android(arm 架构) 上运行的动态库: arm-linux-androideabi-gcc -fPIC -shared test.c -o libtest.so
当源文件很多的时候,手动编译既麻烦又容易出错,此时出现了 makefile 编译。 makefilemakefile 就是“自动化编译”:一个工程中的源文件不计数,其按类型、功能、模块分别放在若干个目录中,makefile定义了一系列的规则来指定,哪些文件需要先编译,哪些文件需要后编译,如何进行链接等等操作。 Android 使用 Android.mk 文件来配置 makefile,下面是一个最简单的 Android.mk: 源文件在的位置。宏函数 my-dir 返回当前目录(包含 Android.mk 文件本身的目录)的路径。LOCAL_PATH := $(call my-dir) 引入其他makefile文件。CLEAR_VARS 变量指向特殊 GNU Makefile,可为您清除许多 LOCAL_XXX 变量不会清理 LOCAL_PATH 变量include $(CLEAR_VARS) 指定库名称,如果模块名称的开头已是 lib,则构建系统不会附加额外的前缀 lib;而是按原样采用模块名称,并添加 .so 扩展名。LOCAL_MODULE := hello 包含要构建到模块中的 C 和/或 C++ 源文件列表 以空格分开LOCAL_SRC_FILES := hello.c 构建动态库include $(BUILD_SHARED_LIBRARY) 我们配置好了 Android.mk 文件后如何告诉编译器这是我们的配置文件呢? 这时候需要在 app/build.gradle 文件中进行相关的配置: apply plugin: ‘com.android.application’ android { defaultConfig { … dependencies { Google 推荐开发者使用 cmake 来代替 makefile 进行交叉编译了,makefile 在引入第三方预编译好的 so 的时候会在 android 6.0 版本前后有些差异,比如在 6.0 之前需要手动 System.loadLibrary 第三方 so,在之后则不需要。 关于 makefile 还有很多配置参数,这里不在讲解,更多参考官方文档。
cmakeCMake是一个跨平台的构建工具,可以用简单的语句来描述所有平台的安装(编译过程)。能够输出各种各样的makefile或者project文件。Cmake 并不直接建构出最终的软件,而是产生其他工具的脚本(如Makefile ),然后再依这个工具的构建方式使用。 Android Studio利用CMake生成的是ninja,ninja是一个小型的关注速度的构建系统。我们不需要关心ninja的脚本,知道怎么配置cmake就可以了。 CMakeLists.txtMake的脚本名默认是CMakeLists.txt,当我们用 android studio new project 勾选 include c/c++ 的时候,会默认生成以下文件: |- app |-- src |— main |---- cpp |----- CMakeLists.txt |----- native-lib.cpp 先来看一下 CMakeLists.txt: 设置 cmake 最小支持版本cmake_minimum_required(VERSION 3.4.1) 创建一个库add_library( # 库名称,比如现在会生成 native-lib.so 设置是动态库(SHARED)还是静态库(STATIC)SHARED 设置源文件的相对路径native-lib.cpp ) 搜索并指定预构建库并将路径存储为变量。NDK中已经有一部分预构建库(比如 log),并且ndk库已经是被配置为cmake搜索路径的一部分可以不写 直接在 target_link_libraries 写上logfind_library( # 设置路径变量的名称 指定要CMake定位的NDK库的名称log ) 指定CMake应链接到目标库的库。你可以链接多个库,例如构建脚本、预构建的第三方库或系统库。target_link_libraries( # Specifies the target library. 我们再来看下 gradle 中的配置: android { 这样在编译产物中就可以看到两个版本的 so: [外链图片转存失败,源站可能有防盗链机制,建议将图片保存下来直接上传(img-k3umOWWU-1637978792295)(https://user-gold-cdn.xitu.io/2020/6/16/172bcb4b3dd44795?imageView2/0/w/1280/h/960/ignore-error/1)] 添加多个源文件比如我们添加一个 extra.h: #ifndef JNITUTORIAL_EXTRA_H 然后在 native-lib.cpp 中使用: #include <jni.h> extern “C” JNIEXPORT jstring JNICALL 源文件已经写好了,这时候要修改一下 CMakeLists.txt: add_library( #================================== 当然如果源文件非常多,并且可能在不同的文件夹下,像上面明确的引入各个文件就会非常繁琐,此时可以批量引入如果文件太多,可以批量加载,下面时将 cpp 文件夹下所有的源文件定义成了 SOURCE(后面的源文件使用相对路径)file(GLOB SOURCE *.cpp *.h) add_library( 引入 SOURCE 下的所有源文件${SOURCE} 添加第三方动态库那么如何添加第三方的动态库呢? 第三方库的存放位置动态库必须放到 src/main/jniLibs/xxabi 目录下才能被打包到 apk 中,这里用的是虚拟机,所以用的是 x86 平台,所以我们放置一个第三方库 libexternal.so 到 src/main/jniLibs/x86 下面。 libexternal.so 中只有一个 hello.c ,里面只有一个方法: const char * getExternalString(){ CMakeLists.txt 的位置这里将 CMakeLists.txt 重新放到了 app 目录下,和 src 同级,这样方便找到 jniLibs 下面的库。 所以别忘了修改 gradle externalNativeBuild { 配置 CMakeLists.txtcmake_minimum_required(VERSION 3.4.1) 如果文件太多,可以批量加载,下面时将 cpp 文件夹下所有的源文件定义成了 SOURCE(后面的源文件使用相对路径)file(GLOB SOURCE src/main/cpp/.cpp src/main/cpp/.h) add_library( 引入 SOURCE 下的所有源文件${SOURCE} #add_library( # Sets the name of the library. native-lib# Sets the library as a shared library.SHARED# Provides a relative path to your source file(s).native-lib.cppextra.h )find_library( 引入外部 so=message(“ANDROID_ABI : ${ANDROID_ABI}”) external 代表第三方 so - libexternal.soSHARED 代表动态库,静态库是 STATIC;IMPORTED: 表示是以导入的形式添加进来(预编译库)add_library(external SHARED IMPORTED) #设置 external 的 导入路径(IMPORTED_LOCATION) 属性,不可以使用相对路径 CMAKE_SOURCE_DIR: 当前cmakelists.txt的路径 (cmake工具内置的)android cmake 内置的 ANDROID_ABI : 当前需要编译的cpu架构set_target_properties(external PROPERTIES IMPORTED_LOCATION ${CMAKE_SOURCE_DIR}/src/main/jniLibs/x86/libexternal.so) 引入外部 so end=target_link_libraries( # Specifies the target library. Links the target library to the log libraryincluded in the NDK.${log-lib} 链接第三方 soexternal 使用第三方库#include <jni.h> extern “C”{ extern “C” JNIEXPORT jstring JNICALL target_link_libraries( # Specifies the target library. Links the target library to the log libraryincluded in the NDK.${log-lib} 链接第三方 soexternal 使用第三方库#include <jni.h> extern “C”{ extern “C” JNIEXPORT jstring JNICALL |
|
移动开发 最新文章 |
Vue3装载axios和element-ui |
android adb cmd |
【xcode】Xcode常用快捷键与技巧 |
Android开发中的线程池使用 |
Java 和 Android 的 Base64 |
Android 测试文字编码格式 |
微信小程序支付 |
安卓权限记录 |
知乎之自动养号 |
【Android Jetpack】DataStore |
|
上一篇文章 下一篇文章 查看所有文章 |
|
开发:
C++知识库
Java知识库
JavaScript
Python
PHP知识库
人工智能
区块链
大数据
移动开发
嵌入式
开发工具
数据结构与算法
开发测试
游戏开发
网络协议
系统运维
教程: HTML教程 CSS教程 JavaScript教程 Go语言教程 JQuery教程 VUE教程 VUE3教程 Bootstrap教程 SQL数据库教程 C语言教程 C++教程 Java教程 Python教程 Python3教程 C#教程 数码: 电脑 笔记本 显卡 显示器 固态硬盘 硬盘 耳机 手机 iphone vivo oppo 小米 华为 单反 装机 图拉丁 |
360图书馆 购物 三丰科技 阅读网 日历 万年历 2024年11日历 | -2024/11/24 5:59:07- |
|
网站联系: qq:121756557 email:121756557@qq.com IT数码 |