Android的JNI调用

Posted

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Android的JNI调用相关的知识,希望对你有一定的参考价值。

  android提供NDK开发包来提供Android平台的C++开发,用来扩展Android SDK的功能。主要包括Android NDK构建系统和JNI实现与原生代码通信两部分。

一、Android NDK构建系统

 1.1 构建库

   Android NDK的构建系统是基于GNU Make的。Android GNU Make系统除了需要一些内部的GNU片段外,还需要两个文件:Android.mk和Application.mk。Android NDK源码给了很多的例子,以HelloJni为例,Android.mk源码: 

#Android.mk必须以LOCAL_PATH变量开头
LOCAL_PATH := $(call my-dir) #清除除了LOCAL_PATH以外的LOCAL_<name>变量,例如LOCAL_MODULE与LOCAL_SRC_FILES等 include $(CLEAR_VARS) #每一个原生组件被称为一个模块 LOCAL_MODULE := hello-jni
#源文件 LOCAL_SRC_FILES :
= hello-jni.c #编译为共享库,即后缀名为.so include $(BUILD_SHARED_LIBRARY)

  Application.mk源码: 

 #一般选择APP_ABI := armeabi-v7a就够了
 APP_ABI := all

  为了建立可供主应用程序使用的模块,必须将该模块变成共享库。按照上述必不可少的步骤,可以继续编译多个共享库。

  Android也可以编译静态库(后缀名为.a),但是实际的Android应用程序并不直接使用静态库,并且应用程序包中也不包含静态库。静态库可以用来构建共享库。但是,当静态库与多个共享库相连时,应用程序包中会包含静态库的多个副本,徒增应用程序包的大小。这种情况下,可以不构建静态库,而是将通用模块作为共享库建立起来,动态连接依赖模块以消除重复的副本。如下Android.mk实现的是共享库之间的代码共享。 

LOCAL_PATH := $(call my-dir)

#第三方AVI库
include $(CLEAR_VARS)
LOCAL_MODULE    := avilib
LOCAL_SRC_FILES := avilib.c
include $(BUILD_SHARED_LIBRARY)

#原生模块1
include $(CLEAR_VARS)
LOCAL_MODULE    := module1
LOCAL_SRC_FILES := module1.c

LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY)

#原生模块2
include $(CLEAR_VARS)
LOCAL_MODULE    := module2
LOCAL_SRC_FILES := module2.c

LOCAL_SHARED_LIBRARIES := avilib
include $(BUILD_SHARED_LIBRARY) 

1.2 Prebuilt库

  共享模块编译时要求有源代码,为此Android提供了Prebuilt库,以下场合,Prebuilt库是非常有用的:

  1. 想在不发布源代码的情况下将你的模块发布给他人;
  2. 想使用共享模块的预建版来加速构建过程。

  其他构建系统变量:

  LOCAL_CFLAGS:一组可选的编译器标志,在编译C和C++源文件的时候会被传送给编译器;

  LOCAL_CPP_FLAGS:一组可选的编译器标志,在只编译C++源文件时被传送给编译器;

  LOCAL_LDLIBS:链接标志的可选列表,它主要用于传送要进行动态链接的系统库列表。如链接日志库:

  LOCAL_LDFLAGS += -llog 

  APP_CPPFLAGS:编译器标志,在编译任何模块的C++源文件时这些标志都会被传送给编译器。 

  nkd-build脚本命令:

ndk-build –C /project path
ndk-build –B
ndk-build clean 

二、JNI实现与原生代码通信 

  Java层代码如下: 

public class HelloJni extends Activity
{
    /** Called when the activity is first created. */
    @Override
    public void onCreate(Bundle savedInstanceState)
    {
        super.onCreate(savedInstanceState);

        TextView  tv = new TextView(this);
        tv.setText( stringFromJNI() );
        setContentView(tv);
    }

    /* A native method that is implemented by the
     * \'hello-jni\' native library, which is packaged
     * with this application.
     */
    public native String  stringFromJNI();

    /* This is another native method declaration that is *not*
     * implemented by \'hello-jni\'. This is simply to show that
     * you can declare as many native methods in your Java code
     * as you want, their implementation is searched in the
     * currently loaded native libraries only the first time
     * you call them.
     *
     * Trying to call this function will result in a
     * java.lang.UnsatisfiedLinkError exception !
     */
    public native String  unimplementedStringFromJNI();

