IT数码 购物 网址 头条 软件 日历 阅读 图书馆
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
图片批量下载器
↓批量下载图片,美女图库↓
图片自动播放器
↓图片自动播放器↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
开发: 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及NDK入门教程 -> 正文阅读

[移动开发]Android JNI及NDK入门教程

Android JNI及NDK入门教程

为什么要使用JNI

  • 一、native语言性能更优。
  • 二、在Java诞生前很多库都是native语言写的,没必要再用Java实现一遍。
  • 三、安全性更好:不容易被反编译。

JNI是什么

  • 全称Java Native Interface。字面上理解就是Java和本地语言的接口。定义了Java和native语言之间互相通信的一套规范。(这是Java定义的,和安卓无关。)
  • NDK是将native语言编译成特定平台的可执行文件的一套开发组件。(将C/C++编译成so库,由Android定义的,和Java无关。)

所以jni和ndk是两个东西,ndk生成可执行so文件,jni通过加载so供java调用。如果有so文件,可以直接使用。

3、Native项目的创建

3.1、创建Native项目

cmake脚本文件定义编译和链接方式。

3.2、Native项目结构

image-20210820162107135

之前的AS版本需要在local.properties文件指定ndk路径:ndk.dir=xxx。新版AS会自动合适的ndk版本,如果本地没有会推荐一个版本让你下载。

3.3、在Java中声明本地方法。

  • 一般我们有一个专门的访问native方法的Java类。比如本项目的NativeBridge.java。
public class NativeBridge {
    static {
        System.loadLibrary("native-lib");
    }

    // 本地方法1 静态注册。
    public static native String dataFromNative(int param);

    // 本地方法2 动态注册。
    public static native String nativeMethod1(int param);

    /// 本地方法3 动态注册。
    public static native String nativeMethod2(int param);

}

声明后需要在jni层进行注册和实现。注册native方法分为静态注册和动态注册。

4、Native方法的注册和实现。

4.1、Native方法的静态注册和动态注册的对比。

  • 静态注册写起来简单,可以AS快捷键一键生成。但运行效率低(第一次调用native方法时需要搜索一遍jni层的native方法,建立对应关系。)
  • 动态注册运行效率高(因为方法的映射关系我们已建立好)、写起来相对麻烦一点点。

4.2、静态注册

extern "C" JNIEXPORT jstring JNICALL
Java_com_hongenit_jnindkdemo_NativeBridge_dataFromNative(JNIEnv *env, jclass clazz, jint param) {
    std::string hello = "静态方法返回的Native字符串";
    return env->NewStringUTF(hello.c_str());
}

