Android JNI:入门

JNI是Java Native Interface的缩写,它提供了若干的API实现了Java和其他语言的通信(主要是C&C++)。JNI标准成为java平台的一部分,它允许Java代码和其他语言写的代码进行交互。

    所以:android 可以通过 JNI 调用本地的 C/C++ 代码,本地的C/C++的代码也可以调用 android 代码。

    Android C++开发环境需要额外用到:

  • NDK:Android 原生开发包(Native Develoment Kit),是Android 软件开发包SDK的相关工具集。它包含 API、交叉编译器、链接程序、调试器、构建工具、文档和示例应用程序。

  • Cmake是一个跨平台的安装(编译)工具

  • LLDBAndroid Studio上面调试本地代码的工具。

 

这里以Android studio IDE为例。

  一、安装Android C++开发环境需要的功能

1、打开android SDK管理工具:

在Android studio的启动页面,选择Configure,点击SDK Manager

或者可以在编辑界面 点击 在 Android Studio右上角 的此按钮

 

2、在 SDK Tools中,下载 CMake、LLDB 和 NDK

(注意:CMake 和 NDK 并非一定要通过 SDK Tools 这里下载,你也可以到官网下载安装)

安装后,你可以在Android SDK安装目录下,看到你安装的模块:

 

二、创建HelloJni项目

创建一个android项目,注意勾选上 Include C++ support 包含C++环境支持

PS:创建项目后,如果报错:

Error:No toolchains found in the NDK toolchains folder for ABI with prefix: mips64el-linux-android

错误提示应该是:NDK安装目录下的 toolchains目录中 缺少了:mips64el-linux-android

解决方法:你可以 在官网重新下载一个NDK:https://developer.android.google.cn/ndk/downloads/ 把缺少的文件复制到相应的目录。重新编译项目即可。

 

项目创建后,可以发现比普通的Android项目 多了些内容:

其中: 

.externalNativeBuild文件夹: cmake编译好的文件, 显示支持的各种硬件等信息。系统生成。 

cpp文件夹: 存放C/C++代码文件,native-lib.cpp文件是该Demo中自带的示例。 

CMakeLists.txt文件: CMake脚本配置的文件。

并且:在build.gradle文件中,多了如下内容:

其中 第二处,由于设置 CMakeLists.txt文件的路径

 

三、原生方法的使用

见创建项目时,默认给出的Demo:

package com.hellojni;

import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.widget.TextView;

public class MainActivity extends AppCompatActivity {

    // 在应用程序启动时,加载'native-lib'库,这是我们自己生成的库,这个库名 见CMakeLists.txt 中的配置
    static {
        System.loadLibrary("native-lib");
    }

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

        // 一个例子,调用native方法:stringFormJNI() 
        // 方法从native中生成一个字符串,并返回,java把此字符串设置为TextView的文字
        TextView tv = (TextView) findViewById(R.id.sample_text);
        tv.setText(stringFromJNI());
    }

    /**
     * 'native-lib'库中的一个方法,用native关键字声明此方法,即可在java中调用此方法
     */
    public native String stringFromJNI();
}

 

    用native关键字声明的方法,为原生方法,没有方法体,这个关键字通知 java 编译器,它用另一种语言提供该方法的实现。

    原生方法在库中被实现,所以需要先加载这个库,以便虚拟机能够找到原生方法的实现。java.lang.System类提供了两个静态方法,用于运行时加载共享库:loadloadLibrary

    由于 java 技术的设计目标时平台独立,作为 java框架 API的一部分,loadLibrary 也要保持独立性。尽管 Android NDK 生成的实际共享库文件名为:libnative-lib.so ,但是 loadLibrary 方法只采用 native-lib 这个库名,再按照所使用的具体操作系统的要求加上必要的前缀或后缀。

 

四、原生方法的实现

见cpp文件夹下的 native-lib.cpp

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

extern “C"

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

包含头文件:

    #include <jni.h> :  这个头文件中包含了 JNI 数据类型和函数的定义。

extern “C” :  

    主要作用就是为了能够正确实现C++代码调用其他C语言代码。加上extern "C"后,会指示编译器这部分代码按C语言的进行编译,而不是C++的。详细了解可见这篇文章: https://www.cnblogs.com/carsonzhu/p/5272271.html

方法的返回值

    jstring 是一种 JNI类型,对应 java 中的 String 类型。关于JNI的数据类型,后面的笔记再详细说明。

方法名

    这里JNIEXPORT JNICALL 都是JNI的关键字,表示此函数是要被JNI调用的。

  Java_com_hellojni_MainActivity_stringFromJNI 这里用了完全限定的函数来声明方法:即:Java+包名+类名+方法名。这种显式的函数命名让虚拟机在加载的共享库中自动查找原生函数。

    尽管 Java 方法中声明的 stringFromJNI 不带任何参数,但是在 C++ 中实现的 stringFromJNI 方法 却有两个参数:JNIEnv *env jobject

    第一个参数:JNIEnv 是指向 JNI 函数表的接口指针。

    第二个参数:jobject 是native方法所在 java 类实例的 java 对象引用。即:MainActivity类实例的引用。

