JNI和NDK编程-JNI入门

JNI和NDK编程-使用AndroidStudio进行NDK开发

JNI和NDK编程-JNI入门


版权声明:本文为博主原创文章,欢迎大家转载!

转载请标明出处: http://blog.csdn.net/guiying712/article/details/78400415,本文出自:【张华洋的博客】


1、原生开发工具包 (NDK) 是一组可让您在 Android 应用中利用 C 和 C++ 代码的工具。 NDK可以让您将 C 和 C++ 源代码构建为可用于Android应用的共享库,或者利用现有的预构建库。

2、JNI是Java Native Interface。它定义了一种Java代码与本地代码交互的方式,是一种允许运行与 JVM 的Java程序去调用(反向依然)本地代码(通常 JNI 面向的本地代码是用C、C++以及汇编语言编写的)的编程框架。

JNI和NDK编程-使用AndroidStudio进行NDK开发 中已经详细介绍了如果创建一个支持C/C++的新项目,以及如何让已有的项目支持 C/C++。这篇文章将介绍如何进行JNI开发。

在介绍 JNI 开发之前,我们先考虑个问题,在Android开发中我们真的需要 JNI 吗? 本地代码通常与硬件或者操作系统有关联,我们平时都是用Java语言来开发Android应用的,好像并不需要使用本地语言,但是实际上在Android系统中就采用了大量JNI手段去调用本地层的实现库,如果我们想要深入学习Android系统原理就必须掌握JNI,当然在Android开发中,还有以下几种情况需要用到 JNI :

  • 应用程序需要一些平台相关的 特性的支持,而Java是无法满足的;
  • 兼容已有的用其他语言编写的代码库(例如在Android上集成 FFmpeg),使用JNI技术可以让Java层的代码访问这些现成的库,实现一定程度的代码复用;
  • 应用程序的某些关键操作对运行速度要求较高,这部分代码可以本地语言如C语言来编写,再通过JNI向Java层提供访问接口;
  • 在Android中,本地代码通常是已 so库 的形式存在,由于 so库 反编译比较困难,因此可以使用本地语言编写某些重要业务代码,提高代码的安全性;

假设你已经阅读过JNI和NDK编程-使用AndroidStudio进行NDK开发,并且按照教程创建好了项目,那么我就可以进入JNI的开发流程了。

Java函数的本地实现

1、首先看下Java代码:

package com.guiying712.ndkdemo;

public class MainActivity extends AppCompatActivity {

    // Used to load the 'native-lib' library on application startup.
    static {
        System.loadLibrary("native-lib");
    }