4.3、动态注册

  1. 准备好要注册的方法的JNINativeMethod结构体数组。
    // JNI本地方法的数组。
    static JNINativeMethod gMethods[] = {
            {"nativeMethod1", "(I)Ljava/lang/String;", (void*)nativeMethod1},
            {"nativeMethod2", "(I)Ljava/lang/String;", (void*)nativeMethod1},
    };
    

    JNINativeMethod结构体的

    java数据类型native 类型域描述符
    基本数据类型
    booleanjbooleanZ
    bytejbyteB
    charjcharC
    shortjshortS
    intjintI
    longjlongJ
    floatjfloatF
    doublejdoubleD
    voidvoidV
    对象引用类型以”L”开头,以”;”结尾,用”/” 隔开的包及类名,如果内部类则使用$连接内部类;
    SurfacejobjectLandroid/view/Surface;
    StringjstringLjava/lang/String;
    数组数据类型对应的基本数据类型前面加个中括号[
    int[]jintArray[I
    float[]jfloatArray[f
    Surface[]jobjectArray[Landroid/view/Surface;
  2. 覆写JNI_OnLoad方法。
    extern "C" jint JNI_OnLoad(JavaVM* vm, void* reserved)
    {
        JNIEnv* env = NULL;
        jint result = -1;
        if (vm->GetEnv((void**) &env, JNI_VERSION_1_6) != JNI_OK) {
            return result;
        }
        // 动态注册native方法。
        register_gmethods(vm, env);
        return JNI_VERSION_1_6;
    }
    
  3. 在JNI_OnLoad中调用RegisterNatives方法注册native方法。
    void register_gmethods(JavaVM *pVm, JNIEnv *pEnv) {
        static const char* const jclassName = "com/hongenit/jnindkdemo/NativeBridge";
        jclass jclazz = pEnv->FindClass(jclassName);
        pEnv->RegisterNatives(jclazz,gMethods,int(sizeof(gMethods)/sizeof(gMethods[0])));
    }
    

    生成的so在build目录下。

4.4、Native方法的实现。

5、cmake编译脚本

# Sets the minimum version of CMake required to build the native library.
cmake_minimum_required(VERSION 3.10.2)

project("jnindkdemo")

# 命名并创建一个动态库(与静态库的区别是不被编译进目标内部),并指定其源码文件。可以定义多个库,CMake会为你编译好。gradle会自动将so库打包到apk中。
add_library(native-lib SHARED native-lib.cpp)


# 从NDK的系统库中搜索某个库并取个别名,通过这个别名可以定位到这个系统库的路径。
find_library(log-lib log)

# 指定哪些库需要Cmake链接到你的目标库。可以链接多个库,比如你在脚本中定义了的库、预编译好的三方库、系统库。
target_link_libraries(
        native-lib
        play gnustl_shared
        ${log-lib}
)

5.1、使用三方库

  • 假设需要用三方so库,在链接到目标库之前先添加定义的库。
#---------------------------三方so库----------------
#添加头文件路径(相对于本文件路径)
include_directories(include)

#设置so库所在路径的变量
set(SO_PATH ${CMAKE_CURRENT_SOURCE_DIR}/libs/${ANDROID_ABI})  #CMAKE_CURRENT_SOURCE_DIR变量为当前cmake文件所在的目录

add_library(gnustl_shared SHARED IMPORTED)
set_target_properties(gnustl_shared PROPERTIES IMPORTED_LOCATION ${SO_PATH}/libgnustl_shared.so)

add_library(play SHARED IMPORTED)
set_target_properties(play PROPERTIES IMPORTED_LOCATION ${SO_PATH}/libplay.so)

如果三方库A(play)需要依赖另一个三方库B,也必须将B(gnustl_shared)链接到目标库。否则会报错如下:

java.lang.UnsatisfiedLinkError: dlopen failed: library "libgnustl_shared.so" not found: needed by /data/app//lib/arm64/libplay.so in namespace。。。
  • 如下表示头文件在include目录下,所以应将so库的头文件放入include目录,然后可以在目标程序中引入头文件并调用方法。
#添加头文件路径(相对于本文件路径)
include_directories(include)

eg. 调用该so库的获取版本号方法

#include <dhplay.h>
...
jstring nativeMethod1(JNIEnv *env, jclass clazz, jint param) {
	...
	unsigned version = PLAY_GetSdkVersion();
	...
}

6、调用Native方法

一般在一个专门访问native方法的类中,在类的静态代码块中用System.loadLibrary加载so库。调用这些声明在Java层Native方法即执行Native对应的实现。

public class NativeBridge {
    static {
        System.loadLibrary("native-lib");
    }

    // 静态注册本地方法。
    public static native String dataFromNative(int param);

    // 动态注册本地方法1 。
    public static native String nativeMethod1(int param);

    /// 动态注册本地方法2 。
    public static native String nativeMethod2(int param);

}

7、Native调用Java方法。

7.1、定义被调用的Java方法。

public class NativeBridge {
	...
	
    // 供Native调用的方法。
    public void printResult(String result) {
        System.out.println(" print result = " + result);
    }
}

7.2、Native调用流程。

  1. 获取Java方法所在类的字节码

    static const char *const jclassName = "com/hongenit/jnindkdemo/NativeBridge";
    // 获取NativeBridge的字节码对象
    jclass jclazz = env->FindClass(jclassName);
    

    还可以通过以下两个方法获取

    // 通过对象实例来获取jclass,相当于Java中的getClass()函数
    jclass GetObjectClass(jobject obj):
    
    // 通过jclass可以获取其父类的jclass对象
    jclass getSuperClass(jclass obj):
    
  2. 获取对象

    构造方法的methodId固定为“” ,根据签名不同调用不同的构造方法。

    jobject obj= env->NewObject(jclazz,env->GetMethodID(jclazz, "<init>", "()V"));
    
  3. 调用方法

    // 获取要调用的Java方法的methodID
    jmethodID methodId = env->GetMethodID(jclazz, "printResult", "(Ljava/lang/String;)V");
    // 调用Java方法。
    env->CallVoidMethod(obj,methodId,env->NewStringUTF(("call from native " + std::to_string(param)).c_str()));
    
void nativeMethod2(JNIEnv *env, jclass clazz, jint param) {
    
    static const char *const jclassName = "com/hongenit/jnindkdemo/NativeBridge";
    // 获取NativeBridge的字节码对象
    jclass jclazz = env->FindClass(jclassName);
    // 获取NativeBridge对象
    jobject obj= env->NewObject(jclazz,env->GetMethodID(jclazz, "<init>", "()V"));
    // 获取要调用的Java方法的methodID
    jmethodID methodId = env->GetMethodID(jclazz, "printResult", "(Ljava/lang/String;)V");
    // 调用Java方法。
    env->CallVoidMethod(obj,methodId,env->NewStringUTF(("call from native " + std::to_string(param)).c_str()));

}

注意事项

1、全局引用对象的内存泄露问题

参考文献

https://www.jianshu.com/p/d8be99605c65

https://blog.csdn.net/carson_ho/article/details/73250163

https://blog.csdn.net/q610098308/article/details/79395232

  移动开发 最新文章
Vue3装载axios和element-ui
android adb cmd
【xcode】Xcode常用快捷键与技巧
Android开发中的线程池使用
Java 和 Android 的 Base64
Android 测试文字编码格式
微信小程序支付
安卓权限记录
知乎之自动养号
【Android Jetpack】DataStore
上一篇文章      下一篇文章      查看所有文章
加:2021-11-14 21:50:06  更:2021-11-14 21:51:59 
 
开发: 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 3:57:40-

图片自动播放器
↓图片自动播放器↓
TxT小说阅读器
↓语音阅读,小说下载,古典文学↓
一键清除垃圾
↓轻轻一点,清除系统垃圾↓
图片批量下载器
↓批量下载图片,美女图库↓
  网站联系: qq:121756557 email:121756557@qq.com  IT数码