方法内容

    简单说一下这两句代码的作用:

std::string hello = "Hello from C++";

return env->NewStringUTF(hello.c_str());

    创建了一个字符串,赋值为:“Hello from C++”

    通过env 调用JNI的函数:NewStringUTF,把C++的字符串转换为Java字符串。最后方法返回此java字符串。

 

JNIEnv 接口指针:

    原生代码通过 JNIEnv 接口指针提供的各种函数来使用虚拟机的功能。JNIEnv 是一个指向 线程-局部数据的指针,而线程-局部数据中包含指向函数表的指针。

    注意:传递给每一个原生方法调用的 JNIEnv 接口指针 在 方法调用相关的线程中也有效,但它不能被缓存以及其它线程使用。

    另:在C 或 C++ 语法中调用 JNI函数,写法是不同的。

    C代码中

        C代码中 JNIEnv 是指向 JNINativeInterface 结构的指针,为了访问任何任何一个 JNI函数,该指针需要先被 解引用。因为C代码中的 JNI函数 不了解当前的 JNI环境,JNIEnv实例应该作为第一个参数 传递给每一个 JNI函数调用者,调用方法如下:

(*env)->NewStringUTF(env, hello.c_str());

    C++代码中

        C++代码中 JNIEnv 实际上是C++类实例,JNI 函数以成员函数的形式存在。因为 JNI方法 已经访问了当前的JNI 环境,因此JNI方法调用 不要求 JNIEnv实例 作为参数。调用方法如下:

env->NewStringUTF(hello.c_str());

 

实例方法 与 静态方法:

    原生方法可以是 实例方法,如本demo中,写的就是实例方法,实例方法与类实例有关,它们只能在类实例中调用。

    定义方式:

JNIEXPORT jstring JNICALL

Java_com_hellojni_MainActivity_stringFromJNI(JNIEnv *env, jobject thiz)

    第二个参数获取的是 实例引用,jobject类型

    原生方法也可以是 静态方法,可以在静态上下文中直接调用。

JNIEXPORT jstring JNICALL

Java_com_hellojni_MainActivity_stringFromJNI(JNIEnv *env, jclass clazz)

    因为静态方法没有与实例绑定,第二个参数获取 类引用 而不是 类实例,是 jclass类型

 

 

五、定义配置文件,把原生代码打包成库

见CMakeLists.txt 脚本文件:

# 设置创建本地库需要的最小Cmake版本

cmake_minimum_required(VERSION 3.4.1)

# 创建并命名你的库,可以定义多个库

add_library( # 设置库名,注意:实际上生成的so文件名是libnative-lib
             native-lib

             # 设置库的模式为 共享库.编译项目后,在Module级别的build文件下的intermediates\transforms\mergeJniLibs\debug\folders\2000\1f\main下会生成相应的so库文件。
             # 此外,so库文件都会在打包到.apk里面,可以通过选择菜单栏的*Build->Analyze Apk...**查看apk中是否存在so库文件,一般它会存放在lib目录下
             SHARED

             # 创建库的源文件.
             src/main/cpp/native-lib.cpp )

# 这个方法与我们要创建的so库无关而是使用NDK的Apis或者库。
# 默认情况下Android平台集成了很多NDK库文件,所以这些文件是没有必要打包到#apk里面去的。直接声明想要使用的库名称即可。
# 在这里不需要指定库的路径,因为这个路径已经是CMake路径搜索的一部分。如示例中使用的是log相关的so库。

find_library( # 这个指定的是在NDK库中每个类型的库会存放一个特定的位置,而log库存放在log-lib中.
              log-lib

              # 指定的库名
              log )

# 如果你本地的库(native-lib)想要调用log库的方法,那么就需要配置这个属性,意思是把NDK库关联到本地库.

target_link_libraries( # 要被关联的库名称
                       native-lib

                       # 要关联的库名称,要用大括号包裹,前面还要有$符号去引用
                       ${log-lib} )

    通过 add_library 配置我们要生成的库即可。

    我们可以自己创建CMakeLists.txt文件,而且路径不受限制,只要在build.gradle中配置externalNativeBuild.cmake.path来指定该文件路径即可。

 

六、运行代码

可以看到界面上显示 Hello from C++

同时,打开Module级别的build文件下的 intermediates\transforms\mergeJniLibs\debug目录,还能找到生成的库文件:

此外,so库文件都会在打包到.apk里面,可以通过选择菜单栏的*Build->Analyze Apk...**查看apk中是否存在so库文件,一般它会存放在lib目录下。

  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 0
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值