    @Override
    protected void onCreate(Bundle savedInstanceState) {
        super.onCreate(savedInstanceState);
        setContentView(R.layout.activity_main);

        // Example of a call to a native method
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

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

很明显,MainActivity 跟我们以往编写的 Activity 有一些区别:

1、在MainActivity 中有个静态代码块,并且在 static块中出现了 System.loadLibrary("native-lib");这行代码。上面的注释已经很清楚了,这段代码的作用就是在应用程序启动的时候将 ‘native-lib’ 这个动态链接库加载进来, ‘native-lib’ 是动态链接库的标识,so库的完整名称是:libnative-lib.so

2、修饰 stringFromJNI() 方法 中有 native 关键字 , 在 Java 代码中声明本地方法必须有 native 标识符, native修饰的方法,在 Java 代码中只作为声明存在,需要我们用本地语言去实现这个 native方法,而我们实现 native方法的代码就在 ‘native-lib’ 这个动态链接库中,所以在调用stringFromJNI()之前,就必须先将 **‘native-lib’ 库 **加载进来,将 System.loadLibrary("native-lib");置于 static 块中,可以在 Java VM 初始化一个类时,首先执行这部分代码,这可保证调用本地方法前,已经装载了本地库。

2、接下来看下native 代码:


#include <jni.h>
#include <string>

extern "C"
JNIEXPORT jstring JNICALL Java_com_guiying712_ndkdemo_MainActivity_stringFromJNI(
        JNIEnv *env, jobject /* this */) {
    std::string hello = "Hello from C++";
    return env->NewStringUTF(hello.c_str());
}

是不是感觉很奇怪 Java_com_guiying712_ndkdemo_MainActivity_stringFromJNI 这玩意怎么这么长,跟我们Java方法的命名方式区别太大了。其实 Java_com_guiying712_ndkdemo_MainActivity_stringFromJNI 就是Java代码中stringFromJNI() 的本地代码实现。在JNI 中,函数名的格式需要遵循如下的规则:

Java _ 包名 _ 类名 _ 方法名

我们可以比照下上面的native代码中的函数名称是不是这样命名的,我的Java代码包名是:com.guiying712.ndkdemo。当然你可能会想,Java代码中那么多本地方法声明,要是一个一个都这样命名岂不是很痛苦,其实AndroidStudio是有快捷方式创建native代码中的本地函数的,我们可以在 MainActivity 中再另创建一个本地方法:getStringFromJNI(String name);

public native String getStringFromJNI(String name);

当你在Java代码中声明一个native方法后,由于还没有实现,所以这个方法是红色,这时候你用鼠标点击 这个方法名称 ,然后在键盘上按:ALT键+Enter键(回车键),就会弹出创建这个native方法的提示,点击创建即可。当一个Java的native方法有了本地函数实现后,AndroidStudio就会贴心的在编辑器旁边显示一个箭头图标,就像下图 红圈标出来的那个图标,点击这个图标可以直接跳转到native函数中,同理native函数也可以通过点击这个箭头跳转到Java方法声明中:

本地函数创建

我们继续介绍上面的本地函数,其中 jstring 是代表的是 stringFromJNI()方法中返回的String类型参数,jstring是JNI中的一种数据类型,这个我们完了再介绍,这里只需要知道Java的String对应于JNI的jstring即可。JNIEXPORT、 JNICALL、JNIEnv和jobject都是JNI标准中所定义的类型或者宏, 它们的含义如下:

JNIEXPORT和JNICALL: 它们是JNI中所定义的宏, 可以在jni.h这个头文件中查找到;

JNIEnv*: 表示一个指向JNI环境的指针, 可以通过它来访问JNI提供的接口方法;

jobject: 表示Java对象中的this;

另外在JNI函数前必须有 extern "C",它指定 extern "C" 内部的函数采用C语言的命名风格来编译,否则当JNI采用C++来实现时, 由于C和C++编译过程中对函数的命名风格不同, 这将导致 JNI 在链接时无法根据函数名查找到具体的函数, 那么无法JNI调用。

从上面的 Java_com_guiying712_ndkdemo_MainActivity_stringFromJNI 函数中,我们可以看到诸如 JNIEnv 、jobject这样的参数类型,而Java代码中的stringFromJNI()本身却没有携带者两个参数。我们通过名称可能会想到它们应该类似于C++中的 this 指针,即代表了类的一个实例化对象。 事实上也确实如此,JNIEnv 的定义如下:

struct _JNIEnv;
struct _JavaVM;
typedef const struct JNINativeInterface* C_JNIEnv;

#if defined(__cplusplus)
typedef _JNIEnv JNIEnv;
typedef _JavaVM JavaVM;
#else
typedef const struct JNINativeInterface* JNIEnv;
typedef const struct JNIInvokeInterface* JavaVM;
#endif

/*
 * Table of interface function pointers.
 */
struct JNINativeInterface {
  //各种JNI的数据类型和函数
  jint        (*GetVersion)(JNIEnv *);
  jclass      (*FindClass)(JNIEnv*, const char*);
  ...
  void        (*SetObjectField)(JNIEnv*, jobject, jfieldID, jobject);
  ...
}

JNINativeInterface 是一个 JNI 的本地接口,包含了很多实用的函数,而 jobject 则代表了这个 本地类方法对应的 Java 类 实例

不过 Java_com_guiying712_ndkdemo_MainActivity_stringFromJNI() 函数返回的是jstring,而不是 String类型,这是因为 JNI 已经对所有的 Java标准数据类型做了相应的 typedef ,具体可以看下表:


Java类型JNI类型说明
booleanjbooleanunsinged 8 bits
bytejbytesigned 8 bits
charjcharunsigned 16 bits
shortjshortsinged 16 bits
intjintsigned 32 bits
longjlongsigned 64bits
floatjfloat32 bits
doublejdouble64 bits
voidvoidvoid
表1:JNI 基础类型对照表

Java类型JNI类型说明
ObjectjobjectObject类型
java.lang.ClassjclassClass类型
java.lang.Stringjstring字符串
java.lang.ThrowablejthrowableThrowable
Object[]jobjectArray对象数组
boolean[]jbooleanArrayboolean数组
byte[]jbyteArraybyte数组
char[]jcharArraychar数组
short[]jshortArrayshort数组
int[]jintArrayint数组
long[]jlongArraylong数组
float[]jfloatArrayfloat数组
double[]jdoubleArraydouble数组
表2:JNI 引用数据类型对照表

基础类型的变量可以在 Java和 本地代码件进行拷贝,而Java对象需要通过引用类型进行传递。JVM需要跟踪所有它传递给本地代码的对象实例,这样才能保证它们不被垃圾回收器回收,而当本地代码不再使用这些对象时,也要及时通知JVM。

类型转换


还记得我们前面创建的 getStringFromJNI(String name)方法吗?


    /**
     * 这个方法的目的是输入程序员的名称,返回 Hello : 大佬程序员
     *
     * @param name 程序员的名称
     * @return Hello : 大佬
     */
    public native String getStringFromJNI(String name);
    

native实现函数:


extern "C"
JNIEXPORT jstring JNICALL
Java_com_guiying712_ndkdemo_MainActivity_getStringFromJNI(JNIEnv *env, jobject instance,
                                                          jstring name_) {

    const char *name = env->GetStringUTFChars(name_, 0);

    std::string hello = "Hello : ";

    std::string coderName = std::string(name);

    std::string helloCoder = hello + coderName;

    //释放GetStringUTFChars(name_, 0)的内存
    env->ReleaseStringUTFChars(name_, name);

    return env->NewStringUTF(helloCoder.c_str());
}

相比基本类型,对象类型的传递要复杂很多。 Java 层对象作为 对象引用(指针) 传递到 JNI 层。对象引用(指针) 是一种 C 的指针类型,它指向 JavaVM 内部数据结构。使用这种指针的目的是:不希望 JNI 用户了解 JavaVM 内部数据结构。对 对象引用(指针) 所指结构的操作,都要通过 JNI 方法进行。 比如,java.lang.String对象,JNI 层对应的类型为 jstring,对该 对象引用(指针) 的操作要通过 JNIEnv->GetStringUTFChars 进行。

JNI 支持 Unicode/UTF-8 字符编码互转。Unicode 以 16-bits 值编码;UTF-8 是一种以字节为单位变长格式的字符编码,并与 7-bits ASCII 码兼容,UTF-8 字串与 C 字串一样,以 NULL(’\0’) 做结束符, 当 UTF-8 包含非 ASCII 码字符时,以’\0’做结束符的规则不变,7-bit ASCII 字符的取值范围在 1-127 之间,这些字符的值域与 UTF-8 中相同,当最高位被设置时,表示多字节编码。

std::string hello = "Hello : ";是C++代码中的字符串,我们需要将 C/C++字符串转换成 JNI 中的 jstring 返回给Java代码。 env->NewStringUTF( helloCoder.c_str() ) ,使用 JNIEnv->NewStringUTF构造一个 java.lang.String,如果此时没有足够的内存,NewStringUTF 将抛 OutOfMemoryError 异常,同时返回 NULL。


JNI和NDK编程-使用AndroidStudio进行NDK开发

JNI和NDK编程-JNI入门

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值