    /* this is used to load the \'hello-jni\' library on application
     * startup. The library has already been unpacked into
     * /data/data/com.example.hellojni/lib/libhello-jni.so at
     * installation time by the package manager.
     */
    static {
        System.loadLibrary("hello-jni");
    }
}  

  JNI代码如下,其中Java方法stringFromJNI不带任何参数,但是原生方法带两个参数:

jstring
Java_com_example_hellojni_HelloJni_stringFromJNI( JNIEnv* env,
                                                  jobject thiz )
{
    return (*env)->NewStringUTF(env, "Hello from JNI !");
}

  第一个参数JNIEnv是指向可用JNI函数表的借口指针;第二个参数jobjects是HelloJni类实例的Java对象引用。  

  这里注意C与C++代码稍有不同,C代码如下: 

  return (*env)->NewStringUTF(env, "Hello from JNI ! "); 

  C++代码如下: 

  return env->NewStringUTF("Hello from JNI ! "); 

  这是因为C++代码中,JNIEnv实际上是一个C++类实例,JNI函数以成员函数的形式存在,因此JNI方法调用不要求JNIEnv实例作参数。

2.1 C/C++ 头文件生成器:javah

  JDK自带一个名为javah的命令行工具,该工具由Java类文件的原始定义生成原生函数名及其参数列表,这样程序员避免编写繁杂多余的定义。C/C++源文件只需要包含这个头文件并提供原生方法实现。 

  javah的参数列表如下: 

C:\\Users\\jiayayao>javah
用法:
  javah [options] <classes>
其中, [options] 包括:
  -o <file>                输出文件 (只能使用 -d 或 -o 之一)
  -d <dir>                 输出目录
  -v  -verbose             启用详细输出
  -h  --help  -?           输出此消息
  -version                 输出版本信息
  -jni                     生成 JNI 样式的标头文件 (默认值)
  -force                   始终写入输出文件
  -classpath <path>        从中加载类的路径
  -cp <path>               从中加载类的路径
  -bootclasspath <path>    从中加载引导类的路径
<classes> 是使用其全限定名称指定的
(例如, java.lang.Object)。

  生成头文件的命令行参数如下:

 javah -classpath bin/classes com.example.hellojni.Hellojni  

2.2 数据类型

Java有两种数据类型:基本数据类型和引用数据类型:基本数据类型中Java/JNI/C++的映射关系如下:

   引用类型的类型映射关系如下: 

  引用类型以不透明的引用方式传递给原生代码,而不是以原生数据类型的的形式呈现,因此引用类型不能直接使用和修改。JNI提供了与这些引用类型密切相关的一组API。

  字符串操作: 

// 创建字符串
jstring javaString;
javaString = (*env)->NewStringUTF(env, "hello world!");
// 内存溢出时,会返回NULL,注意判空

// 将Java字符串转换成C字符串
const jbyte* str;
jboolean isCopy;
str = (*env)->GetStringUTFChars(env, javaString, &isCopy);
if (0 != str) {

}

// 释放字符串
(*env)->ReleaseStringUTFChars(env, javaString, str);

   数组操作: 

// 创建数组
jintArray javaArray;
javaArray = (*env)->NewIntArray(env, 10);
if(0!=javaArray) {
    
}

// Get<Type>ArrayRegion函数将给定的基本Java数组复制到给定的C数组中
// 将Java数组区复制到C数组中 jint nativeArray[10]; (*env)->GetIntArrayRegion(env, javaArray, 0, 10, nativeArray); // 从C数组向Java数组提交所做的修改 (*env)->SetIntArrayRegion(env, javaArray, 0, 10, nativeArray);

   原生方法的内存分配超出了虚拟机的管理范围,且不能用虚拟机的垃圾回收器回收原生方法中的内存。

  原生代码回到Java损耗性能,建议将所有需要的参数传递给原生代码调用,而不是让原生代码回到Java中。

  先记录这么多,以后接着补充。

以上是关于Android的JNI调用的主要内容,如果未能解决你的问题,请参考以下文章

[RK3568][Android11]JNI调用流程分析

[RK3568][Android11]JNI调用流程分析

深入了解android平台的jni---本地多线程调用java代码

android开发源代码分析--多个activity调用多个jni库的方法

Android使用JNI,C++调用JAVA代码,能找到JAVA的函数无法调用?

Android JNI之Java和C互相调用